proteuswilliam hill官网 |仿真william hill官网
直播中

oldbeginner

11年用户 125经验值
擅长:可编程逻辑
私信 关注
[经验]

PROTEUS仿真学习笔记03 (uart )——2014_6_17

`***************
只看重点

***************


uart 内容点很多,为了简化,只看模式一。

06.JPG

这个结构起始 刚开始不用太理会,理解两个重点:
1、模式一 利用了 定时器 1 来产生波特率,占用了一个定时器。
2、 SBUF 名字只有一个,但物理上有 2个。

利用 C 语言而不是 汇编 更容易成功,同理 先 看代码,再来理解定义 也是一个好方法:

ATMEL 应用笔记,"UART Program Examples" 第一个例子中,就是

void main (void)
{
    SCON = 0x50; /* uart in mode 1 (8 bit), REN=1 */

。。。。省略

最先设置的寄存器就是 SCON,

07.JPG

红色的需要进行设置, 灰色的默认设置为0。

表示什么:

08.JPG

REN =1 表示允许 接受中断。

09.JPG

比如 输出 0x0F,示例如下

方式1输出.gif

从中可以看出,起始位为0, 然后LSB 先传送,8个字符传送结束后,再发送 停止位 1,一共10位。

方式1 下,传送 0x0F 需要多长时间?
首先,可以确认共有 10个 bit 需要传送,另外 就需要 波特率的帮助了。

找到百度百科
http://baike.baidu.com/view/119333.htm?fr=aladdin

单位“波特”本身就已经是代表每秒的调制数,以“波特每秒”(Baud per second)为单位是一种常见的错误。
每秒钟通过信道传输的信息量称为位传输速率,也就是每秒钟传送的二进制位数,简称比特率比特率表示有效数据的传输速率,用b/s 、bit/s、比特/秒,读作:比特每秒。

传输最常见的是 9600 bit/s,就是 每秒传送 9600个 bit,那么每个 bit 的传输时间 为 1/9600 秒= 0.104 ms。
10个bit 传输时间为 1.04 ms,即方式1 传送 一个字符(ASCII码,例如0x0F)的时间为 1.04 ms。

怎样确定波特率,

继续看代码
  1. TMOD = TMOD | 0x20 ; /* timer 1 in mode 2 */
  2. TH1 = 0xFD; /* 9600 Bds at 11.059MHz */
  3. TL1 = 0xFD; /* 9600 Bds at 11.059MHz */
两行代码就确保了波特率 为 9600,
计算公式如下
10.JPG

计算过程如下,
11.JPG

12.JPG

利用辅助软件,也很方便

这时,晶振 选择 11.0592Mhz 的作用就明显了,9600 波特率 没有误差;
13.JPG

如果选择 12MHz 晶振,
误差 8.51%。

14.JPG


`

回帖(22)

oldbeginner

2014-6-17 16:41:19
********************
简单的 串口发送字符串实例
********************

http://home.eeworld.com.cn/my/space-uid-139222-blogid-28925.html



例子非常简单,用来初步学习 uart 再好不过了。

代码风格不是很好,主函数内容有些多,先不管这些了。

先看主函数(晶振11.0592MHz,修改了一下),

  1. //主函数
  2. void main(void)
  3. {
  4.   unsigned char c = 0;

  5. SCON = 0x50;         //串口工作方式设置
  6. TMOD = 0x20;         //定时器工作方式设置

  7. TL1 = 0xfd;         //波特率设置
  8. TH1 = 0xfd;        //

  9. TI = 0;           // 清0发送中断标志        
  10. TR1 = 1;          //开启定时器

  11. delay(200);
  12. putstring("Receiving from 8051...rn");      //发送字符串,结尾回车换行
  13. putstring("----------------------rn");
  14. delay(50);
  15. while(1)
  16. {
  17.    putchar(c + 'A');          //发送字符
  18.   delay(100);
  19.   putchar(' ');         //
  20.   delay(100);
  21.   if(c == 25)           //每输出一遍后加横线
  22.   {
  23.     putstring("rn----------rn");
  24.    delay(100);
  25.   }
  26.   c = (c+1)%26;
  27.   if(c%10 == 0)         //每输出10个字符后回车换行
  28.   {
  29.     putstring("rn");
  30.    delay(100);
  31.   }
  32. }
  33. }

主函数 只是一个架子,SCON 和 TMOD 设置基本固定,
学习重点,在于
字符发送函数,该函数同时被 字符串 发送 函数调用。
  1. //字符发送函数
  2. void putchar(unsigned char data1)
  3. {
  4.   SBUF = data1;               //将待发送的字符送入发送缓冲器
  5. while(TI == 0);            //等待发送完成
  6. TI = 0;                     //发送中断标志请0
  7. }

  8. //字符串发送函数
  9. void putstring(unsigned char *dat)
  10. {
  11.   while(*dat != '\0')           //判断字符串是否发送完毕
  12. {
  13.    putchar(*dat);        //发送单个字符
  14.   dat++;                 //字符地址加1,指向先下一个字符
  15.   delay(5);
  16. }
  17. }

SBUF = data1;               //将待发送的字符送入发送缓冲器

