打印

一个比较复杂的串口校验收发的例子 个人心得

[复制链接]
6748|16
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
gdmgb520|  楼主 | 2009-8-31 16:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 gdmgb520 于 2011-5-17 16:27 编辑

串口是很重要也很基础的东西,以前做实验的时候也做过,但是现在看来,那时候根本就没有彻底弄懂。这几天做一个与串口有关的东西,仔细做过了之后才算把他弄明白。现在跟大家分享一下我的心得。
我做的是PC与单片机通信的,PC 发送的数据格式是  包头(4B)+通道号(1B)+状态(1B)+时间(系统6B、任务6B)+包尾(1B)(共19字节):ff ff ff ff 01 0f 09 09 09 23 59 59 08 08 08 08 08 08 fb 。串口工作状态是:9600波特率,校验位:None,数据位:8,停止位:1。
要求是:先要进行包头的校验,然后进行通道号的校验(因为PC串口接了好几个设备),然后才开始接收数据。

首先,我弄明白的是串口的初始化,虽然这个很简单,但是一开始我就是没弄对。而且我当时看了网上的一些例子,照着那些例子写的(看例子之前我还看过教材),结果数据就是有乱码,后面晚上又把教材仔仔细细看了一遍才弄对。我用的是串口工作方式1,板子的晶振是11.0592,所以串口的初始化应该如下:

/*----------串口初始化-----------*/
void Init_Com(void)
{
  SCON = 0x50;    //串口工作在方式1;允许接收
     PCON = 0x00;    // 波特率不*2
     TMOD = 0x20;    //定时器1工作在方式2,自动重装
     TH1  = 0xFD;       // 波特率为9600
     TR1  = 1;          // 定时器1启动计数
     ES   = 1;          // 串口开中断     
     PS   = 1;          // 串口高优先级   
     EA   = 1;             // 开CPU中断  
}

由于我这里有特殊要求,所以串口设了高优先级PS=1。有时候你会看到有的初始化中没有EA和ES,而是EI=0x09,实际上这是一回事,EA和ES就是EI的第七位和第四位。还有的人给TL1也装入了0XFD(TL1=0XFD),这实际上是没有必要的,因为TMOD = 0x20; 定时器1工作在方式2,自动重装,即第八位做计数器并计数满以后自动把高八位的数据装入第八位。此时的PCON一定是等于0x00的,不然波特率就变成了19200,网上有的例子就是错的。
然后,串口的收发其实还是很简单的。
串口接收,实际上就是等待串口接收中断。每当MCU的串口接收到一个字节数据后就会触发串口接收中断,所以我们只要在中断程序中把串口接收寄存器中的数据读入变量即可(ch=SBUF;)。而串口的发送其实就是把变量中的数据送入串口发送寄存器。(这两个寄存器物理上是不一样的,但共用一个地址,我们在操作中完全可以把他们当成是一个寄存器。)下面是串口接收一个字符和发送一个字符的子函数:

void Serial_Com() interrupt 4  //串口接收中断
{
if (RI)
{
  RI=0;                //串口接收中断标志必须是有软件清零的,且应该在中断内部清零
  cmd=SBUF;
  read_flag=1;
}
}

发送一个字符子函数:

void Send_Char_Com(void)
{
SBUF=cmd;
while(TI==0);    //gb  在串口接收中断里如果不加if(RI){}语句,那么这里的while(TI);语句就必须有,否则PC串口会一直接收到信息。这也说明:在单纯的串口接收程序中必须有if(RI){}语句
TI=0;      //gb  发生程序中的while(TI)和接收程序中的if(RI)两者都没有时串口收发不正常,一直接收到数据;两者有一个或都存在时串口工作正常。
}

说明一下:这两个子程序中的if(RI==0)和while(TI==0)为什么要加我也不清楚,但事实证明要加,还望老鸟指点!!!!!
然后在主函数中做一个循环,一直调用就可以了。这样就实现了最基本的串口收发(一个字节)。

/*---------------------------2011.05.17---------------------------------------*/
今天重新读自己的帖子,补充下现在对RI和TI的理解。方便新人,呵呵,也保证完整性。嘿嘿
RI和TI是接收和发送中断标志,即表示接收完一个字节或发送完一个字节,该标志应该是需要软件清零(硬件不自动清零)。所以,如果进入中断后软件不清零该标志,那么单片机会再次进入中断。(大概应该是这样,没有看51的书籍,应该参考书籍。)
一句话,就是这两个标志需要软件清零。

