找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3|回复: 0
收起左侧

第10章 UART串口通信和指针基础10.3 10.4

[复制链接]
ID:1167894 发表于 2026-4-21 14:04 | 显示全部楼层 |阅读模式
10.3 UART串口通信的基本应用
10.3.1
通信的三种基本类型
常用的通信从传输方向上可以分为单工通信、半双工通信、全双工通信三类。
单工通信就是指只允许一方向另外一方传送信息,而另一方不能回传信息。比如电视遥控器、收音机广播等,都是单工通信技术。
半双工通信是指数据可以在双方之间相互传播,但是同一时刻只能其中一方发给另外一方,比如对讲机就是典型的半双工。
全双工通信就发送数据的同时也能够接收数据,两者同步进行,就如同电话一样,说话的同时也可以听到对方的声音。
10.3.2 UART模块介绍
使用定时器模块定时某一特定时间时,利用定时器中断系统,可以让单片机只有在定时器中断发生时去执行相应动作。同样,串口的中断系统,可以让串口通信模块自动接收完数据后,通知单片机去执行相应的动作。当然,这一切都要基于配置好对应的特殊功能寄存器的前提。
51单片机的UART串口的结构由串行口控制寄存器SCON、发送电路和接收电路三部分构成,串口控制寄存器SCON如表10-1表10-2所示。
10-1  SCON——串行控制寄存器的位分配(地址0x98、可位寻址)
表10-1.jpg
10-2  SCON——串行控制寄存器的位描述
表10-2.jpg
对于串口的四种模式,模式1是最常用的,即前边提到的1位起始位,8位数据位和1位停止位。下面主要介绍模式1的工作细节和使用方法,至于其它3种模式与此也是大同小异,真正遇到需要使用的时候再去查阅相关资料。
UART串口模块有一个专门的波特率发生器用来控制发送和接收数据的速度。对于STC89C52单片机来讲,这个波特率发生器只能由定时器T1或定时器T2控制产生,而不能由定时器T0产生。
如果用定时器2,需要配置额外的寄存器,默认是使用定时器1的。本章内容使用定时器T1作为波特率发生器,方式1下的波特率发生器必须使用定时器T1的模式2,也就是自动重装载模式,定时器的重载值计算公式为:
    TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率
和波特率有关的还有一个寄存器,是一个电源管理寄存器PCON,他的最高位可以把波特率提高一倍,也就是如果写PCON |= 0x80以后,计算公式就成了:
    TH1 = TL1 = 256 - 晶振值/12 /16 /波特率
