单片机C语言的串口通信协议资料和代码概述

现实生活中, 我们总是要与人打交道,互通有无 。单片机也一样,需要跟各种设备交互 。例如汽车的显示仪表需要知道汽车的转速及电动机的运行参数,那么显示仪表就需要从汽车的底层控制器取得数据 。而这个数据的获得过程就是一个通信过程 。类似的例子还有控制器通常是单片机或者PLC变频器的通信 。通信的双方需要遵守一套既定的规则也称为协议,这就好比我们人之间的对话,需要在双方都遵守一套语言语法规则才有可能达成对话 。
通信协议又分为硬件层协议和软件层协议 。硬件层协议主要规范了物理上的连线,传输电平信号及传输的秩序等硬件性质的内容 。常用的硬件协议有串口,IIC, SPIRS485, CANUSB 。软件层协议则更侧重上层应用的规范,比如modbus协议 。
好了,那这里我们就着重介绍51单片机的串口通信协议,以下简称串口 。串口的6个特征如下 。
(1)、物理上的连线至少3根,分别是Tx数据发送线,Rx数据接收线,GND共用地线 。
(2)、0与1的约定 。RS232电平,约定﹣5V至﹣25V之间的电压信号为1,﹢5V至﹢25V之间的电压信号为0。TTL电平,约定5V的电压信号为1,0V电压信号为0。CMOS电平,约定3.3V的电压信号为1,0V电压信号为0。其中,CMOS电平一般用于ARM芯片中 。
(3)、发送秩序 。低位先发 。
(4)、波特率 。收发双方共同约定的一个数据位(0或1)在数据传输线上维持的时间 。也可理解为每秒可以传输的位数 。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s 。
(5)、通信的起始信号 。发送方在没有发送数据时,应该将Tx置1。当需发送时,先将Tx置0,并且保持1位的时间 。接受方不断地侦测Rx,如果发现Rx常时间变高后,突然被拉低(置为0),则视为发送方将要发送数据,迅速启动自己的定时器,从而保证了收发双方定时器同步定时 。
(6)、停止信号 。发送方发送完最后一个有效位时,必须再将Tx保持1位的时间,即为停止位 。
【单片机C语言的串口通信协议资料和代码概述】

单片机C语言的串口通信协议资料和代码概述

文章插图
其中D0-D7是一个字节的8个位 。9位模式只是多了一个位TB8,这个TB8的作用是奇偶校验或多机通信 。奇偶校验原理这不加分析 。多机通信时比如主机只发送数据给网络中的一台地址为0x02的设备,这时候先让TB8为1,前面的D0-D7则为地址即0x02,之后再让TB8为0,前面的D0-D7则为数据了 。
上面设置了片上串口的模式,另外还要设置串口的波特率 。
片上串口的波特率等于定时器1工作在方式2时溢出率的32分频 。如果要定时器1工作在方式2,那么TMOD=0x20 。另外要保证为32分频,我们还必须设置计数器初值 。设晶振为11.0592Mhz,则定时器的计数脉冲为F=f/12,则定时器每计一个脉冲的时间为T=12/f 。又令计数器的起点为x,则溢出一次要计的脉冲数为(256-x) 。所以在计数起点为x时,溢出一次的时间为t=12/f*(256-x) 。则对应的溢出率为1/t=f/(12*(256-x)) 。对应的波特率就为b=f/(384*(256-x)) 。
x=256-f/(384*b)
其中f为晶振频率,b为希望的波特率,x为定时器的计数起点TH1的值 。
例如当晶振为11.0592M,希望波特率为9600bit/s,则TH1=253 。题外话,我们同样可以演算出在其他常用波特率情况下,TH1始终为一个整数 。这里也就解释了为什么51里面选用了11.0592M的晶振而不是12M,这样就保证了串口的时序更加准确,虽然牺牲了定时器的准确度 。
实验二,片外串口发送一个字节 。
好了现在开始我们的实验之旅 。直接看代码吧 。
[cpp] view plain copy# include “reg51.h”#define u16 unsigned int# define u8 unsigned charvoid delay(u16 x){while (x--);}void Uart_Init() //串口初始化 { SCON = 0x50; //8位异步模式 TMOD |= 0x20; //定时器1工作方式2 TH1 = 253; //9600bit/s TR1 = 1;}void Send_Byte(u8 dat){ SBUF = dat; //启动发送,只需要把发送内容给SBUF这个寄存器 while (TI == 0); //等待发送完成,因为TI为1时表示在发送停止位 TI = 0;}void main(){ Uart_Init();while (1) { Send_Byte(‘m’); delay(60000); }}
实验二较之实验一,代码减少了很多,而且不用考虑繁琐的位发送时序 。只需要明白各个寄存器SCON,TMOD,TCON,SBUF的用法 。TI是SCON中的第一位,为发送中断请求标志位 。在本方式中,在停止位开始发送时由内部硬件置位,响应中断后TI必须又软件清零 。
实验三、片上串口发送一个字符
上面介绍了如何发送一个字节,那如何发送一个字符串甚至文本呢?这里我们首先介绍下字符串的概念 。
字符串:从存储器的某个地址开始,连续存放多个字符的ASCII码,并且在最后一个字符的后面存放一个0,这段连续的内存空间就叫字符串,最后的0叫字符串的结束符 。注意这里的0和加单引号的0不是一个概念,加单引号的0是指0的ASCII码 。
数组与字符串的关系:字符串是数组的一种特殊情况,数组在特定条件下可当做字符串用 。C语言用双引号描述一个字符串,如“abcd” 。
下面我们通过一个实验来展示如何发送字符串 。我们实验的目标是打印字符串“Hello World ! 第一!”到打印机 。直接上代码 。
[cpp] view plain copy#include “reg51.h”#define u16 unsigned int #define u8 unsigned char void delay(u16 x){ while(x--); } void Uart_Init() //串口初始化 { SCON=0x50; //8位异步模式 TMOD|=0x20; //定时器1工作方式2 TH1=253;//9600bit/s TR1=1; } void Send_Byte(u8 dat) //串口发送一个字节 { SBUF=dat; //启动发送,只需要把发送内容给SBUF这个寄存器 while(TI==0); //等待发送完成,因为TI为1时表示在发送停止位 TI=0; }void Send_String(u8 *str) //发送一个字符串 *str为字符串第一个字符的地址 { abc: //标号 if(*str != 0) { Send_Byte(*str); str++; goto abc; } } void main(){ Uart_Init(); while(1) { Send_String(“Hello World! 第一!”); Send_Byte(10); delay(60000); delay(60000); } }
责任编辑 LK

    推荐阅读