先休息一下,保存一下。

先保存一下!

测试7-COM基本通信.rar

16.98 KB

KeilC

相关帖子

沙发
gdmgb520|  楼主 | 2009-8-31 16:37 | 只看该作者
本帖最后由 gdmgb520 于 2011-5-17 16:19 编辑

实现单字节的收发后我就做了多字节的收发
首先是多字节的接收,实际上还是字节的接收,只不过是把接收到的数据存入一个数组中。因为MCU每收到一个字节就会产生一个接收中断,所以只要在每一次中断中把接收寄存器的内容读入到新的变量中即可。程序如下:

void Serial_Com() interrupt 4                //串口中断中接收字符串
{
        if (RI)
        {
                RI=0;
                cmd[i]=SBUF;
                i++;
                if (i==14)  //一共是14个字节,全部接收到后才算一次接收完成
                {
                        read_flag=1;
                        i=0;
                }
        }
}

接着是多字节的发送,前面我们有了单字节的发送,那么现在多字节的发送实际上就是多次调用单字节的发送子函数,程序如下:

void Send_Char_Com(uchar ch)    //串口发送字符函数
{
        SBUF=ch;
        while(TI==0);
     TI=0;
}

void Send_String_Com()                        //串口发送字符串函数
{
        uint j;
        for (j=0;j<14;j++)
        {
                Send_Char_Com(cmd[j]);
        }
}

然后主函数中把单字节的发送函数改成多字节的发送函数就可以了:

void main()
{
        Init_Com();
        while(1)
        {
                if (read_flag)
                {
                        //Send_Char_Com();
                        Send_String_Com();
                        read_flag=0;
                }
        }
}

这样就实现了多字节的收发。

测试8-COM字符串收发.rar

18.58 KB

KeilC

使用特权

评论回复
板凳
gdmgb520|  楼主 | 2009-8-31 17:18 | 只看该作者
本帖最后由 gdmgb520 于 2011-5-17 16:18 编辑

由于最终的要求是要进行包头校验和通道号判断,所以接下来我接加入了包头校验和通道号判断。
首先是包头校验,这个工作主要是在中断子程序中做的。请看下面的程序:

void Serial_Com() interrupt 4
{
        if (RI)
        {
                if (n_temp==4)                //包头检验完成,可以接收数据        包头必须是连续的4组FF
                {
                        RI=0;
                        cmd[i]=SBUF;
                        i++;
                        if (i==14)                 //本包数据接收完成
                        {
                                read_flag=1;  //数据接收完成标志
                                i=0;
                                n_temp=0;          //包头校验完成标志清零,
                        }
                 }
                 else                               
                 {
                         RI=0;
                         temp=SBUF;
                        if (temp==0xff)           //包头校验
                        {
                                n_temp++;
                        }
                        else                           //在进行包头判断,该过程中发现一个不是FF那前面的**都失效
                        {        n_temp=0;        }  //因为包头是连续的四字节FF,所以只要中间有一个不是FF则前面的**清零

                 }
        }
}

来分析一下我的程序,n_temp的作用是记录已经连续收到几个FF,当串口中断产生时首先判断是否已经收到4个连续的FF:
a).如果不是就判断这个数是不是FF,若是n_temp++,否则n_temp清零.这里问什么要清零,因为包头是4个连续的FF,如果中间有一个不是FF那么这就不是包头,那前面记录的就应该清零。
b).如果是,就说明包头校验通过,那就把后面的数据读到数组中去。(这里发送的数据还不包括通道号,即包头+状态+时间+包尾(共18字节))。
在完成了这一步调试通过以后,我就开始插入通道号判断。这个地方我自己觉得我做得很巧妙,呵呵(实际上可能是做得很笨,哈哈)。先看程序吧:

