我也不算是真正的单片机开发人员,只是项目需要,单片机程序也一直在优化,优化最多的串口通信,尽可能快的传输大数据,以前直接用中断的方式,稳定,但传输稍大的数据时,需要的时间就久了,特别是需要传输扫描枪扫到的图片时,就会等待很久。后来改成了DMA,也只有DMA是最快了。好了,进入正题
1.百度,是最好的老师,可以查到很多前辈积累的经验和方法,我也是参考了他们的
2.DMA任意字符的接收,很多是使用串口的USART_IT_IDLE空闲中断,没错, 我也使用它了。
3.还有使用DMA双缓冲区,当时研究时发现双缓冲转换不是很方便,就没用了。
4.使用DMA的DMA_IT_TC中断,当接收满时就触发中断,这个我也需要用到
5.有前辈使用DMA与环形缓冲区结合,这个方法很好,不过我有点看不懂,对于手上的项目改动太大,就没继续深研
6.我使用了环型缓冲区,可以做到数据与业务分离,很方便
7.我上了RTX系统,多任务,很方便,也比较危险,所以线程同步要做好
好了,我要说下我的思路
我使用的单片机是STM32F205,RTX系统,关于串口和DMA配置忽略吧,百度都能查到
接收数据:
前面说到要用到的两个中断,串口的空闲和DMA的接收完成中断,两个相结合后就可以实现任意长度了(说是任意长度,其实接收的长度当然要小于分配的环型缓冲区)。我使用了环型缓冲区用来接收数据,这样就可以做到业务逻辑的分离了,接收的只管接收,处理的只管处理
//串口1中断处理
void USART1_IRQHandler(void)
{
uint8_t ID = 0; //串口序号
uint16_t Size = 0;
if ( USART_GetITStatus( USART1, USART_IT_IDLE ) != RESET ) //串口空闲中断 用于DMA
{
USART_ClearITPendingBit( USART1, USART_IT_IDLE ); //清除空闲中断
USART_ReceiveData( USART1); //读DR,只有读过一次,才能真正清除标志
USART_ClearFlag( USART1, USART_FLAG_IDLE ); //读SR其实就是清除标志
DMA_Cmd(DMA2_Stream2, DISABLE); //先停止DMA才行设置缓冲区大小
Size = DMA_USART_RecieveBuffer_Len - DMA_GetCurrDataCounter( DMA2_Stream2 ); //获得DMA当前收到的字节数,因为DMA中断只有接收长度满时才会触发,所以要把剩余数据读出来 DMA2 Stream2-Channel4
if(Size > 0) //如果有剩余数据
FIFO_PutBuff( &USART1_FIFO_Receive, DMA_USART1_Recieve_Buff, Size ); //将收到的DMA缓冲复制进串口输入FIFO FIFO_PutBuff()函数是环型缓冲区写入Buff的用到的,USART1_FIFO_Receive 环型缓冲区结构, DMA_USART1_Recieve_Buff DMA接收缓冲区
USART_DMA_Status[ID].USART_IS_IDLE = 1; //自定义标识 串口空闲标识
USART_DMA_Status[ID].DMA_ReceiveOK = 1; //自定义标识 DMA接收完成标识
DMA_SetCurrDataCounter( DMA2_Stream2, DMA_USART_RecieveBuffer_Len ); //重新设置DMA的读取缓冲区长度 DMA_USART1_Recieve_Buff[DMA_USART_RecieveBuffer_Len]
DMA_Cmd(DMA2_Stream2, ENABLE); //开启DMA
}
}
//DMA中断处理
void DMA2_Stream2_IRQHandler(void)
{
uint8_t ID = 0; //串口序号
if( DMA_GetITStatus(DMA2_Stream2, DMA_IT_TCIF2) != RESET )
{
DMA_ClearITPendingBit(DMA2_Stream2, DMA_IT_TCIF2);
//重新设置DMA坊取缓冲区长度时会触发该中断,所以当有该标志时,不处理该条中断。
//在实际使用中,发现经常接收的数据有出入,跟踪后发现在串口中断USART_IT_IDLE重新设置DMA缓冲长度时,会触发该中断,所以加入了自定义标识USART_DMA_Status[ID].USART_IS_IDLE,为1时,说明已经空闲了,不再将数据写入环型缓冲区
if( USART_DMA_Status[ID].USART_IS_IDLE != 1 )
{
FIFO_PutBuff( &USART1_FIFO_Receive, DMA_USART1_Recieve_Buff, DMA_USART_RecieveBuffer_Len ); //将DMA缓冲区数据写入环型缓冲区
USART_DMA_Status[ID].DMA_ReceiveOK = 1; //DMA接收完成
}
USART_DMA_Status[ID].USART_IS_IDLE = 0; //重置串口空闲中断
}
}
业务逻辑处理:
//项目在通信中,使用了定长命令头协议方式,这样能更好的处理数据,可以知道这个命令是做什么的,以及这个命令将要接收的数据的长度
typedef struct
{
uint8_t Cmd; //命令标识
uint8_t CmdType; //命令类型
uint32_t DataLen; //发送数据长度,用于下次发送的数据长度
uint32_t Param1; //参数1
uint32_t Param2; //参数2
uint32_t Param3; //参数3
uint32_t Param4; //参数4
uint8_t LRC; //校验码 除最后一位LRC码外所有字节的XOR运算
} TCMDHeader; //串口 命令头 结构体
void Query_USART1(void)
{
uint16_t BuffSize = FIFO_GetSize( &USART1_FIFO_Receive ); //串口1缓冲区可读字节数
if( BuffSize > 0 ) //如果串口1有数据
{
ReadSize = FIFO_ReadByte( &USART1_FIFO_Receive, (uint8_t *) &USART_Receive_CMD, sizeof( TCMDHeader ), 50 ); //读取命令头 超时50ms
if( ReadSize != sizeof( TCMDHeader ) ) //如果读到的大小与待读大小不一致,则退出
{
return;
}
if( Check_LRC( (uint8_t *) &USART_Receive_CMD, ReadSize) ) //命令头LRC校验成功
{
//这里通过USART_Receive_CMD命令来区分命令的类型和处理
//......
}
}
}
以上就是接收的部分,我的项目中两块单片机使用串口进行大数据传输,单片机1把采集的图片通过串口发送到单片机2上,单片机2首先接收命令头,通过命令头知道下次待接收的数据大小,这样单片机1只管发数据,单片机2处理命令头后循环处理环型缓冲的数据,边接收边通过网络接口透传到上位机上,直到数据接收完成。单片机1的发送也是通过DMA方式发送,当然也是任意长度的发送。
下面我就说说DMA如何发送任意长度
发送数据:
思路是这样的:发送也有一个环型缓冲区,发送的时候把数据填入缓冲区中,直到填满,如果未开启DMA发送中断,则开启,开启后,调用函数只管往缓冲区填数据,DMA的发送完成中断会判断缓冲区是否为空,空则发送完成,不空,则继续调用DMA发送
//DMA的底层发送函数,每调用一次发送一个包 SendFIFO是发送的环型缓冲区,结构与接收的一致
//DMAy_Streamx DMA通道号
//因为函数都是做成通用性的,不同的参数使用不同的串口和DMA流
void USART_DMA_Send( TFIFO* SendFIFO, DMA_Stream_TypeDef* DMAy_Streamx )
{
uint16_t BuffSize = 0;
uint16_t ReadSize = 0;
int ID = USART_Get_DMA_USARTIndex_FromStreamX( DMAy_Streamx ); //通过DMAy_Streamx获得串口序列号 0:USART1 1:USART2...
uint8_t* Buffer = USART_Get_DMA_Buffer_FromStreamX( DMAy_Streamx ); //获得DMA的发送缓冲区 如:if( DMAStreamX == DMA2_Stream7 ) return DMA_USART1_Send_Buff; //USART1 TX
if( SendFIFO == NULL || DMAy_Streamx == NULL || ID == -1 || Buffer == NULL ) return;
//DMA发送标识,如果DMA未开启发送,则开启DMA发送。
//第一次调用发送时 USART_DMA_Status[ID].DMA_IsSending = 0, 开启DMA发送后 USART_DMA_Status[ID].DMA_IsSending = 1 ,之后的发送将不在此处理剩余的数据,而是在发送完成中断中继续发送,如果发送后再置USART_DMA_Status[ID].DMA_IsSending = 0
if( USART_DMA_Status[ID].DMA_IsSending == 0 )
{
BuffSize = FIFO_GetSize( SendFIFO ); //获得发送缓冲区大小
if( BuffSize == 0 ) return; //为空则退出
if( BuffSize > DMA_USART_SendBuffer_Len ) BuffSize = DMA_USART_SendBuffer_Len; //如果发送大小大于DMA发送缓冲,则大小取DMA发送缓冲区大小
if( FIFO_GetBuff( SendFIFO, BuffSize, Buffer, &ReadSize) != 1 ) return; /复制发送数据到DMA缓冲区中 DMA_USART_Send_Buff[ID]:串口数对应的DMA缓冲区
USART_DMA_Status[ID].DMA_IsSending = 1; //设置发送标识为1
DMA_Cmd( DMAy_Streamx, DISABLE ); //停止发送
DMA_SetCurrDataCounter( DMAy_Streamx, ReadSize ); //设置DMA发送缓冲区的大小为待发送大小
DMA_Cmd( DMAy_Streamx, ENABLE ); //开启发送
}
}
//任意字符发送,主要是使用该函数发送,该函数主要是向发送环型缓冲区填入数据,并触发开启第一次DMA发送,之后的发送将在发送中断完成后发送
uint16_t USART_DMA_PostBuff( USART_TypeDef* USARTx, uint8_t *Buff, uint16_t BuffSize )
{
uint16_t Free = 0;
uint16_t SendSize = 0;
TFIFO* SendFIFO = USART_GetSendFIFOName( USARTx ); //获得串口对应的FIFO环型缓冲区
DMA_Stream_TypeDef* DMAy_Streamx = USART_Get_DMAStreamX(USARTx, 1); //取得对应串口的DMA流通道 0:为RX 1:为TX
if( SendFIFO == NULL || DMAy_Streamx == NULL ) return SendSize;
//进入循环发送,直到所有的数据都填入环型缓冲区为止
while( SendSize < BuffSize )
{
Free = FIFO_GetFree( SendFIFO ); //获得串口发送环型缓冲区空闲大小
if( Free > 0 ) //如果有空闲
{
if( Free > BuffSize ) //如果空闲大小大于待发送大小,则直接把待发送数据压入发送缓冲区
{
FIFO_PutBuff( SendFIFO, Buff, BuffSize); //压入发送缓冲区
SendSize = BuffSize; //发送数据大小
USART_DMA_Send( SendFIFO, DMAy_Streamx ); //发送DMA数据
}
else
{
if( BuffSize - SendSize < Free ) Free = BuffSize - SendSize;
FIFO_PutBuff( SendFIFO, (uint8_t *) (Buff + SendSize), Free); //压入发送缓冲区
SendSize += Free;
USART_DMA_Send( SendFIFO, DMAy_Streamx ); //发送DMA数据
}
}
Delay_ms(1); //延时,多任务下让出时间片
}
return SendSize;
}
/*
*函数名称:DMA2_Stream7_IRQHandler
*函数功能:USART1-TX DMA2 Stream7-Channel4 响应中断
*发送完成后触发该中断,并继续发送未完成的数据
*/
void DMA2_Stream7_IRQHandler(void)
{
uint8_t ID = 0; //串口序号
uint16_t BuffSize = 0;
uint16_t ReadSize = 0;
if( DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7) != RESET )
{
DMA_ClearITPendingBit(DMA2_Stream7, DMA_IT_TCIF7); //清除中断标志
BuffSize = FIFO_GetSize( &USART1_FIFO_Send ); //获得发送缓冲区大小
if( BuffSize > 0 )
{
if( BuffSize > DMA_USART_SendBuffer_Len ) BuffSize = DMA_USART_SendBuffer_Len; //如果发送大小大于DMA发送缓冲,则大小取DMA发送缓冲区大小
if( FIFO_GetBuff( &USART1_FIFO_Send, BuffSize, DMA_USART1_Send_Buff, &ReadSize) != 1 ) //如果发送环型缓冲区已经为空,则退出发送,否则继续发送
{
USART_DMA_Status[ID].DMA_SendOK = 1; //发送完成
USART_DMA_Status[ID].DMA_IsSending = 0; //重置发送标识,下次发送需要开启DMA发送
return;
}
DMA_Cmd( DMA2_Stream7, DISABLE ); //停止发送
DMA_SetCurrDataCounter( DMA2_Stream7, ReadSize ); //设置DMA发送缓冲区的大小为待发送大小
DMA_Cmd( DMA2_Stream7, ENABLE );
USART_DMA_Status[ID].DMA_SendOK = 0; //未发送完成
USART_DMA_Status[ID].DMA_IsSending = 1; //正在发送
}
else
{
USART_DMA_Status[ID].DMA_SendOK = 1; //发送完成
USART_DMA_Status[ID].DMA_IsSending = 0; //已发送完成
}
}
}
终于发完了,并整理了备注,程序可能比较复杂,该方法也稳定运行了一段时间,暂时没发现问题
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
楼主这个 FIFO_PutBuff( &USART1_FIFO_Receive, DMA_USART1_Recieve_Buff, Size ); //将收到的DMA缓冲复制进串口输入FIFO FIFO_PutBuff()函数是环型缓冲区写入Buff的用到的,USART1_FIFO_Receive 环型缓冲区结构
这个环形缓冲区不是很明白,这个FIFO_PutBuff函数以及USART1_FIFO_Receive 环型缓冲区结构您是如何定义的可否将这部分分享一下
一周热门 更多>