这个命令一执行,单片机就开始 发送 data1,根据前面的计算,需要耗时 1.04 ms,

在这 1.04 ms内,cpu 什么事情也不做,就是等着 发送完成,
while(TI == 0);            //等待发送完成

发送完成后,
TI = 0;                     //发送中断标志请0

这样,一个字符就完成了一次发送,
如果是 字符串,则一个一个字符 按照 上述步骤 发送。



举报

oldbeginner

2014-6-17 18:19:49
引用: oldbeginner 发表于 2014-6-17 16:41
********************
简单的 串口发送字符串实例
********************

*******************
串口输入

*******************




  用软件置REN为1时,接收器以所选择波特率的16倍速率采样RXD引脚电平,检测到RXD引脚输入电平发生负跳变时,则说明起始位有效,将其移入输入移位寄存器,并开始接收这一帧信息的其余位。接收过程中,数据从输入移位寄存器右边移入,起始位移至输入移位寄存器最左边时,控制威廉希尔官方网站 进行最后一次移位。当RI=0,且SM2=0(或接收到的停止位为1)时,将接收到的9位数据的前8位数据装入接收SBUF,第9位(停止位)进入RB8,并置RI=1,向CPU请求中断。



同样看一个例子,
http://home.eeworld.com.cn/my/space-uid-139222-blogid-28926.html、



可以在上面的虚拟终端上 输入字符,然后下面的虚拟终端上可以显示出来。



  1. //主函数
  2. void main(void)
  3. {
  4.   unsigned char c = 0;
  5. SCON = 0x50;           //串口方式1 ,允许接收
  6. TMOD = 0x20;           //T1工作于方式2

  7. TL1 = 0xfd;              //波特率设置
  8. TH1 = 0xfd;            //

  9. EA = 1;                    //开总中断
  10. ES = 1;               //开串口接收中断
  11. //TI = 0;
  12. TR1 = 1;             //定时器开启

  13. delay(200);
  14. putstring("Receiving from 8051...rn");         //串口向终端发送字符串,结尾处回车换行
  15. putstring("----------------------rn");
  16. delay(50);
  17. while(1)
  18. {
  19.    
  20. }
  21. }

主函数中,根本没有显示 是如何接收的?这也是重点,接收 是通过中断来实现的。

  1. void revdata(void) interrupt 4
  2. {
  3.   unsigned char temp;
  4. if(RI == 0) return;         //如果没有接收中断标志,返回

  5. ES = 0;            //关闭串口中断
  6. RI = 0;            //清串行中断标志位
  7. temp = SBUF;        //接收缓冲器中的字符
  8. putchar(temp);        //将接收的字符发送出去
  9. ES = 1;                 //开启串口中断
  10. }

在接收的时候,关闭了串口中断,根据分析,接收需要耗时 1.04 ms,又发送出去再耗时1.04 ms,如果手工输入,没问题;但是 如果接收的 是字符串 就会出现丢失问题。
这个是 后面要 分析的。

举报

zhou2sheng

2014-6-17 22:32:36
感谢分享
举报

purplecomeeast

2014-6-18 10:14:00
谢谢分享学习一下
举报

oldbeginner

2014-6-18 15:19:54
本帖最后由 oldbeginner 于 2014-6-18 15:29 编辑
引用: oldbeginner 发表于 2014-6-17 18:19
*******************
串口输入

*************************
串口收、发数据的处理方法

*************************


这是一个很实用的话题,只可惜这样的专题很少,不过有,而且写得很好。

http://blog.csdn.net/gszhy/article/details/8594433
基本摘录


先引出上文的结论:

做而论道发表自己常用的方法:
接收数据时,使用“中断方式”,清除 RI 后,用一个变量通知主函数,收到新数据。
发送数据时,也用“中断方式”,清除 TI 后,用另一个变量通知主函数,数据发送完毕。

这样一来,收、发两者基本一致,编写程序也很规范、易懂。
更重要的是,主函数中,不用在那儿死等发送完毕,可以有更多的时间查看其它的标志。



***********************
反面教材:

看了一些网友编写的程序,发现有如下几条容易出错:
1.有人在发送数据之前,先关闭了串口中断!等待发送完毕后,再打开串口中断。
这样,在发送数据的等待期间内,如果收到了数据,将不能进入中断函数,也就不会保存的这个新收到的数据。
这种处理方法,就会遗漏收到的数据。

2.有人在发送数据之前,并没有关闭串口中断,当 TI = 1 时,是可以进入中断程序的。
但是,却在中断函数中,将 TI 清零!
这样,在主函数中的while(TI ==0);,将永远等不到发送结束的标志。

3.还有人在中断程序中,并没有区分中断的来源,反而让发送引起的中断,执行了接收中断的程序。

这些都是错的!
ico.ooopic.com.png

***********************

实例:

要求:
求一个PC与单片机串口通信的程序,要求如下:
1、如果在电脑上发送以$开始的字符串,则将整个字符串原样返回(字符串长度不是固定的)。
2、如果接收到1,则将P10置高电平,接收到0,P10置低电平。(用来控制一个LED)

串口中断.gif

代码非常好理解:

