OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 11742|回复: 16

★★★ 关于 usart.c 中 void USART1_IRQHandler(void) 函数的bug分析及问题的解决

[复制链接]

8

主题

93

帖子

2

精华

中级会员

Rank: 3Rank: 3

积分
446
金钱
446
注册时间
2013-9-22
在线时间
0 小时
发表于 2014-1-5 21:21:08 | 显示全部楼层 |阅读模式

STM32开发指南-库函数版本V1.2.pdfP142和《原子教你玩STM32(库函数版)》P107中,都有这样一句话:“当收到回车(回车的表示由2个字节组成:0X0D0X0A)的第一个字节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;  //接收数据错误,重新开始接收   

                }  

            }

        }     

  } 

 

请注意上述内容的红色加粗部分即为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("\n");  

             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; //补上刚刚接收到的0X0DUSART_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. (注意:0x0D10进制是13)

     

 

所以,修改后的void USART1_IRQHandler(void) 是可行有效的。

 

其实,原USART1_IRQHandler出错的概率也不大,256分之一吧,但是问题的严重性在于,一旦你所发的数据中含有0x0D,那么,你所发的所有的数据都会遭殃,我们不可能保证我们发给STM32的数据(即使是字符或string,其ASCII码中也很有可能包含0x0D)绝对不含0x0D。况且,对任何程序而言,小概率从来都不是我们容忍bug的理由。所以此问题有待重视。

另外,从这个角度出发,以回车键(0X0D+0X0A)来判断串口是否接受结束也有其局限性,因为我们的数据中绝不能含有0X0D 0X0A这样的序列(尽管概率只有1/(256*256),但是严重性仍不容忽视),否则,又得遭殃了。
    只要问题发现了,解决的办法总归是有的,只是提醒大家,在做实际项目时,数据传输协议的字头字尾的定义一定要用心。

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

8

主题

93

帖子

2

精华

中级会员

Rank: 3Rank: 3

积分
446
金钱
446
注册时间
2013-9-22
在线时间
0 小时
 楼主| 发表于 2014-1-6 03:26:16 | 显示全部楼层
排版排的肝疼,要是有.doc文件导入功能就好了。
回复 支持 反对

使用道具 举报

36

主题

1263

帖子

1

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
1612
金钱
1612
注册时间
2012-6-15
在线时间
39 小时
发表于 2014-1-6 10:09:12 | 显示全部楼层
一般 0X0D 0X0A  都是用来 作 ASCII 帧 截止符号的, 而在ASCII字符串中 一般不会出现 无显示的标识符 [范围0x20~0x7f] 所以用 <0x20 或>=0x80 的字符作为传送控制字符。

对于 任意字符发送[串口助手的十六进制发送方式], 肯定不能用 0xd ,0x0a 作为停止符号
会有更合理的规定, 比如连续的0xff, 0xfe  或以空闲时间长度作为停止标志【比如Modbus】
回复 支持 反对

使用道具 举报

65

主题

440

帖子

0

精华

高级会员

Rank: 4

积分
782
金钱
782
注册时间
2012-8-29
在线时间
17 小时
发表于 2014-1-6 11:09:49 | 显示全部楼层
回复【2楼】shr5791:
---------------------------------
串口传输数据0d 0a 通常都是组合使用。回车换行 。键盘上的回车按键 也是0d 0a
人生永远追逐着幻光,但谁把幻光看作幻光,谁便沉入无边的苦海
回复 支持 反对

使用道具 举报

8

主题

93

帖子

2

精华

中级会员

Rank: 3Rank: 3

积分
446
金钱
446
注册时间
2013-9-22
在线时间
0 小时
 楼主| 发表于 2014-1-6 11:17:18 | 显示全部楼层
回复【3楼】aleda303:
---------------------------------
是的,你说的对。但是你想,从USART1的中断服务程序本身的代码来看,只要用户数据中含有0D那么最终用户数据就是无效的。所以在串口接收数据时用户还是要适当修改。对于ISR来说,它不关心上位机给它发送的是什么char或string,它只认识ASCII:0xXX、0xYY、0xZZ。。。。。
回复 支持 反对

使用道具 举报

8

主题

93

帖子

2

精华

中级会员

Rank: 3Rank: 3

积分
446
金钱
446
注册时间
2013-9-22
在线时间
0 小时
 楼主| 发表于 2014-1-6 11:20:04 | 显示全部楼层
回复【4楼】sun_shine:
---------------------------------
这个我知道,这个不是我讲的问题所在。我想讲的是,对于接收到0D之后,如果接下来接收的不是0A,那么0D和刚刚收到的这个数都应该放到串口接收BUF中,而不应该丢弃然后重新开始接收。啊理解?
回复 支持 反对

使用道具 举报

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-1-6 11:29:19 | 显示全部楼层
回复【2楼】shr5791:
---------------------------------
马上就会升级了。。。升级了就有了
我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
回复 支持 反对

使用道具 举报

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-1-6 11:29:37 | 显示全部楼层
谢谢楼主分析
我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
回复 支持 反对