/*---------------串口接收中断-------------*/
void Serial_Com() interrupt 4
{
        if (RI)
        {
                if (n_temp==4)                //包头检验完成,可以接收数据
                {
                        switch(p)                                           //关于变量p的解释,在包头校验时p始终=0,当包头校验通过后再进入if语句时就会用到p,而此时p肯定是=0的所以就会执行case 0,当执行饿了case 0后p只能为1或2,当接收下一个数据时就不会进入case 0了,而是会根据p的结果执行接收或执行不接收操作
                        {
                                case 0 :
                                                {
                                                        temp=SBUF;
                                                        if (temp==channel_no)
                                                        {        p=1;        }                  //通道号与本机吻合,允许接收数据
                                                        else
                                                        {        p=2;                          //通道号与本机不吻合,不允许接收数据
                                                            n_temp=0;                  //本包不接收
                                                    }                  
                                                } break;
                                case 1 :
                                                {
                                                        cmd[i]=SBUF;
                                                        i++;
                                                        if (i==14)                 //本包数据接收完成
                                                        {
                                                                read_flag=1;  //数据接收完成标志
                                                                i=0;
                                                                n_temp=0;          //包头校验完成标志清零,
                                                        }                                                               
                                                } break;
                                case 2: break;
                                default: break;
                        }
                        RI=0;
                 }
                 else                               
                 {
                         RI=0;
                         temp=SBUF;
                        if (temp==0xff)           //包头校验
                        {
                                n_temp++;
                                p=0;                  
                        }
                        else
                        {        n_temp=0;        }        //因为包头是连续的四字节FF,所以只要中间有一个不是FF则前面的**清零

                 }
        }
}

还是在中断里做的,感觉注释写得很详细的,就不多说了。
我感觉整个程序的好处在于,是先判断了包头,包头正确了才开始接收后面的数据;再判断通道号,如果通道号与本机不符,数据接收到此为止,后面的数据都不用接收了。
数据接收进来后还有一个包尾(校验),只有包尾是正确的才认为本次数据是有效的。由于包尾是在整个数据的尾部,要读包尾必须先把他前面的数据接收进来,所以包尾也干脆放到了数组中,这样我加不加包尾校验程序都不用做大的改动。

我不知不觉写了这么多,估计大家都看累了,呵呵。我也是刚入门,所以自己做了以后难免有些感想和体会,呵呵,跟大家分享分享,再者,请大家帮我看看我整个过程中的纰漏和思维的盲点。
还有一个地方是我觉得这次做得比较好的,那就是我一个工程一个工程地些,后面的工程在前面的工程的基础上扩展。前面简单的工程一般程序都不会出现什么错误,而且一些基础的问题(例如硬件的驱动,串口的初始化等等)也比较容易找出来,毕竟就那么几行代码;而后面的工程由于是在前面的工程上扩展的(也就是新写入了几行),如果出现错误也就是刚写的那几行的问题。所以,这样代码通过的效率非常高,而且万一哪里出问题了用不着整个重来,充其量就是这个工程作废,但前面做的工程还是没有变的。
还有一点收获:如果有时候出现类似于 error C100: unprintable character 0xA2 skipped  的错误却找不到问题在哪里,那我告诉你解决办法:把代码复制到记事本里,在出现错误的地方附近你就会发现异常的字符,呵呵,删掉就可以了。

啊,写了这么多,我自己都不敢相信啊!!以后一定要常来21

使用特权

评论回复
地板
gdmgb520|  楼主 | 2009-8-31 17:19 | 只看该作者
版主啊,为什么有的会变成斜体啊,这么解决啊???

使用特权

评论回复
5
wml409520| | 2009-8-31 19:23 | 只看该作者
呵呵,刚刚学习串口不久,楼主好好写哈,我好好学习下,喜欢这样的**。

使用特权

评论回复
6
gdmgb520|  楼主 | 2009-9-1 01:10 | 只看该作者
谢谢支持啊!

使用特权

评论回复
7
shinerj| | 2009-9-1 08:52 | 只看该作者
:)谢谢楼主,写得不错可以参考,不过这个是用什么单片机写的啊???

使用特权

评论回复
8
冷漠| | 2009-9-1 09:14 | 只看该作者
呵呵,如今这个通信芯片高速发展的时代,谁还用串口按字节来发送数据包?硬件早就实现了:封装、成帧打包、CRC校验等等。最差的就是CAN,至少也是8字节一帧,或者64字节一帧。8344-51增强型通信单片机,128字节一帧(包)都是20年前的技术了。一条汇编语句就完成了LZ那么大块的程序功能。