最佳答案:
下列程序,已经调试成功。
#include
***it LED = P1^0;
unsigned char UART_buff;
bit New_rec = 0, Send_ed = 1, Money = 0;
//----------------------------------------------
void main (void)
{
    SCON = 0x50;   //串口方式1, 8-n-1, 允许接收.
    TMOD = 0x20;   //T1方式2
    TH1 = 0xFD;    [url=]//9600bps@11.0592MHz[/url]
    TL1 = 0xFD;
    TR1 = 1;                        
    ES  = 1;       //开中断.
    EA  = 1;
    while(Money == 0);    //等着交费,呵呵,等着接收$.
    while(1)  {
      if ((New_rec == 1) && (Send_ed == 1))  {  //如果收到新数据及发送完毕
        SBUF = UART_buff; //那就发送.
        New_rec = 0;
        Send_ed = 0;
    } }
}
//----------------------------------------------
void ser_int (void) interrupt 4
{
    if(RI == 1) {  //如果收到.
      RI = 0;      //清除标志.
      New_rec = 1;
      UART_buff = SBUF;  //接收.
      if(UART_buff == '1')  LED = 1;
      if(UART_buff == '0')  LED = 0;
      if(UART_buff == '$')  Money = 1;
    }
    else  {        //如果送毕.
      TI = 0;      //清除标志.
      Send_ed = 1;
    }
}
//----------------------------------------------


重点就是 最后面的 中断 处理函数,

接收中断处理
      RI = 0;      //清除标志.  
      New_rec = 1;  
      UART_buff = SBUF;  //接收.  
      if(UART_buff == '1')  LED = 1;  
      if(UART_buff == '0')  LED = 0;  
      if(UART_buff == '$')  Money = 1;


发送中断处理
      TI = 0;      //清除标志.  
      Send_ed = 1;


****************************

利用 中断 处理 串口 收 和 发 是个不错的开始。
举报

oldbeginner

2014-6-18 16:30:35
引用: oldbeginner 发表于 2014-6-18 15:19
*************************
串口收、发数据的处理方法

*************************
串口 接收 数组

接收缓冲区 大小怎样确定?*************************

上面的例子都是 单个单个 字符 的收发。

http://zhidao.baidu.com/link?url ... miK669s0Xgl0DnHBX5K
我现在已经会了串口接受然后直接串口发送。可是要怎么把接收到的数组数据储存在单片机里呢?

答案也是非常的简单,使用数组。
你用一个数组变量(也就是开辟一个接收缓冲区)就OK了如:
  1. #include
  2. #define uchar unsigned char
  3. uchar buf[16],num;
  4. void ser_isr() interrupt 4
  5. {
  6.         if(RI)
  7.         {
  8.         RI=0;
  9.         buf[num]=SBUF;
  10.         num++;
  11.         num&=0x0f;
  12.         }
  13. }
  14. main()
  15. {
  16.         TMOD=0x20;
  17.         SCON=0x50;

  18.         TH1=TL1=0xfd;

  19.         TR1=1;
  20.         ES=1;
  21.         EA=1;

  22.         while(1);
  23. }

***********************************

一般情况下,串口数组根据 需求来设定 大小,比如 已知 接收的字符串 最大长度 L,那么 设定 L+1 就可以了,最末尾是字符串结束符 'n'。

因为51单片机内部 RAM 有限,所以 比较大的数组可以 通过 xdata 关键字 使用扩展RAM。

一般情况下,不涉及到 scheduler,所以 按需制定。 如果使用了 ucos 等类似操作系统,

http://zzhere2007.blog.163.com/blog/static/85336771201111210440786/

在设计串口程序时,我们都知道要设置一个缓冲区,那么到底这个缓冲区要多大呢?
Jean J.Labrosse 的Embedded_systems_building_blocks中,他是这么说的:
  When you use a buffer,the size of the buffer depends on how quickly your background process can get control of the CPU to process the information .For example,if the worst case latency of your background process is 200ms,you should plan for a buffer of at least 192 bytes if your serial port receives bytes at 9600 baud(960 bytes/sec * 200ms).
所以,按他的讲法,缓冲区大小 = 波特率*cpu处理最严重事件的等待时间



举报

oldbeginner

2014-6-20 15:19:55
本帖最后由 oldbeginner 于 2014-6-20 15:24 编辑
引用: oldbeginner 发表于 2014-6-18 16:30
*************************
串口 接收 数组

*******************
串口 的暗号
22.jpg
*******************


在串口 和 上位机 的通讯中, 经常要用 到 “暗号”,这样上位机 和 单片机 才能互相 确认 对方。

这里主要理解 单片机 是如何 通过 接收到 的字符串 判断出 “自己人”。

主要内容参考
http://blog.csdn.net/gszhy/article/details/8594433
这篇文章的下半部分。

这就要求我们的单片机能够在连续接收到的串口数据序列中识别出符合自己板卡对应的通信协议,来进行控制操作,不符合则不进行任何操作。

简而言之就是,单片机要在一串数据中找到符合一定规律的几个字节的数据。



23.JPG


要实现上述 功能,

先 上一个 带 bug 的代码

然后串口中断部分
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;//判断不满足条件就将计数值清零
  }
}



在 串口 中断 中,判断 接收 字符串 是否 有 “暗号”。
如果 确认 含有 “暗号”,则 讲 uart_flag 置 1,标示 收到信息。

