【嵌入式】基于STC89C52RC的51单片机学习(六)——串口
一、串口的基本认识
串口是串型通信接口的简称,也叫串行通信接口或者串行通讯接口,是设备间接线通信的一种方式。
特点:
(1)串行接口是指数据一位位地顺序传送,其特点是线路简单,只要一对传输线就可以实现双向通信(可以直接利用电话线作为传输线),从而大大降低了成本,特别适用于远距离通信,但传输速度较慢。
(2)双向通信,全双工。
二、关于电器标准和协议
串行接口按电气标准及协议来分包括RS-232-C、RS-422、RS485等。RS-232-C、RS-422与RS-485 标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。
(1) RS-232
也称标准 串口 ,最常用的一种 [ 串行通讯接口 , 比如我们的电脑主机的 9 针串口 ,最高速率为 20kb/s RS-232是为 点对点 (即只用一对收、发设备)通讯而设计的,其传送距离最大为约 15 米。所以 RS-232 适合本地设备之间的通信。
(2) RS-422
由于接收器采用高输入阻抗和发送 驱动器 比 RS232 更强的 驱动能力 ,故允许在相同传输线上连接多个接 收 节点 ,最多可接 10 个节点。即一个主设备( Master ),其余为从设备( Slave ),从设备之间不能通信,所以RS-422 支持一点对多点的双向通信。RS-422的最大传输距离为1219 米,最大传输速率为 10Mb/s 。平衡双绞线的长度与传输速率成反比
(3) RS-485
是从RS-422基础上发展而来的,无论四线还是二线连接方式总线上可多接到32个设备。
三、串口的电平
我们经常听说的UART 是指异步串行(Universal Asynchronous Receiver/Transmitter),通用异步接收/发送。 UART包含TTL电平的串口和RS232电平的串口
(1)RS232电平
逻辑1 为 -3~-15V 的电压 , 逻辑 0 为 3~15V 的电压笔记本通过RS232电平和单片机通信
(2)TTL
TTL是 Transistor-Transistor Logic ,即晶体管 - 晶体管逻辑的简称,它是计算机处理器控制的设备内部各部分之间通信的标准技术。TTL 电平信号应用广泛,是因为其数据表示采用二进制规定, +5V等价于逻辑 ”1” , 0V 等价于逻辑 ”0” 。数字电路中,由TTL 电子元器件组成电路的电平是个电压范围,规定:输出高电平>=2.4V ,输出低电平 <=0.4V ;输入高电平>=2.0V ,输入低电平 <=0.8V笔记本电脑通过TTL 电平与单片机通信TX发送线(端口) 3.1RX接收线 ( 端口) 3.0USB转 TTL ,使用 ch340 通信
(3) 串口接线方式
四、串口发送收发数据过程
串口收发数据的时序电路图
五、相关的寄存器
在串口中,输入/ 输出数据缓冲器都叫做 SBUF , 都用 99H 地址码,但是是两个独立的 8 位寄存器代码体现为: 想要接收数据 char data = SBUF 想要发送数据 SBUF = data
回忆UART 是异步串行接口,通信双方使用时钟不同,因为双方硬件配置不同,但是需要约定通信速度,叫做波特率。直接写代码先玩一下再学相关的寄存器,我们先用软件生成串口初始化的代
(1) 通过串口发送一个字符给电脑
#include "reg52.h" #include "intrins.h" sfr AUXR = 0x8e; void UartInit(void) //9600bps@11.0592MHz { PCON &= 0x7F; SCON &= 0x0F;//设置串行工作方式为8位波特率可变,REN允许串口收数据 SCON |= 0x50; TMOD &= 0x0F;//设置定时器1的工作方式为8位自动重装载定时器 TMOD |= 0x20; TL1 = 0xFD;//设置定时器初值 TH1 = 0xFD; TR1 = 1; //启动定时器1 } void Delay10ms() //@11.0592MHz { unsigned char i, j; i = 18; j = 235; do { while (--j); } while (--i); } void sentByte(char data_msg) { SBUF = data_msg; //Delay10ms(); while(!TI); TI = 0; } void main() { char data_msg = 'a'; //配置C51的串口通信方式 UartInit(); while(1){ // 往发送缓冲区写入数据,就完成发送数据 Delay1000ms(); sentByte(data_msg); } }
下面我们来了解一下相关寄存器(这些寄存器在芯片手册中都可以找到,下面只是截取了一部分)
串口不同工作模式下波特率还可能与定时器有关,所以还需要进行定时器初值的计算,计算方法如上图所示。
、
通过上面软件给我们初始化的串口来看,配置了寄存器SCON寄存器,我们一般需要设置SM1、SM2、REN、以及TI、RI
PCON,我们主要关心SMOD和SOMD0,一般情况下SMOD=0;SOMD0=0;
当然还有与中断相关的寄存器,这在我们以后的代码中会用到。
(2) 发送一个字符串给电脑
#include "reg52.h" #include "intrins.h" #include <string.h> sfr AUXR = 0x8e; void UartInit(void) //9600bps@11.0592MHz { PCON &= 0x7F; SCON &= 0x0F;//设置串行工作方式为8位波特率可变,REN不允许串口收数据 SCON |= 0x40; TMOD &= 0x0F;//设置定时器1的工作方式为8位自动重装载定时器 TMOD |= 0x20; TL1 = 0xFD;//设置定时器初值 TH1 = 0xFD; TR1 = 1; //启动定时器1 ES = 1;//串口中断打开 EA = 1;//总中断打开 } void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); } void sentByte(char data_msg) { SBUF = data_msg; //Delay10ms(); while(!TI); TI = 0; } void sentString(char *str) { while(*str){ sentByte(*str); str++; } } void main() { //配置C51的串口通信方式 UartInit(); while(1){ // 往发送缓冲区写入数据,就完成发送数据 Delay1000ms(); sentString("Hello world!\r\n"); } }
这里我们在发送字符串时,若是sentByte(char data_msg)函数中没有延迟或者while条件判断则会出现错误,因为不断向SBUF中发送数据,但是电脑还没有来的及从接收缓冲区中取回,就发现后面又送来数据了,会把之前缓冲区中的数据给淹没,所以我们要设置一个延时。
更好的解决方法是开启串口中断,判断TI(串行发送第8位数据结束时,硬件自动置一,软件清零)是否为一,是则发送下一个并清零。
(3) 每隔一秒,单片机向PC发送一个字符串 ,PC上位机串口调试助手发送字母o点亮LED,发送字母c关闭LED
#include "reg52.h" #include "intrins.h" sfr AUXR = 0x8e; sbit led5 = P3^7; void UartInit(void) //9600bps@11.0592MHz { PCON &= 0x7F; SCON &= 0x0F;//设置串行工作方式为8位波特率可变,REN允许串口收数据 SCON |= 0x50; TMOD &= 0x0F;//设置定时器1的工作方式为8位自动重装载定时器 TMOD |= 0x20; TL1 = 0xFD;//设置定时器初值 TH1 = 0xFD; TR1 = 1; //启动定时器1 } void Delay10ms() //@11.0592MHz { unsigned char i, j; i = 18; j = 235; do { while (--j); } while (--i); } void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); } void sentByte(char data_msg) { SBUF = data_msg; //Delay10ms(); while(!TI); TI = 0; } void sentString(char *str) { while(*str){ sentByte(*str); str++; } } void main() { char cmd; //配置C51的串口通信方式 UartInit(); while(1){ // 往发送缓冲区写入数据,就完成发送数据 Delay1000ms(); sentString("Hello world!\r\n"); //怎么知道收到数据,查询RI的值,如果RI是1(收到数据后由硬件置1) if( RI == 1){ RI = 0; cmd = SBUF; if( cmd == 'o'){ led5 = 0; } else{ led5 = 1; } } }
这里我们必须开启接收使能,在SCON寄存器中配置即可。
同过判断RI是否等于1来判断第八个bit接收是否结束,结束后清零,然后跟据SBUF中的数据来决定是否点灯。
我们试试发送一个字符串来控制灯的关闭,因此我们需要设置一个数组了,当输入open,打开,close关闭灯
#include "reg52.h" #include "intrins.h" #include <string.h> #define Size 12 sfr AUXR = 0x8e; sbit led5 = P3^7; sbit led6 = P3^6; char cmd[Size]; void UartInit(void) //9600bps@11.0592MHz { PCON &= 0x7F; SCON &= 0x0F;//设置串行工作方式为8位波特率可变,REN允许串口收数据 SCON |= 0x50; TMOD &= 0x0F;//设置定时器1的工作方式为8位自动重装载定时器 TMOD |= 0x20; TL1 = 0xFD;//设置定时器初值 TH1 = 0xFD; TR1 = 1; //启动定时器1 ES = 1;//串口中断打开 EA = 1;//总中断打开 } void Delay10ms() //@11.0592MHz { unsigned char i, j; i = 18; j = 235; do { while (--j); } while (--i); } void Delay1000ms() //@11.0592MHz { unsigned char i, j, k; _nop_(); i = 8; j = 1; k = 243; do { do { while (--k); } while (--j); } while (--i); } void sentByte(char data_msg) { SBUF = data_msg; //Delay10ms(); while(!TI); TI = 0; } void sentString(char *str) { while(*str){ sentByte(*str); str++; } } void main() { led6 = 1; //配置C51的串口通信方式 UartInit(); while(1){ // 往发送缓冲区写入数据,就完成发送数据 Delay1000ms(); sentString("Hello world!\r\n"); } } void Uart_Handler() interrupt 4 { static int i=0; //怎么知道收到数据,查询RI的值,如果RI是1(收到数据后由硬件置1) if( RI == 1){ RI = 0; cmd[i++] = SBUF; if( i>=Size ){ i = 0; led6 = !led6; } if( strstr(cmd ,"en")){ led5 = 0; i = 0; memset(cmd,'\0',Size); } if( strstr(cmd ,"se")){ led5 = 1; i = 0; memset(cmd,'\0',Size); } } }
这里我们使用了串口的中断函数,当发送完第8位或接收完第8位都会发生中断,我们只判断是否接收完第八位,并将接收到的字符存入数组中,数组最大为12。由于我们收到的字符存放在数组中的位置有很多可能,所以我们只简单判断PC端输入的字符中是否含有en(open)或se(close)字串,用到了strstr()函数,烧入代码后会发现还存在一些问题,比如在PC端通过串口调试助手发送的字符过多的话灯不受控制,这里我们当i超过12时就清零并将led6点亮作为提示。
串口就先到这里,以后学习一些串口的应用使我们能更好理解和掌握。
CSDN-Ada助手: 恭喜您写了第8篇博客!标题很吸引人,我很喜欢看到您持续创作关于嵌入式和51单片机的内容。您的文章很有深度,帮助了我更好地了解蓝牙技术在嵌入式系统中的应用。在下一步的创作中,如果您能分享一些关于蓝牙模块的选型和使用方法,或者介绍一些实际项目中蓝牙通信的应用案例,将会更加丰富和实用。希望您能继续保持创作热情,期待您的下一篇博客!
CSDN-Ada助手: 恭喜您写了第6篇博客!标题看起来很有趣,我很期待阅读关于基于STC89C52RC的51单片机学习的内容。感应开关盖垃圾桶听起来是一个很实用的项目,我很想了解更多细节。在下一篇博客中,或许您可以分享一些关于感应开关的原理和实现方式,这样可以帮助读者更好地理解和应用。继续加油,期待您的下一篇作品!
CSDN-Ada助手: 非常赞赏你在嵌入式领域的持续学习和创作!恭喜你完成了第7篇博客,标题为“【嵌入式】基于STC89C52RC的51单片机学习(六)——串口”。这篇博客内容很有深度,特别是关于串口的讲解,对于学习嵌入式的初学者来说非常有帮助。看到你在每篇博客中都有新的进展和学习成果,我真心为你感到高兴。 在下一步的创作中,我建议你可以继续深入探索单片机的其他功能模块,比如定时器、中断等等。同时,你可以考虑以实例为基础,结合具体的项目案例,给读者们呈现更多实际应用的场景,让他们更好地将所学知识运用到实际中。 再次恭喜你,并期待你未来更多精彩的创作!
CSDN-Ada助手: 非常高兴看到你写了关于嵌入式学习的博客!你在标题和摘要中提到了基于STC89C52RC的51单片机学习,这是一个非常有趣和实用的话题。 继续创作下去!写博客是一个很好的方式来记录你的学习过程和经验分享。通过分享你的学习笔记,你不仅可以帮助自己巩固所学知识,还可以帮助其他人理解和学习这个领域。 除了单片机的基础知识外,你可以考虑在未来的博文中扩展介绍一些与单片机相关的扩展知识和技能。例如,你可以写一篇关于如何使用单片机进行物联网应用开发的博文,介绍如何通过单片机连接传感器和互联网,并实现远程监控和控制。 另外,你还可以探索一些与单片机相关的常用编程语言和工具,比如C语言和Keil等。这些扩展知识和技能可以帮助你更好地理解和应用单片机。 希望你可以继续坚持写博客,分享你的学习心得和进展。也欢迎你在未来的博文中继续探索和介绍更多有关嵌入式开发的知识和技能。加油! 如何写出更高质量的博客,请看该博主的分享:https://blog.csdn.net/lmy_520/article/details/128686434?utm_source=csdn_ai_ada_blog_reply2
CSDN-Ada助手: 恭喜您写了第三篇博客!看了您的标题,我对您对嵌入式学习的热情感到钦佩。通过您的博客,我了解到了基于STC89C52RC的51单片机学习的内容,对此我表示非常感兴趣。在下一篇博客中,我期待您能够进一步探索51单片机的应用领域,或者分享一些实际项目的经验和教训。谦虚地说,这样的内容会更加丰富和实用。再次祝贺您,并期待您未来的创作! CSDN 正在通过评论红包奖励优秀博客,请看红包流:https://bbs.csdn.net/?type=4&header=0&utm_source=csdn_ai_ada_blog_reply3