公式中数字的含义这里解释一下,256是8位定时器的溢出值,也就是TL1的溢出值,晶振值在Kingst51开发板上是11059200,12表达的是1个机器周期等于12个时钟周期,值得关注的是这个16,采取并确认信号是0还是1的方式是把一位信号采集16次,其中第7、8、9次取出来,这三次中其中两次如果是高电平,那么就认定这一位数据是1,如果两次是低电平,那么就认定这一位是0,这样一旦受到意外干扰读错一次数据,也依然可以保证最终数据的正确性。
了解了串口采集模式,在这里留一个思考题。“晶振值/12/2/16/波特率”计算的时候,出现不能除尽,或者出现小数怎么办,允许出现多大的偏差?弄清楚这个问题,也就理解了晶振为何使用11.0592M了。
串口通信的发送和接收电路在物理上有2个名字相同的SBUF寄存器,它们的地址也都是0x99,但是一个用来做发送缓冲,一个用来做接收缓冲。就是说,有2个房间,两个房间的门牌号是一样的,其中一个只出人不进人,另外一个只进人不出人,这样就可以实现UART的全双工通信,相互之间不会产生干扰。在逻辑上每次操作SBUF,单片机会自动根据对它执行的是“读”操作还是“写”操作来选择是接收SBUF还是发送SBUF。
10.3.3UART串口通信配置流程
通常情况下编写串口通信程序的基本步骤如下所示:
1、配置串口为模式1。
2、配置定时器T1为模式2,即自动重装模式。
3、根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
4、打开定时器控制寄存器TR1,让定时器跑起来。
这里还要特别注意一下,就是在使用T1做波特率发生器的时候已经被使用,千万不要再使能T1的中断了。
10.4 通信实例与ASCII码
串口通信一项重要功能就是实现单片机和电脑之间的信息交互,既可以用电脑控制单片机,也可以把单片机的一些信息状况发给电脑上的软件。下面做一个简单的例程,实现单片机串口调试助手发送的一个字节的数据,在Kingst51开发板上的数码管上显示出来,并且单片机再将接收到的数据通过串口发送给电脑。
  1. #include <reg52.h>
  2. sbit ADDR3 = P1^3;
  3. sbit ENLED = P1^4;
  4. unsigned char code LedChar[] = {  //数码管显示字符转换表
  5.     0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
  6.     0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
  7. };
  8. unsigned char LedBuff[7] = {  //数码管+独立LED显示缓冲区
  9.     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
  10. };
  11. unsigned char T0RH = 0;  //T0重载值的高字节
  12. unsigned char T0RL = 0;  //T0重载值的低字节
  13. unsigned char RxdByte = 0;  //串口接收到的字节
  14. void ConfigTimer0(unsigned int ms);
  15. void ConfigUART(unsigned int baud);
  16. void main()
  17. {
  18.     EA = 1;        //使能总中断
  19.     ENLED = 0;    //选择数码管和独立LED
  20.     ADDR3 = 1;
  21.     ConfigTimer0(1);   //配置T0定时1ms
  22.     ConfigUART(9600);  //配置波特率为9600
  23.    
  24.     while (1)
  25.     {   //将接收字节在数码管上以十六进制形式显示出来
  26.         LedBuff[0] = LedChar[RxdByte & 0x0F];
  27.         LedBuff[1] = LedChar[RxdByte >> 4];
  28.     }
  29. }
  30. /* 配置并启动T0,ms-T0定时时间 */
  31. void ConfigTimer0(unsigned int ms)
  32. {
  33.     unsigned long tmp;  //临时变量
  34.    
  35.     tmp = 11059200 / 12;       //定时器计数频率
  36.     tmp = (tmp * ms) / 1000;  //计算所需的计数值
  37.     tmp = 65536 - tmp;         //计算定时器重载值
  38.     tmp = tmp + 13;            //补偿中断响应延时造成的误差
  39.     T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
  40.     T0RL = (unsigned char)tmp;
  41.     TMOD &= 0xF0;   //清零T0的控制位
  42.     TMOD |= 0x01;   //配置T0为模式1
  43.     TH0 = T0RH;     //加载T0重载值
  44.     TL0 = T0RL;
  45.     ET0 = 1;        //使能T0中断
  46.     TR0 = 1;        //启动T0
  47. }
  48. /* 串口配置函数,baud-通信波特率 */
  49. void ConfigUART(unsigned int baud)
  50. {
  51.     SCON  = 0x50;  //配置串口为模式1
  52.     TMOD &= 0x0F;  //清零T1的控制位
  53.     TMOD |= 0x20;  //配置T1为模式2
  54.     TH1 = 256 - (11059200/12/32)/baud;  //计算T1重载值
  55.     TL1 = TH1;     //初值等于重载值
  56.     ET1 = 0;       //禁止T1中断
  57.     ES  = 1;       //使能串口中断
  58.     TR1 = 1;       //启动T1
  59. }
  60. /* LED动态扫描刷新函数,需在定时中断中调用 */
  61. void LedScan()
  62. {
  63.     static unsigned char i = 0;  //动态扫描索引
  64.    
  65.     P0 = 0xFF;               //关闭所有段选位,显示消隐
  66.     P1 = (P1 & 0xF8) | i;  //位选索引值赋值到P1口低3位
  67.     P0 = LedBuff[ i];        //缓冲区中索引位置的数据送到P0口
  68.     if (i < 6)               //索引递增循环,遍历整个缓冲区
  69.         i++;
  70.     else
  71.         i = 0;
  72. }
  73. /* T0中断服务函数,完成LED扫描 */
  74. void InterruptTimer0() interrupt 1
  75. {
  76.     TH0 = T0RH;  //重新加载重载值
  77.     TL0 = T0RL;
  78.     LedScan();   //LED扫描显示
  79. }
  80. /* UART中断服务函数 */
  81. void InterruptUART() interrupt 4
  82. {
  83.     if (RI)  //接收到字节
  84.     {
  85.         RI = 0;  //手动清零接收中断标志位
  86.         RxdByte = SBUF;  //接收到的数据保存到接收字节变量中
  87.         SBUF = RxdByte;  //接收到的数据又直接发回,叫作-"echo",
  88.                             //用以提示用户输入的信息是否已正确接收
  89.     }
  90.     if (TI)  //字节发送完毕
  91.     {
  92.         TI = 0;  //手动清零发送中断标志位
  93.     }
  94. }
复制代码
打开STC下载软件右侧菜单栏的串口助手,在“接收缓冲区”和“发送缓冲区”各自有两个选项--文本格式和HEX格式,这是什么意思呢?
抛开汉字不谈,常用的字符包含了0~9的数字、A~Z/a~z的字母、还有各种标点符号等。那么在单片机系统里面怎么来表示它们呢?ASCII码(American Standard Code for Information Interchange,即美国信息互换标准代码)可以完成这个使命:在单片机中一个字节的数据可以有0~255共256个值,取其中的0~127共128个值赋予了它另外一层涵义,即让它们分别来代表一个常用字符,其具体的对应关系如表10-3所示。
表10-3  ASCII码字符表

表10-3.jpg
这样就在常用字符和字节数据之间建立了一一对应的关系,现在一个字节就既可以代表一个整数又可以代表一个字符了,至于什么时候代表整数,什么时候代表字符主要看编程者的实际意图。ASCII码在单片机系统中应用非常广泛。首先将发送缓冲区和接收缓冲区的“文本模式”取消勾选,在“HEX模式”勾选上。选择对应的串口号,波特率9600,校验位无校验,停止位1位,然后发送缓冲区的白框内,随意输入一个数字比如“31”,点击发送数据,就可以将0x31发送出去,并且能看到接收缓冲区显示也接收到了一个“31”,将操作步骤按照数字编排到串口助手截图中,如图10-4所示。
图10-4.png
  
10-4
串口助手发送接收数据
                                                   
将这个程序的串口助手发送模式保持“HEX模式”不变,接收模式改成“文本模式”,会观察到接收到一个数字1,也就是0x31对应的ASCII值,从表10-3可以查询到。如果将接收缓冲区保持“HEX模式”,而发送模式改为“文本模式”,在接收缓冲区会看到发送了0x33和0x31,也就是发送的时候31当做两个字符发送出去的,接收到的0x33和0x31分别是3和1的ASCII值。
回复

使用道具 举报

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

本版积分规则

小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

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