上面的代码有 两个 bug:

1、如果数据帧发送一半,然后突然停止,再来重新发,就会丢失一帧的数据。比如先接受到aa 55,然后断了,再进来aa 55 01 01,就不受控制了。

2、如果在多设备通信中,属于其他设备的的帧数据最后一位是aa(或者最后两位为aa 55 ,或者最后3位为aa 55 板选),下一次通信的数据就接收不到了。


解决方案:

考虑到每次数据都是连续发送的(至少我们用labwindows做的上位机程序是这样的),成功接收到了一帧数据是要有一定时间回复的,也就是说如果接收到一半,但是很长时间没接收到数据,把计数值count清零就ok啦。



全局变量定义
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,工作方式1
       PCON = 0x80;                // 波特率加倍
       TH1 = 0xfa;
       TL1 = 0xfa;               //写入串口定时器初值
       TH0=(65536-2000)/256;    //写入定时器0初值,串口传输一个字节时间为(1/19200)*10,计算得0.52ms
       TL0=(65536-2000)%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-2000)/256;
       TL0=(65536-2000)%256;
       if(count!=0)
       {
              TR0=1;
       }
}

定时器中断函数
void T0_time()interrupt 1
{     
    TR0=0;
       TH0=(65536-2000)/256;
       TL0=(65536-2000)%256;
       count=0;

}


感觉代码还是 有些问题的。

问题在于:只要一接收,count 不为0,一定会打开 定时器0,1ms 后,count 变为0,好像非常容易出错。

不过思路 非常值得借鉴,就是利用定时器,来判断 接收字节 之间 的时间 是否 过长,如果时间过长,表示传送结束。

举报

oldbeginner

2014-6-20 17:11:19
引用: oldbeginner 发表于 2014-6-20 15:19
*******************
串口 的暗号

*******************
串口 接收 字符串 实例

———— 利用定时器 判断 超时

*******************


这是 根据一个开源 的仿三菱PLC 代码中 串口接收 改过来的,所以变量名 有些怪。

http://www.amobbs.com/thread-5560211-1-1.html

带中断接收.gif

需要用到的变量

volatile unsigned char UartReceiveCounter=0;                        //接收计数器
volatile unsigned char UartRxTimerStartFlag=0;                        //接收超时计数器启动标志
volatile unsigned char UartWaitForCounter=0;                        //接收超时计数器
volatile unsigned char UDRFlag=0;                                //接收完成标志

#define InLEN 30
unsigned char Buffer[InLEN]={0};

这里省略了 “暗号”,但是 可以 很容易 添加上去。

主函数 很简单,

//主函数
void main(void)
{
  unsigned char c = 0;
  /*****************************************/
SCON = 0x50;           //串口方式1 ,允许接收
TMOD = 0x20;           //T1工作于方式2

TL1 = 0xfd;              //波特率设置
TH1 = 0xfd;            //
EA = 1;                    //开总中断
ES = 1;               //开串口接收中断
//TI = 0;
TR1 = 1;             //定时器开启
/********************************************/
    //***定时器Timer0初始化***
    TMOD&=0xF0;            //将TMOD的低4位定时器0控制部分清零
    TMOD|=0x01;            //设置定时器0为方式1
    TL0=0x47;              //设置定时器0初值低8位
    TH0=0xFF;              //设置定时器0初值高8位
    TR0=1;                 //启动定时器0
    ET0=1;                 //Timer0中断允许
    //**********************

    /*****************************************/
delay(200);
putstring("Receiving from 8051...rn");         //串口向终端发送字符串,结尾处回车换行
putstring("----------------------rn");
delay(50);
while(1)
{
     FX1NProcessing();
   
}
}


学习的重点 在于 串口 中断 和 定时器 中断,利用 两个中断 一起完成任务。

串口 中断中,
1、接收字符;
2、启动定时器(字符之间 最大延时判断),并且将 计数器UartWaitForCounter 置0;
3、判断是否超过了 最大数组长度(字符结束标志位)。


void revdata(void) interrupt 4 using 2
{
    if(RI)
    {
        RI = 0;
        
        Buffer[UartReceiveCounter++]=SBUF;
        Buffer[UartReceiveCounter]='';
        UartRxTimerStartFlag=1;    // 启动超时计数器
          UartWaitForCounter=0;        // 清超时计数器    // 10ms
   
        if (UartReceiveCounter>=InLEN)
        {
            UDRFlag=1;
            REN=0;
        }
    }
}



重中之重,
定时器中,
1、每次 UartWaitForCounter 加 1;
2、当 UartWaitForCounter 超过 10 (接收超时的标志),发出接收结束标志。


void Timer0(void) interrupt 1
{                          //定时100微秒
    TL0=0x47;              //重新给TL0赋初值
    TH0=0xFF;              //重新给TH0赋初值

    //***此处用户自行添加定时器T0中断处理程序***

    //******************************************
          if(UartRxTimerStartFlag)                        // 接收超时...50ms
        { if(UartWaitForCounter>=10)
                { UartWaitForCounter=0;
                  UartRxTimerStartFlag=0;
                  UDRFlag=1;
                  REN=0;                                                // 禁止UART接收器
                }
          else UartWaitForCounter++;
        }
}


