51单片机学习笔记(清翔版)(19)——串口通信
学好了中断再学串口通信
今天这课内容是关于数据传输的,在工业控制和商业控制是很重要的。数据传输包含串口、并行通信。
这里说的计算机都可以理解为单片机,因为工作方式是一样的。
单片机通信是指单片机之间,单片机与外部设备之间(温度传感器,这样单片机才能知道温度传感器采集回来的温度)。
控制数码管就是并行通信,给P0直接赋位选或段选,P0八条线,一次传送8条数据,例如P0=0x01,一次性8位数据都传送过去。
询问和应答就是说的查询工作方式。询问有没有准备好接收数据,将准备好了通过应答这条线传送过去。然后把8位数据发过去。
从这节课开始我们学的数据传输多使用串行通信,前面的8*8点阵也是串行通信。
缺点:数据传送的控制比并行复杂,意思是,传数据不止一条数据线,还需要控制线,控制传数据的节拍是多长,就是一位一位的传数据,那么要告诉每传一位要间隔多长时间再传下一位,例如前面的8*8点阵就有一个时钟线。
右侧就是单片机的串行通信,有一个发送引脚,一个接收引脚,发送引脚对应另一个的接受引脚,通过这两条线数据交换。下面是相互之间要共地。
下方的是异步通信的图。发送1个字节的数据,前面有起始位,后面有停止位。读到起始位就知道什么时候来数据了,读到停止位就知道这帧数据完了,是由硬件设备判断的。每一帧数据之间的间隔是任意的。
同步通信数据是源源不断的发的,有一个控制线,即时钟线,给一个时钟,发一个数据,只要时钟不停,发数据就不能停。
这些理论就是一个了解,单片机串口通信内部硬件有这个功能,我们只需要配置内部的寄存器就可以了,如果要发送数据,只要给寄存器赋值就可以了,如果接收,我们读这个数据就能读到,我们不用关心帧与帧之间和位与位之间的间隔,如果不自带这个功能,我们用软件模拟,我们就需要清除的知道这些理论知识。例如I²C就是软件模拟的。
LSB是低位,MSB是高位。
校验位校验前面数据是否在发送的过程受到干扰或别的原因产生错误。
8*8的点阵,使用的SPI通信,就是外同步的方式,发送机是单片机,接收机是点阵内部的595,发送机有一个时钟引脚,控制接收机,直接传到595的时钟引脚上,每来一个时钟就发一个数据,收到时钟就接一个数据,源源不断的时钟就源源不断的发,间隔由时钟控制的,可以看出同步通信比异步通信效率高很多,因为时钟是源源不断给的,数据是源源不断过去的,异步通信是一帧一帧发,中间夹杂着起始位,停止位,校验位,这三位是没用的东西,也不能说没用,在异步通信有用,但是在同步通信可以省掉,所以效率就高了,而且同步通信是连续不断的发,所以更快。
自同步,双方约定好时钟是相同的,来一个时钟发一个数据,另一边接收一个数据,外同步是使用最多的,开发板上还有一些,像I²C设备的数模转换芯片,E²PROM芯片,都是用的同步通信的外同步。
我们的串口是异步通信,后面的I²C和SPI是同步。
我们单片机串口是全双工的。
校验简单了解即可,就不多说了。因为我们单片机内部自带串口,所以我们不需要进行奇偶校验,只需要配置寄存器。
这是指异步通信的传输速率。每帧数据的间隔是任意的,但是每位是固定的,这个时间是由传输速率的比特率决定的。
现在实际我们都没用这两种接口了。
25针的看的是比较少的了,很早的工业设备上还能看到,如90年出厂的,又分为公头和母头,或阳头和阴头。阳头是带针脚的,母头就是带孔的。
现在还能看到9针式的。
每个引脚的功能及怎么排都是统一的,不然是无法通信的,所以才需要这标准。
我们现在都不是用这两种,板子上用的微型usb接口,内部只有4个脚,只有TXD、RXD,电源和地。
工业上还有9脚的,家用电器上很少有了
非平衡屏蔽双绞线,是一种抗干扰的线,是把两条绞在一块的。网线中多使用。信号传输的时候有噪声和磁场的干扰,使用它,有屏蔽层,尽可能的去屏蔽这个干扰。
电容允许值就是前面说的电气特性。
线上会形成分布电容(寄生电容)、还有电感,线越长,分布电容越大,电容电感会形成LC低通滤波电路,信号传输的时候就会受到这电路影响而衰减。这电容不是人为加上的,是布线方式就自动产生的。这电容在三极管和场效应管都是存在的,直接影响了开通与关断的速度。电容大小与两线间的距离成反比,与导体面积成正比。
看到实际中的电容,容值越大,体积就越大。如电解电容,贴片瓷片电容(内部有许多金属片,之间是隔开的,金属片是绝缘的,然后就形成了电容,金属片面积越大,电容越大,间隔越短,电容也越大)。
99H,H是HEX即16进制,这是地址,也不需要关心,都在reg52.h中定义了,直接写SBUF就可以。
我们通过程序,把要发送的值赋值给发送SBUF,然后它通过门电路、发送控制器、波特率产生器,控制每一位发的速度(设置比特率),然后通过TXD(P3.1接口)发送出去。
通过RXD接收到数据,经过移位寄存器,发送到接收SBUF中。如果我们要读取外部发来的数据,直接读取SBUF就可以。当然也要设置比特率,要和发送的相同。
以上这点是对内部带有串口功能的硬件而言的。很简单。
比特率的发生器使用T1做的,计算T1初值就是计算波特率。
SCON是串口工作方式选择。
串口也可以有查询方式和中断方式,查询方式是通过标志位控制的,根据两个标志位判断是否发送和接收完成,完成会被置1。我们直接用中断方式。
中断肯定要用到IE寄存器。
SCON是可位寻址的。
SM0和SM1是工作方式的设置。SM2是多机通信的。REN是接收控制。TB8和RB8是第九位。TI和RI是发送和接收中断标志位,发送完和接收完会被置1。
这两个才是最主要的。
方式0的波特率时系统时钟的12分频。SMOD是波特率倍增位。
方式1使我们用的最多的,方式2、3是多机通信用的。
REN位0时,发送过来的数据是不会存到SBUF的。所以接收要软件置1。
一定要记得软件把TI和RI清零,否则会一直进入中断。
什么时候会用到SMOD呢?外部时钟是11.0592,如果要产生更高的波特率,受时钟影响,产生波特率最大值是有限度的,那么我们就可以用SMOD加倍。
fosc是外部晶振,11.0592.
T1的初值可以自己算,也可以用软件算。或者直接记住T1=0xFD。
若使用晶振12MHz,误差很大,导致传输过程中数据可能出错,例如我发1,可能接收到3。
虽然机器周期1.085,再做软件延时的时候,感觉没有12M好,但是软件延时不重要,有一点误差也没事,但是传数据就很重要。
接下来开始编程
照着这个来就好。
电脑(上位机)发送值,单片机收到的值用数码管以十进制形式显示。
1 #include <reg52.h> 2 3 #define uchar unsigned char 4 #define uint unsigned int 5 6 sbit we = P2^7; //位定义数码管位选锁存器接口 7 sbit du = P2^6; //位定义数码管位选锁存器接口 8 9 uchar num; 10 //数码管0~9段选表 11 uchar code leddata[]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; 12 //数码管1~3位选表 13 uchar code DPY[]={0xfe, 0xfd, 0xfb}; 14 void display(uchar i) 15 { 16 static uchar wei; 17 P0=0xff; 18 we=1; 19 P0=DPY[wei]; 20 we=0; 21 switch(wei) 22 { 23 case 0:du=1;P0=leddata[i/100];du=0;break; 24 case 1:du=1;P0=leddata[i%100/10];du=0;break; 25 case 2:du=1;P0=leddata[i%10];du=0;break; 26 } 27 wei++; 28 if(wei==3) 29 wei=0; 30 } 31 //中断服务特殊功能寄存器配置 32 void init() 33 { 34 35 TMOD |= 0x01; //定时器16为计数工作模式 36 TH0 =0xED; 37 TL0 =0xFF; //5ms 38 ET0 = 1; //开定时器0中断 39 TR0 = 1;//启动定时器0 40 EA = 1; //开总中断 41 } 42 void UART_init() 43 { 44 EA=1;//开总中断 45 ES=1;//开串口中断 46 SM0=0;SM1=1;//串口工作方式1 47 REN=1;//串口接受允许 48 TR1=1; //打开定时器1 49 TMOD|=0x20;//定时器1,工作模式2,八位自动重装 50 TH1=0xFD; 51 TL1=0xFD;//253,波特率(比特率)9600 52 } 53 void main() 54 { 55 init();//初始化定时器0 56 UART_init();//串口初始化 57 while(1) 58 { 59 60 } 61 } 62 void UART() interrupt 4 63 { 64 if(RI) 65 { 66 num=SBUF; 67 RI=0; 68 } 69 70 } 71 //定时器0中断服务程序 做数码管动态扫描,不用软件延时 72 void timer0() interrupt 1 73 { 74 TH0 =0xED; 75 TL0 =0xFF; //5ms 模式1非自动重装需要手动重装 76 display(num); 77 }
发16进制的数字。刚打开串口时,会接收到别的数,没关系,下载也是通过串口的。
下面把接收到的数据+1再发送出去。
1 #include <reg52.h> 2 3 #define uchar unsigned char 4 #define uint unsigned int 5 6 sbit we = P2^7; //位定义数码管位选锁存器接口 7 sbit du = P2^6; //位定义数码管位选锁存器接口 8 9 uchar num; 10 //数码管0~9段选表 11 uchar code leddata[]={0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F}; 12 //数码管1~3位选表 13 uchar code DPY[]={0xfe, 0xfd, 0xfb}; 14 void display(uchar i) 15 { 16 static uchar wei; 17 P0=0xff; 18 we=1; 19 P0=DPY[wei]; 20 we=0; 21 switch(wei) 22 { 23 case 0:du=1;P0=leddata[i/100];du=0;break; 24 case 1:du=1;P0=leddata[i%100/10];du=0;break; 25 case 2:du=1;P0=leddata[i%10];du=0;break; 26 } 27 wei++; 28 if(wei==3) 29 wei=0; 30 } 31 //中断服务特殊功能寄存器配置 32 void init() 33 { 34 35 TMOD |= 0x01; //定时器16为计数工作模式 36 TH0 =0xED; 37 TL0 =0xFF; //5ms 38 ET0 = 1; //开定时器0中断 39 TR0 = 1;//启动定时器0 40 EA = 1; //开总中断 41 } 42 void UART_init() 43 { 44 EA=1;//开总中断 45 ES=1;//开串口中断 46 SM0=0;SM1=1;//串口工作方式1 47 REN=1;//串口接受允许 48 TR1=1; //打开定时器1 49 TMOD|=0x20;//定时器1,工作模式2,八位自动重装 50 TH1=0xFD; 51 TL1=0xFD;//253,波特率(比特率)9600 52 } 53 void main() 54 { 55 init();//初始化定时器0 56 UART_init();//串口初始化 57 while(1) 58 { 59 60 } 61 } 62 void UART() interrupt 4 63 { 64 uchar temp; 65 if(RI) 66 { 67 num=SBUF;//读SBUF,读出串口接收到的数据 68 RI=0;//软件清零接受标志位 69 temp=num; 70 SBUF=++temp;//写SBUF,把要发送的数据送给发送缓冲器,然后单片机硬件会自动把数据发到TXD引脚 71 } 72 if(TI)//判断是否发送完成 73 TI=0;//清零发送完成标志位 74 } 75 //定时器0中断服务程序 做数码管动态扫描,不用软件延时 76 void timer0() interrupt 1 77 { 78 TH0 =0xED; 79 TL0 =0xFF; //5ms 模式1非自动重装需要手动重装 80 display(num); 81 }
上方式计算机接收端。
如果我们选择文本格式发送,发送1后,数码管会显示49,因为发文本格式是以asc码格式发送的。
看到图中1是49。发送1,文本格式的1变为数字是asc的49,那么数码管接收到的就是49,而发给单片机,又以文本格式显示,那么又变回了文本格式,49+1是50,也就是2。文本发送9,那么单片机显示57,计算机接收到文本格式是冒号。
接下来如何通过串口发送汉字?
发送汉字可以使用标准库函数,可以使用printf()和puts(),需要#include<stdio.h>
这个串口初始化没有用到中断,我们用的查询方式。
puts()上面为什么TI=1,因为我们给SBUF送值后,串口会自动发送,发送完,硬件置1,我们为什么用软件置1?
puts()是调用了putchar()函数,这个函数发送前先进行while(!TI)判断,如果TI没置1,那么会一直在这里等待,那么要发送的字符就送不到SBUF里,所以就一直不会发送,所以要是用puts()和printf()都应该先把TI置1。
我们也可以在帮助中看到。
然后选择uVision help
putchar()函数就是把字符c发送到串口。
1 #include <reg52.h> 2 #include<stdio.h> 3 4 #define uchar unsigned char 5 #define uint unsigned int 6 7 void UART_init() 8 { 9 SM0=0;SM1=1;//串口工作方式1 10 TR1=1; //打开定时器1 11 TMOD|=0x20;//定时器1,工作模式2,八位自动重装 12 TH1=0xFD; 13 TL1=0xFD;//253,波特率(比特率)9600 14 } 15 void Delay1ms() //@12.000MHz 16 { 17 unsigned char i, j; 18 19 i = 12; 20 j = 169; 21 do 22 { 23 while (--j); 24 } while (--i); 25 } 26 void delay(uchar num) 27 { 28 while(num--) 29 { 30 Delay1ms(); 31 } 32 } 33 void main() 34 { 35 UART_init();//串口初始化 36 while(1) 37 { 38 TI=1; 39 puts("大家好!欢迎学习单片机"); 40 while(!TI); 41 TI=0; 42 delay(1000); 43 } 44 }
接收的时候是以文本接收
1s发一次,且换行,而printf不换行。