您的位置 首页 IOT

搞定单片机多字节串口接纳

工作了一年多,写了不少单片机串口程序。感觉串口多字节接收部分的逻辑相对于配置寄存器跟串口来说,是有点难度的——寄存器配置基…

    工作了一年多,写了不少单片机串口程序。感觉串口多字节接纳部分的逻辑相关于装备存放器跟串口来说,是有点难度的——存放器装备基本上都是死的,串口多字节跟一字节仅仅多了一个循环。

串口接纳程序是根据串口中止的,单片机的串口每次接纳到一字节数据发生一次中止,然后再读取某个存放器就能够得到串口接纳的数据了。然而在实践使用傍边,基本上不会有单字节接纳的状况。一般都是根据必定串口通讯协议的多字节通讯。在422或许485通讯中,还或许是一个主机(一般是核算机)带多个从机(相应的有单片机的板卡)。这就要求咱们的单片机能够在接连接纳到的串口数据序列中识别出契合自己板卡对应的通讯协议,来进行操控操作,不契合则不进行任何操作。简而言之便是,单片机要在一串数据中找到契合必定规则的几个字节的数据。

先来说下怎样定串口协议吧。这个协议指的不是串口底层的协议,而是前面说到的数据帧协议。一般都是有帧头(2~3个字节吧),数据(长度根据需要),结束位(1位,有时分规划成校验字节,最简略的校验也便是前面一切数据求和)。

比方0xaa 0x55 +(数据部分省掉)+校验和(除了aa 55 之外数据的和),假如要是多板卡的话有时分还要在帧头后边加一个板选字节(相当于3字节帧头了)。

榜首次写串口接纳程序的时分,我首要想到的便是界说一个大局变量(实践上最好是界说部分静态变量),初始值设置为0,然后每进一次中止+1,然后加到串口通讯协议的长度的时分再清零。然后判别帧头、校验。写完了之后我自己都觉得不对,一旦数据错开了一位,后边就永久都接纳不到数了。无法看了一下长辈们的代码,跟我的思路差不多,只不过那个计数值跟接纳到的数据时一起判别的,并且每次中止都要判别,一旦不对计数的那个变量就清零。

废话少说,直接上一段代码让咱们看看就理解了。(通讯协议权且依照简略的aa 55 一个字节数据 一个字节校验,代码是根据51单片机的)。接纳成功则在中止程序中把串口接纳成功标志方位1。

下面是大局变量界说

1 unsigned char receive[4]={0,0,0,0};//接纳缓存2 3 bit uart_flag;//串口接纳成功标志

然后串口中止部分