可以看出, 串口 中断 和 定时器 中断 互相配合,才能顺利完成 字符接收。

主函数中 的循环,可以进行 “暗号”处理,这里为了简便,只是把接收的字符串 再 发出去。

void FX1NProcessing(void)
{
        if(UDRFlag)
                {
                        UDRFlag=0;
                        putstring(Buffer);
       
                        UartReceiveCounter=0;
                        REN=1;
                }
}
举报

oldbeginner

2014-6-21 17:07:21
引用: oldbeginner 发表于 2014-6-20 17:11
*******************
串口 接收 字符串 实例

************************
环形 队列

—— 发送 部分
************************

3b292df5e0fe9925b4e0ccbc35a85edf8cb17169.jpg.png

环形队列 的象征 还是挺酷的。

http://blog.csdn.net/zyboy2000/article/details/21618131

****************************
必要性
—— 不使用 环形队列 的缺点
****************************

1、查询方式 ---编程简单,缺点耗时

24.JPG

逆向思维:下面的两种 方法不理解时,就用这种,简单不出错;只是 耗时(9600 波特率下,一个字符 需要耗时 1.04 ms,CPU 只能等 传输结束,期间不能 干其他事情。)

2、非缓冲中断方式 --不耗时,缺点数据无缓冲区,前面包的数据如果还未发完,后面又有包,前面包就会被后面包冲掉

unsigned char sendBuffer[100];
unsigned char sendBufCur,sendBufTotal;