LZ用30年前的技术练手?精神可嘉。只是时代不同了。

使用特权

评论回复
9
icecut| | 2009-9-1 09:41 | 只看该作者
考虑一个问题:
假如其中一个包丢一个字节.那么,下一个包能成功接收吗?

粗略看了一下,觉得你的算法可能有这个问题

使用特权

评论回复
10
古道热肠| | 2009-9-1 11:20 | 只看该作者
变量名起得过于简单,不过楼主的积极学习和乐于分享的精神值得表扬的。

使用特权

评论回复
11
wml409520| | 2009-9-1 13:31 | 只看该作者
呵呵,如今这个通信芯片高速发展的时代,谁还用串口按字节来发送数据包?硬件早就实现了:封装、成帧打包、CRC校验等等。最差的就是CAN,至少也是8字节一帧,或者64字节一帧。8344-51增强型通信单片机,128字节一帧 ...
冷漠 发表于 2009-9-1 09:14

文如其名~

使用特权

评论回复
12
gdmgb520|  楼主 | 2009-9-2 11:10 | 只看该作者
7# shinerj

AT89S52

使用特权

评论回复
13
gdmgb520|  楼主 | 2009-9-2 11:16 | 只看该作者
呵呵,如今这个通信芯片高速发展的时代,谁还用串口按字节来发送数据包?硬件早就实现了:封装、成帧打包、CRC校验等等。最差的就是CAN,至少也是8字节一帧,或者64字节一帧。8344-51增强型通信单片机,128字节一帧 ...
冷漠 发表于 2009-9-1 09:14



呵呵,谢谢赐教!
其实我现在做的这个事公司里的一个项目,就用的AT89S52,呵呵,串口,我写下位机软件。呵呵,这东西比较简单,不需要更高级的技术,不过新技术还是要学的。学习!

使用特权

评论回复
14
gdmgb520|  楼主 | 2009-9-2 11:34 | 只看该作者
考虑一个问题:
假如其中一个包丢一个字节.那么,下一个包能成功接收吗?

粗略看了一下,觉得你的算法可能有这个问题
icecut 发表于 2009-9-1 09:41



是不是我前面忘了说了有包尾校验啊。
我的包尾校验是放在主程序里的,在串口接受中断里先校验了包头和通道号,然后把包尾一个字节先当做数据的一部分接收进来,存入变量,再在主程序里判断包尾校验是否通过。
如果丢了一个字节,那么包尾校验就通不过(包尾=最后一个字节数据+1)。如果包尾校验没通过,就认为本次接收失败,不进行任何处理。当然,他会把下一个包的包头第一个字节吃进来,但是不会有影响。因为上位机接了四个下位机,轮流发送属于每一个通道的数据。在本包接收失败后,程序就一直数包头,然后判断通道号。在这期间应该有三个数据包不属于本机,所以这三个包可以给它作调整。
你们帮我分析分析上述理解是否正确!

使用特权

评论回复
15
gdmgb520|  楼主 | 2009-9-2 11:39 | 只看该作者
变量名起得过于简单,不过楼主的积极学习和乐于分享的精神值得表扬的。
古道热肠 发表于 2009-9-1 11:20


能不能说具体点啊,我自己还以为变量名写得定规范的。
我还是很崇尚良好的编程风格的。呵呵,还望赐教。

使用特权

评论回复
16
john_light| | 2009-9-2 14:18 | 只看该作者
斜体应该是程序中用到数组索引[ i ],应该用[code][/code]括一下代码。

使用特权

评论回复
17
john_light| | 2009-9-2 14:22 | 只看该作者
测试一下贴代码:

void    main(void)
{
    unsigned char array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    unsigned char sum;
    unsigned char i;

    sum = 0;

    for (i = 0; i < 10; i++)
    {
        sum += array[i]
    }

    while(1);
}
void    main(void)
{
    unsigned char array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    unsigned char sum;
    unsigned char i;

    sum = 0;

    for (i = 0; i < 10; i++)
    {
        sum += array
    }

    while(1);
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

67

主题

452

帖子

1

粉丝