void ser()interrupt 4{  static unsigned char count;//串口接纳计数的变量RI=0;//手动清某个存放器,咱们都懂的receive[count]=SBUF;if(count==0&&receive[count]==0xaa)//一起判别count跟收到的数据{count=1;}else if(count==1&&receive[count]==0x55){count=2;}else if(count==2){count++;}else if(count==3&&receive[count]== receive [2])//判别校验和,数据多的话是求//和,或许其他的校验办法,也或许是固定的帧尾{count=0;uart_flag =1;//串口接纳成功标志,为1时在主程序中,然后清零ES=0;      //关中止,完了再ES=1;}else{count=0;//判别不满足条件就将计数值清零}}

榜首次做的串口大约就依照这个办法写完了(我后来看过其他的代码,有人用switch查办写的,逻辑跟这个也差不多,不过我仍是感觉用if else来写明晰一些),

不过在测验的时分发现了bug,假如数据帧发送一半,然后忽然中止,再来重新发,就会丢掉一帧的数据。比方先接遭到aa 55,然后断了,再进来aa 55 01 01,就不受操控了。后来我也想到一个bug,假如在多设备通讯中,归于其他设备的的帧数据最终一位是aa(或许最终两位为aa 55 ,或许最终3位为aa 55 板选),下一次通讯的数据就接纳不到了。

其时关于数据忽然中止的bug,没有想到很好的处理办法,不过这种状况几率极小,所以一向用这个办法写也没有问题。多设备通讯最终一位刚好是aa的几率也很小,出问题的或许也很小。其时项目晒干的操控数据跟校验刚好不或许呈现aa,所以我把if(count==0&&receive[count]==0xaa)改成了if(receive[count]==0xaa)其他都没变,处理了,没有bug了。

后来我又写了几回单片机程序,才想到了一些处理问题的办法——不过改天再接着写吧,太累了,明日还要上班呢。

在后来的项目中,真的遇到了数据位跟校验位都或许呈现aa的状况。我考虑到每次数据都是接连发送的(至少咱们用labwindows做的上位机程序是这样的),成功接纳到了一帧数据是要有必守时刻的,也便是说假如接纳到一半,但是很长时刻没接纳到数据,把计数值count清零就ok啦。触及时刻的问题天然要用守时器来完成啦。

这次的通讯协议如下,串口波特率19200,2个帧头aa 55 ,一个板选,6字节数据,一个校验字节(除帧头外其他数据的和)。

大局变量界说

unsigned char boardAddr;//板选地址,通过检测几个io引脚,详细怎样得到的就不写了,很简略的unsigned char g_DatRev [10]={0};//接纳缓存bit retFlag=0;//为1代表串口接纳到了一帧数据

串口初始化函数,晶振22.1184

void init_uart(){SCON = 0x50;                 //串口办法1答应接纳TMOD = 0x21;                //守时器1,办法2,8位主动重载,一起装备守时器0,工作办法1PCON = 0x80;                // 波特率加倍TH1 = 0xfa;TL1 = 0xfa;               //写入串口守时器初值TH0=(65536-2)/256;    //写入守时器0初值,串口传输一个字节时刻为(1/19200)*10,核算得0.52msTL0=(65536-2)%256;   //守时器0守时大约1ms多EA=1;ET0=1;                  //波特率:19200    22.1184M  初值:250(0xfa)IE = 0x90;           TR1 = 1;                   }

串口中止函数

void UART_INT(void) interrupt 4{ static unsigned char count;//串口接纳计数的变量RI = 0;g_DatRev[count] = SBUF;if(g_DatRev[count]==0xaa&&count==0)             //帧头{count=1;                                                 }else if(count==1&&g_DatRev[count]==0x55) {  count=2;          }else if (count==2&&g_DatRev[2] == boardAddr){ CK = g_DatRev[count];count=3;        }else if(count>=3&&count<9){                   CK += g_DatRev[count];count ++;}else if(count == 9&&CK==g_DatRev[9]){     ES = 0; retFlag = 1;count=0;            }            else{count=0;} resettimer();}
//判别count不为0的话就发动守时器void resettimer(){TR0=0;TH0=(65536-2)/256;TL0=(65536-2)%256;if(count!=0){TR0=1;}}守时器中止函数void T0_time()interrupt 1{     TR0=0;TH0=(65536-2)/256;TL0=(65536-2)%256;count=0;}

这种办法确实是自己自己想出来的,他人或许也这样做过,但我这个必定不是抄袭或许仿照来的。这样写确实能够防止前面说到过的bug,不过价值是多用了一个守时器的资源,并且中止函数里的内容更多了,占用了更多的时刻。

要是能把榜首种办法改善一下就好了,主要是那个校验不能为aa的那个bug,由于究竟传输到一半忽然断了的或许性是十分小的。后来我想榜首个判别if(count==0&&receive[count]==0xaa)如同有点太严厉了,考虑到第二字节的帧头,跟板选地址不或许为aa,所以把这个改写为if(count>=0&&count<=2&& receive[count]==0xaa),这样就把bug呈现的几率降到了十分小,也仅仅在前一帧结束数据刚好为 aa 55 板选 的时分才呈现,几率是多少咱们自己算一下吧,。这样我自己觉得,昨日写的那种办法改善到这个程度,应该算能够啦,横竖我是很满足了。

实践上我还想过其他的办法,比方缓存的数组选用移位存放的办法。拿前面的4个字节的协议为例。

void ser()interrupt 4

{

unsigned char i;

RI=0;

for(i=0;i<3;i++)

{

receive[i]=receive[i+1];

}

receive[3]=SBUF;

if(reveive[0]==0xaa&&receive[1]==0x55&&receive[2]==receive[3])

{

ret_flag=1;

ES = 0;

}

}

这段代码看上去但是简略明了,这样判别但是不错啊,一起判别帧头跟校验不会发生前面说到的bug。说实话其时我刚想出这种办法并写出来的时分,立刻就被我给否了。那个for循环可真是很占时刻的啊,延时函数都是这样写的。每次都循环一下,这延时太长,通讯速度太快的话就不能接纳到下一字节数据了。最要命的是这个时刻的长度是跟着通讯协议帧的字节数添加而添加的,假如一非必须接纳几十个字节,必定就玩完了。这种办法我一次都没用过。

不过我竟然又想出来了这种办法的改进办法,是前两天刚想出来的,,还没有实践过呢。

下面代码的协议就按第二段程序(守时器清零的那个协议,总共10字节)

大局变量

bit ret_flag;

unsigned char receive[256]={0};

unsigned char boardaddress;

中止函数

void ser()interrupt 4

{

static unsigned char i=0;

static unsigned char total=0;

RI=0;

receive[i]=SBUF;

total=total-receive[i-7]+receive[i-1];

if(receive[i-9]==0xaa&&receive[i-8]==0x55&& receive[i-7]==boardaddress&&receive[i]==total)

{

ret_flag=1;

ES = 0;

}

i++;

}

之所以要界说256个长度的数组,便是为了能够让数组“首尾相接”。由于0 -1 = 255 , 255+1 = 0。并且我在核算校验的时分也改善了算法,不会由于数据长度的添加而添加核算校验值的时刻。这种办法也是我不久前才想出来的,所以还没有通过实践的验证。上面的代码或许会有逻辑上的过错,假如真有过错,有网友看出来的话,请在下面留言告诉我。这个办法也是我原创的哦,他人也肯能会想到,不过我这个必定不是抄袭他人的。

上面的代码最大的缺陷便是变量界说的太多了,太占ram资源了,编译的时分或许会呈现过错,究竟51单片机才128字节的ram(有的资源也很丰厚的,比方c8051系列的),这一下子便是256字节的变量。不过关于资源多一些的单片机,这样写仍是能够的。要是能有4bit在一起的数据类型就好了,,verilog代码晒干是能够的,C言语里圆满不可啊。

要想能在例如51单片机上运转,只能依照下面的折中办法了,也便是把i相关的量都与一个0x0f

大局变量

bit ret_flag;

unsigned char receive[16]={0};// 能够考虑在界说时加上idata,究竟还或许是32

//或许64长度的数组呢unsigned char idata receive[16]={0};

unsigned char boardaddress;

中止函数

void ser()interrupt 4

{

static unsigned char i=0;

static unsigned char total=0;

RI=0;

receive[i&0x0f]=SBUF;

total=total-receive[(i-7)&0x0f]+receive[(i-1)&0x0f];

if(receive[(i-9)&0x0f]==0xaa&&receive[(i-8)&0x0f]==0x55

&&receive[(i-7)&0x0f]==boardaddress&&receive[i&0x0f]==total

)

{

ret_flag=1;

ES = 0;

}

i++;

}

这样就能够了。等我有机会试一下吧,。我写了这么多,想必咱们都能搞定串口接纳了吧。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/yingyong/iot/265309.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部