void SendString(unsigned char *string,int length)
{
int m;

for(m=0;m {
  sendBuffer = *(string+m);         
}
        sendBufCur = 0;
        sendBufTotal = length;
        SBUF =string[0];
}


void ComInt() interrupt 4  
{   
      
    if(TI)                            //发送中断处理  
    {  
        TI = 0;                    //清标志
        sendBufCur++;
        if(sendBufCur         {
            SBUF = sendBuffer[sendBufCur];
        }

    }  
}



上述两种方法 就是 最常见 的串口 发送 方法,环形队列相对来说 比较少。

先看代码,再理解 是什么。

unsigned char SendItComm=1;

void SendCommBuffer(unsigned char *base, unsigned char size)
{
        unsigned char j=0;
         if (!size) { return; }

         while (j         {  
                 CommSendBuffer[CommSendBufferTail]=base[j];
                 j++;
                 CommSendBufferTail++;

                 if (CommSendBufferTail==DB_SENDMAXSIZE)
                {
                        CommSendBufferTail=0;
                }
        }
        if (SendItComm) //当SendItComm为零时,表示上一帧数据还未发送完毕
         {  
                 SBUF=CommSendBuffer[CommSendBufferHead];
         }
}



void CommISR(void) interrupt 4
{
         if (TI)
         {
               TI=0;
               CommSendBufferHead++;
               if (CommSendBufferHead==DB_SENDMAXSIZE)
               {  
                       CommSendBufferHead=0;
               }

              if (CommSendBufferHead!=CommSendBufferTail)
              {  
                      SBUF=CommSendBuffer[CommSendBufferHead];  // send the next byte
                      SendItComm=0;
              }
               else
              {
                      SendItComm=1;
              }
         }
}



上面的代码 就是 环形 队列,看上去 也不是 那么 拒人千里之外。

要理解 环形队列,先补充些 基础知识,

25.JPG

顺序队列.gif

26.JPG

27.JPG


如图所示:由于入队时尾指针向前追赶头指针,出队时头指针向前追赶尾指针,故队空和队满时头尾指针均相等。因此,我们无法通过front=rear来判断队列“空”还是“满”。

另设一个布尔变量(标志位)以匹别队列的空和满


有了上面的基础,再来理解 这个程序(可能存在错误,红色):
1、在发送缓冲 函数中,主要是 操作 尾指针,用来 给 环形队列 元素入队;
2、在串口 中断函数中,主要是 操作 头指针,用来 元素 出对;
3、 没有 判断 满 和 空 的措施。


感觉 链接 中 的程序, 发送缓冲 函数 有问题:添加元素时,没有 检查 尾指针 是否 追赶上 头指针。
这个程序 不能够 使用,不过 用来 理解 概念 是可以的。
举报

oldbeginner

2014-6-21 18:53:40
本帖最后由 oldbeginner 于 2014-6-21 18:55 编辑
引用: oldbeginner 发表于 2014-6-21 17:07
************************
环形 队列

*********************
完善后的

环形队列 发送函数
*********************


在上一楼中,举得例子 其实 是错误的,因为 没有 “满”和“空”的判断,不过用来 理解 大体思路 是可以的。

还是看一个比较 完善的,
源代码还是来源于
http://www.amobbs.com/thread-5560211-1-1.html

环形队列输出proteus.gif

利用 环形队列输出 的仿真。

看代码前的准备工作,

满空 标志判断 方法:
少用一个元素的空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队满(注意:rear所指的单元始终为空);

29.JPG

然后,理解程序结构

28.JPG

30.JPG

31.JPG

上面就是 程序 实现的大框架。

先看头文件 和变量定义

  1. #include        //包含头文件
  2. #include
  3. //毫秒级延时函数

  4. #define OutLEN 30
  5. volatile unsigned char UartSendBuffer[OutLEN];                        //发送缓冲

  6. volatile unsigned char *front=UartSendBuffer;                        //最后由中断传输出去的字节位置
  7. volatile unsigned char *rear=UartSendBuffer;                        //最后放入发送缓冲区的字节位置
  8. volatile unsigned char UartSendBufferemptyFlag=1;                //缓冲区数据发完标志   发完=1
  9. volatile unsigned char UartSendBufferHaveDataFlag=0;        //发送缓冲区非空标志   有=1

  10. volatile unsigned char UartReceiveCounter=0;                        //接收计数器
  11. volatile unsigned char UartRxTimerStartFlag=0;                        //接收超时计数器启动标志
  12. volatile unsigned char UartWaitForCounter=0;                        //接收超时计数器
  13. volatile unsigned char UartDataReadyFlag=0;                                //接收完成标志


然后看主函数

  1. //主函数
  2. void main(void)
  3. {
  4.   unsigned char c = 0;
  5. SCON = 0x50;         //串口工作方式设置
  6. TMOD = 0x20;         //定时器工作方式设置

  7. TL1 = 0xfd;         //波特率设置
  8. TH1 = 0xfd;        //

  9. TI = 0;           // 清0发送中断标志        
  10. TR1 = 1;          //开启定时器
  11.      ES=1;                  //允许串口中断
  12.     //**************************

  13.     //***开全局中断设置****
  14.     //串口接口UART设置了中断允许,此处要开全局中断
  15.     EA=1;                  //开全局中断
  16.     //****
  17. delay(200);
  18. UartSendString("Receiving from 8051...rn");      //发送字符串,结尾回车换行
  19. UartSendString("--------------------------------------------------rn-------------------------------------------------------------rn");
  20. delay(50);
  21. while(1)
  22. {
  23.    UartSendchar(c + 'A');          //发送字符
  24.   delay(100);
  25.   UartSendchar(' ');         //
  26.   delay(100);
  27.   if(c == 25)           //每输出一遍后加横线
  28.   {
  29.     UartSendString("rn----------rn");
  30.    delay(100);
  31.   }
  32.   c = (c+1)%26;
  33.   if(c%10 == 0)         //每输出10个字符后回车换行
  34.   {
  35.     UartSendString("rn");
  36.    delay(100);
  37.   }
  38. }
  39. }

OUTLEN = 30,但是 发送 的字符串"--------------------------------------------------rn-------------------------------------------------------------rn" 是超过30的,这也是环形队列的优点。用有限的缓冲 发送 更大的字符串。

现在就开始看重点:

发送缓冲函数,

void UartSendchar(unsigned char ucdata)
{
    ES=0;                                     // 暂停串行中断,以免数据比较时出错
    while((((front-rear)==1)&&(front > rear ))||((front < rear)&&(OutLEN-(rear-front)==1)))
    {
        ES=1;
        _nop_();
        _nop_();
        ES=0;
    }
    *rear=ucdata;                        // 放字节进入缓冲区
    rear++;                              // 发送缓冲区指针加1
    if (rear==UartSendBuffer+OutLEN) rear=UartSendBuffer;        // 指针到了顶部换到底部
    UartSendBufferHaveDataFlag=1;
    if (UartSendBufferemptyFlag)             // 缓冲区无数据
    {
        UartSendBufferemptyFlag =0;
        SBUF=*front;                        // 未发送完继续发送
        front++;                            // 最后传出去的字节位置加1
        if (front==UartSendBuffer+OutLEN)front=UartSendBuffer;    // 地址到顶部回到底部
        if (rear==front)UartSendBufferHaveDataFlag=0;            // 数据发送完置发送缓冲区空标志
    }                                        // 缓冲区开始为空,置为有,启动发送
    ES=1;
}



还是比较复杂的,结合 框架理解,




串口中断函数,

void Uart(void) interrupt 4 using 2
{
    if(TI)
    {
        TI=0;
        if (UartSendBufferHaveDataFlag)
        {
            SBUF=*front;                                                 // 未发送完继续发送
            front++;                                                     // 最后传出去的字节位置加1
            if (front==UartSendBuffer+OutLEN)front=UartSendBuffer;    // 地址到顶部回到底部
            if (rear==front)UartSendBufferHaveDataFlag=0;            // 数据发送完置发送缓冲区空标志
        }
        else UartSendBufferemptyFlag =1;
    }
    if(RI)
    {
        RI = 0;
    }
}


相比较而言,串口中断函数 非常容易理解。


代码和仿真下载

举报

oldbeginner

2014-6-24 20:59:06
引用: oldbeginner 发表于 2014-6-21 18:53
*********************
完善后的

**********************
多机 通讯
RS 232
**********************

来自于《单片机C语言程序设计实训100例——基于8051+Proteus仿真》

https://bbs.elecfans.com/jishu_357238_1_1.html

****************************
第 45 例,
甲机通过串口控制乙机LED闪烁


甲机通过串口控制乙机LED闪烁.gif

实现的功能 很简单,

先看甲机:
36.JPG

这里 的接口 模仿了实际情况,使用 MAX232 中间过渡。

和之前的的程序 相比,这个程序 极其 简单,

/*    名称:甲机发送控制命令字符
    说明:甲单片机负责向外发送控制命令字符"A"、"B"、"C",或者停止发送,乙机根据所接收到的字符完成LED1闪烁、LED2闪烁、双闪烁、或停止闪烁。
*/
#include
#define uchar unsigned char
#define uint unsigned int
***it LED1=P0^0;        
***it LED2=P0^3;
***it K1=P1^0;
//延时
void DelayMS(uint ms)
{
    uchar i;
    while(ms--) for(i=0;i<120;i++);
}
//向串口发送字符
void Putc_to_SerialPort(uchar c)
{
    SBUF=c;
    while(TI==0);
    TI=0;
}
//主程序
void main()
{
    uchar Operation_No=0;
    SCON=0x40;        //串口模式1
    TMOD=0x20;        //T1工作模式2

    TH1=0xfd;
    TL1=0xfd;
    TI=0;
    TR1=1;
    while(1)
    {
        if(K1==0)    //按下K1时选择操作代码0,1,2,3
        {
            while(K1==0);
            Operation_No=(Operation_No+1)%4;
        }
        switch(Operation_No)    //根据操作代码发送A/B/C或停止发送
        {
            case 0:    LED1=LED2=1;
                    break;
            case 1:    Putc_to_SerialPort('A');
                    LED1=~LED1;LED2=1;
                    break;
            case 2:    Putc_to_SerialPort('B');
                    LED2=~LED2;LED1=1;
                    break;
            case 3:    Putc_to_SerialPort('C');
                    LED1=~LED1;LED2=LED1;
                    break;
        }
        DelayMS(100);
    }
}


经常用的 SCON= 0x50,这里 SCON= 0x40,是因为 甲机不需要接收,所以 REN=0。

再来看 乙机,

37.JPG

程序同样 好简单,

/*    名称:乙机程序接收甲机发送字符并完成相应动作
    说明:乙机接收到甲机发送的信号后,根据相应信号控制LED完成不同闪烁动作。
*/
#include
#define uchar unsigned char
#define uint unsigned int
***it LED1=P0^0;        
***it LED2=P0^3;
//延时
void DelayMS(uint ms)
{
    uchar i;
    while(ms--) for(i=0;i<120;i++);
}
//主程序
void main()
{
    SCON=0x50;        //串口模式1,允许接收
    TMOD=0x20;        //T1工作模式2

    TH1=0xfd;        //波特率9600
    TL1=0xfd;
    RI=0;
    TR1=1;
    LED1=LED2=1;
    while(1)
    {
        if(RI)    //如收到则LED闪烁
        {
            RI=0;
            switch(SBUF)    //根据所收到的不同命令字符完成不同动作
            {
                case 'A':    LED1=~LED1;LED2=1;break;    //LED1闪烁
                case 'B':    LED2=~LED2;LED1=1;break;    //LED2闪烁
                case 'C':    LED1=~LED1;LED2=LED1;        //双闪烁
            }
        }
        else LED1=LED2=1;                                //关闭LED
        DelayMS(100);
    }
}


**********************

这个例子很不错,即简单容易理解,又能 表达出 主题。
举报

oldbeginner

2014-6-24 21:40:13
引用: oldbeginner 发表于 2014-6-24 20:59
**********************
多机 通讯
RS 232

***************
例程 46

单片机之间双向通信
***************


单片机之间双向通信.gif

例程46 是 在 45基础上,做了升级,例程45只是 甲机 发送,乙机 接收;而例程46 则 收发都有。

甲机状态
38.JPG

代码只是稍微复杂了一点,同样容易理解

/*    名称:甲机串口程序
    说明:甲机向乙机发送控制命令字符,甲机同时接收乙机发送的数字,并显示在数码管上。
*/
#include
#define uchar unsigned char
#define uint unsigned int
***it LED1=P1^0;        
***it LED2=P1^3;
***it K1=P1^7;
uchar Operation_No=0;    //操作代码
//数码管代码
uchar code DSY_CODE[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
//延时
void DelayMS(uint ms)
{
    uchar i;
    while(ms--) for(i=0;i<120;i++);
}
//向串口发送字符
void Putc_to_SerialPort(uchar c)
{
    SBUF=c;
    while(TI==0);
    TI=0;
}
//主程序
void main()
{
    LED1=LED2=1;
    P0=0x00;
    SCON=0x50;        //串口模式1,允许接收
    TMOD=0x20;        //T1工作模式2

    TH1=0xfd;
    TL1=0xfd;
    TI=RI=0;
    TR1=1;
    IE=0x90;        //允许串口中断
    while(1)
    {
        DelayMS(100);
        if(K1==0)    //按下K1时选择操作代码0,1,2,3
        {
            while(K1==0);
            Operation_No=(Operation_No+1)%4;
        
            switch(Operation_No)    //根据操作代码发送A/B/C或停止发送
            {
                case 0:    Putc_to_SerialPort('X');
                        LED1=LED2=1;
                        break;
                case 1:    Putc_to_SerialPort('A');
                        LED1=~LED1;LED2=1;
                        break;
                case 2:    Putc_to_SerialPort('B');
                        LED2=~LED2;LED1=1;
                        break;
                case 3:    Putc_to_SerialPort('C');
                        LED1=~LED1;LED2=LED1;
                        break;
            }
        }
    }
}
//甲机串口接收中断函数
void Serial_INT() interrupt    4
{
    if(RI)
    {
        RI=0;
        if(SBUF>=0&&SBUF<=9) P0=DSY_CODE[SBUF];
        else P0=0x00;
    }
}
   

SCON 又变回 熟悉 的 0x50,因为需要接收了,要打开接收中断,REN=1。
使用了 接收串口中断,发送则放在主函数中,非常简单。


乙机状态
39.JPG

/*    名称:乙机程序接收甲机发送字符并完成相应动作
    说明:乙机接收到甲机发送的信号后,根据相应信号控制LED完成不同闪烁动作。
*/
#include
#define uchar unsigned char
#define uint unsigned int
***it LED1=P1^0;        
***it LED2=P1^3;
***it K2=P1^7;
uchar NumX=-1;
//延时
void DelayMS(uint ms)
{
    uchar i;
    while(ms--) for(i=0;i<120;i++);
}
//主程序
void main()
{
    LED1=LED2=1;
    SCON=0x50;        //串口模式1,允许接收
    TMOD=0x20;        //T1工作模式2
    TH1=0xfd;        //波特率9600
    TL1=0xfd;

    RI=TI=0;
    TR1=1;
    IE=0x90;
    while(1)
    {
        DelayMS(100);
        if(K2==0)
        {
            while(K2==0);
            NumX=++NumX%11;    //产生0~10范围内的数字,其中10表示关闭
            SBUF=NumX;
            while(TI==0);
            TI=0;
        }
    }
}
void Serial_INT() interrupt 4
{
    if(RI)    //如收到则LED则动作
    {
        RI=0;
        switch(SBUF)    //根据所收到的不同命令字符完成不同动作
        {
            case 'X':    LED1=LED2=1;break;        //全灭
            case 'A':    LED1=0;LED2=1;break;    //LED1亮
            case 'B':    LED2=0;LED1=1;break;    //LED2亮
            case 'C':    LED1=LED2=0;            //全亮
        }
    }
}


************************
虽然 有 主机 和 从机 代码,初学者很容易被唬住。从学习角度上看,理解主机代码,也就能够非常容易理解从机代码。
所以只要学会 一个机器的代码,另外一个也很容易学会。

而且只看 主机 或 从机 代码,还是非常简单的。

举报

oldbeginner

2014-6-24 22:00:02
本帖最后由 oldbeginner 于 2014-6-24 22:04 编辑
引用: oldbeginner 发表于 2014-6-24 21:40
***************
例程 46

******************
例程 47 和 48

和主机通讯
******************

例程47就是 开头引用 例子 的原版出处,不再理解了。
例程47.gif

例程48,
例程48.gif

这个有点复杂,不过也好理解,多了一个 外部中断0。

#include
#define uint unsigned int
#define uchar unsigned char
uchar Receive_Buffer[101];
uchar Buf_Index = 0;
uchar code DSY_CODE[]=
{
        0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00
};

void Delay(uint x)
{
        uchar j;
        while(x--)
        {
                 for(j=0;j<120;j++);
        }
}

void main()
{
        uchar j;
        P0 = 0x00;
        Receive_Buffer[0]=i;
        SCON = 0x50;
        TMOD = 0x20;

        TH1  = 0xfd;
        TL1  = 0xfd;
        EA   = 1;
        EX0  = 1;
        IT0  = 1;
        ES   = 1;
        IP   = 0x01;
        TR1  = 1;
        while(1)
        {
                 for(j=0;j<100;j++)
                {
                         if(Receive_Buffer[j]==-1)
                                break;
                        P0 = DSY_CODE[Receive_Buffer[j]];
                        Delay(200);
                }
                Delay(200);
        }
}

void Serial_INT() interrupt 4
{
        uchar c;
        if(RI==0)
                return;
        ES = 0;
        RI = 0;
        c  = SBUF;
        if(c>='0' && c<='9')
        {
                 Receive_Buffer[Buf_Index]=c-'0';
                Receive_Buffer[Buf_Index+1]=-1;
                Buf_Index = (Buf_Index+1)%100;       
        }
        ES = 1;
}

void EX_INT0() interrupt 0
{
        uchar *s = ("Receiving From 8051...rn");
        uchar j = 0;
        while(s[j]!='')
        {
                 SBUF = s[j];
                while(TI == 0);
                TI = 0;
                j++;
        }
}


《100例》选取的几个例子 作为入门还是不错的。
举报

lss9310

2014-8-1 11:53:20
很好得学习资料
举报

沈阳

2014-8-4 15:40:58
好厉害     哈哈哈
举报

lss9310

2014-8-12 11:23:22
好的资料,谢谢
举报

shj21cn

2014-8-17 22:58:10
非常好的学习笔记!顶~~!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
举报

孙阳

2015-7-17 21:26:28
赞一个,,感谢楼主
举报

867930

2016-8-10 16:26:36
holy high!谢谢楼主啦
举报

更多回帖

发帖
×
20
完善资料,
赚取积分