使用道具 举报

65

主题

440

帖子

0

精华

高级会员

Rank: 4

积分
782
金钱
782
注册时间
2012-8-29
在线时间
17 小时
发表于 2014-1-6 14:53:06 | 显示全部楼层
回复【6楼】shr5791:
---------------------------------
理解,你分析得很对,只是我认为没必要改。如果你控制好你发送的数据,是不会出现这个bug的。一般的串口通信发送数据很少会出现只回车不换行或者 只换行不回车。
人生永远追逐着幻光,但谁把幻光看作幻光,谁便沉入无边的苦海
回复 支持 反对

使用道具 举报

8

主题

93

帖子

2

精华

中级会员

Rank: 3Rank: 3

积分
446
金钱
446
注册时间
2013-9-22
在线时间
0 小时
 楼主| 发表于 2014-1-6 14:59:30 | 显示全部楼层
回复【9楼】sun_shine:
---------------------------------
你还是没有理解我的意思,呵呵。跟换行回车什么的没有关系,就是一个简单的数据:0X0D,就可以引起所有数据遭殃。不要再去想和回车换行了。
回复 支持 反对

使用道具 举报

65

主题

440

帖子

0

精华

高级会员

Rank: 4

积分
782
金钱
782
注册时间
2012-8-29
在线时间
17 小时
发表于 2014-1-9 11:42:12 | 显示全部楼层
回复【10楼】shr5791:
---------------------------------
第一、你引用的那句红字 也就是协议,大家约定好的。你发了0x0D也就是说明 你要结束这次传输,你不发0a说明这次传输有问题,od之前的数据有问题。BUG修改之后,发的数据都是有效数据,即便是遇到无效数据也会被接收,接收是不会失败的,但是按照协议说的,0d之前的都是无效数据。不是我没理解你,是你没理解协议。 
第二、涉及到实际应用。通过串口调试助手发数据,你在键盘上输入ASCII码的数据是不会出现只有0d没有0a的情况的。0x0d是十六进制的数据。串口与传感器通信时也很少有  只有0D没有0a的情况,至少我从来没遇到过。
人生永远追逐着幻光,但谁把幻光看作幻光,谁便沉入无边的苦海
回复 支持 反对

使用道具 举报

30

主题

87

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
427
金钱
427
注册时间
2015-3-11
在线时间
123 小时
发表于 2016-4-8 19:17:48 | 显示全部楼层
这还是有点问题 比如说我发的数据的最后一位是0d
比如说这样:aa 01 06 00 1C 0d 01 0d 0d 0a
判断了0d后面不是0a就把刚才那位还原0d然后继续往后加
然而当0d过后还是0d的时候好像就有问题了
实验的时候发现这个读不完整了
回复 支持 反对

使用道具 举报

11

主题

32

帖子

0

精华

初级会员

Rank: 2

积分
102
金钱
102
注册时间
2016-4-3
在线时间
17 小时
发表于 2016-4-14 11:10:21 | 显示全部楼层
其实你分析得还是有道理的
回复 支持 反对

使用道具 举报

1

主题

25

帖子

0

精华

初级会员

Rank: 2

积分
172
金钱
172
注册时间
2016-5-9
在线时间
22 小时
发表于 2016-6-7 13:37:32 | 显示全部楼层
各位分析的都有道理,不过我没看懂
回复 支持 反对

使用道具 举报

1

主题

25

帖子

0

精华

初级会员

Rank: 2

积分
172
金钱
172
注册时间
2016-5-9
在线时间
22 小时
发表于 2016-6-7 13:41:01 | 显示全部楼层
觉得你说的很对
回复 支持 反对

使用道具 举报

2

主题

43

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
299
金钱
299
注册时间
2016-6-19
在线时间
195 小时
发表于 2016-12-20 17:10:01 | 显示全部楼层
看了好久才明白是楼主发HEX(十六进制)格式的数据时用这个判断发ascll字符格式是否出错的程序时遇到BUG了,
回复 支持 反对

使用道具 举报

1

主题

15

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
456
金钱
456
注册时间
2018-10-12
在线时间
62 小时
发表于 2020-2-20 15:16:09 | 显示全部楼层
本帖最后由 Lukesias 于 2020-2-20 15:23 编辑

今天抄原子哥的程序,感觉这里有点问题,随后发现并改正如下
                if(Res != 0x0a)
                {
//                    USART_RX_STA = 0;   //接收错误,重新开始
                    USART_RX_STA &= ~0x4000;

                    USART_RX_BUF[USART_RX_STA] = 0x0d;
                    USART_RX_STA++;

                    if(Res == 0x0d) //0d 0d 0d 连续的情况
                    {
                        USART_RX_STA |= 0x4000;              
                    }
                    else
                    {
                        USART_RX_BUF[USART_RX_STA] = Res;
                        USART_RX_STA++;                        
                    }   
                }  
关于数据帧中包含0x0D,出现的问题就没有了。
本来想发帖炫耀哩,没想到都是大神N年前玩过剩的,,,我要加油
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-20 17:55

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表