《STM32开发指南-库函数版本V1.2.pdf》P142和《原子教你玩STM32(库函数版)》P107中,都有这样一句话:“当收到回车(回车的表示由2个字节组成:0X0D和0X0A)的第一个字节0X0D时,计数器将不再增加,等待0X0A的到来,而如果0X0A没有来到,则认为这次接收失败,重新开始下一次接收”。
usart.c中,void USART1_IRQHandler(void) 的实现如下:
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0) //接收未完成
{
if(USART_RX_STA&0x4000) //接收到了0x0d
{
if(Res!=0x0a) USART_RX_STA=0; //接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d) USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1)) USART_RX_STA=0; //接收数据错误,重新开始接收
}
}
}
}
}
请注意上述内容的红 {MOD}加粗部分即为bug。因为当收到0X0D后,如果下一个不是0X0A,也不应该“USART_RX_STA=0;//接收错误, 重新开始”,一旦你所发的数据中含有0x0D,那么,你所发的所有的数据都会遭殃。
按照上述内容易知,如果我们发给STM32的数据中含有0X0D(这里的0X0D不是回车键的0X0D 0X0A中的0X0D),那么从0X0D后面从第二个数开始,将会依次被放入USART_RX_BUF[0]、USART_RX_BUF[1]、USART_RX_BUF[2]、……
比如:通过串口给STM32发送数据 01 02 0D 04 05 06 (以16进制发送,且选择发送新行),那么在接收到0D后,再接收到04时,因为0x04!=0x0A,所以执行“USART_RX_STA=0;//接收错误,重新开始”,也就是说,0x05会被放入USART_RX_BUF[0]中,0x06会被放入USART_RX_BUF[1]中。
现在我们来测试usart.c中,void USART1_IRQHandler(void) 到底是不是这样。我用原子提供的Template工程模板,写了这样的main()函数:
int main(void)
{
u8 i;
delay_init();
NVIC_Configuration();
uart_init(9600);
for(i=0;i<6;i++) USART_RX_BUF=0; //首先清零USART_RX_BUF[0~5],我们就用前6个元素来测试
while(1)
{
if(USART_RX_STA&0x8000) //USART1接收完成
{
/* 输出刚刚收到的数据USART_RX_BUF[0~5]并换行 */
for(i=0;i<6;i++) printf("%d ",USART_RX_BUF); printf("
");
USART_RX_STA=0; //清状态标志
for(i=0;i<6;i++) USART_RX_BUF=0; //清USART_RX_BUF[0~5]
}
}
}
结果如下图所示。当所发数据中不含0X0D时,一切正常:发 01 02 03 04 05 06 ,收到的也是01 02 03 04 05 06.
而当所发数据中含有0X0D时(我把上面的03改成0D),就糟糕了:发 01 02 0D 04 05 06 ,我们收到的却是05 06 0 0 0.
原因,我上面已经分析了。
可见,“当收到第一个字节0X0D时,计数器将不再增加,等待0X0A的到来,而如果0X0A没有来到,则认为这次接收失败,重新开始下一次接收”是不正确的。
围绕带下划线的这句,我对void USART1_IRQHandler(void) 作了如下修改(黑体加粗部分):
void USART1_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0) //接收未完成
{
if(USART_RX_STA&0x4000) //接收到了0x0d
{
//if(Res!=0x0a) USART_RX_STA=0; //接收错误,重新开始
/*
如果当前接收到的数据不是0x0a,说明刚刚接收到的数据0x0D不是回车的0X0D 0X0A中的0X0D
只是用户所发的普通数据0x0D而已
*/
if(Res!=0x0a)
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=0X0D; //补上刚刚接收到的0X0D到USART_RX_BUF[]中
USART_RX_STA++;
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res; //将当前接收到的数据放入USART_RX_BUF[]中
USART_RX_STA++;
USART_RX_STA &= 0xBFFF; //清除刚刚0x0d的接收标志 (0xBFFF = 1011 1111 1111 1111)
}
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
……………………
}
}
}
}
main()不变,我们再来试试修改后的void USART1_IRQHandler(void) 正确与否,如下图所示:
当所发数据中不含0X0D时,一切正常:发 01 02 03 04 05 06 ,收到的也是01 02 03 04 05 06.
同样我把上面的03改成0D:发 01 02 0D 04 05 06 ,我们收到的是01 02 13 04 05 06. (注意:0x0D的10进制是13)
所以,修改后的void USART1_IRQHandler(void) 是可行有效的。
其实,原USART1_IRQHandler出错的概率也不大,256分之一吧,但是问题的严重性在于,一旦你所发的数据中含有0x0D,那么,你所发的所有的数据都会遭殃,我们不可能保证我们发给STM32的数据(即使是字符或string,其ASCII码中也很有可能包含0x0D)绝对不含0x0D。况且,对任何程序而言,小概率从来都不是我们容忍bug的理由。所以此问题有待重视。
另外,从这个角度出发,以回车键(0X0D+0X0A)来判断串口是否接受结束也有其局限性,因为我们的数据中绝不能含有0X0D 0X0A这样的序列(尽管概率只有1/(256*256),但是严重性仍不容忽视),否则,又得遭殃了。
只要问题发现了,解决的办法总归是有的,只是提醒大家,在做实际项目时,数据传输协议的字头字尾的定义一定要用心。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
对于 任意字符发送[串口助手的十六进制发送方式], 肯定不能用 0xd ,0x0a 作为停止符号
会有更合理的规定, 比如连续的0xff, 0xfe 或以空闲时间长度作为停止标志【比如Modbus】
一周热门 更多>