串口基本认知
- 是设备间接线通信的一种方式
- 数据一位一位地顺序传送
- 双向通信,全双工
- 传送速度相对较慢
串口关于电器标准和协议
RS-232
也称为标准串口,最常用的一种串行通讯接口,比如我们电脑主机的9针串口,最高速率是20kb/s,RS-232是为点对点设计的,最长传输距离为15m,适合本地设备之间的通信。
支持点对点的双向通信,最大传输距离为1219米,最大传输速率为10Mb/s。
是从RS-422基础上发展而来的,无论四线还是二线连接方式总线上可多接32个设备。
串口的电平
怎么理解异步通信?
由于单片机和电脑,不同设备的频率和配置不同,因此CPU运行速度不同,所以各自使用各自的时钟来相互配合。
RS232电平和TTL电平有什么区别?
对于RS232,逻辑1是 -3 ~ -15V的电压,逻辑0是 3 ~ 15V 的电压。
而TTL电平是Transistor-Transistor Logic,即晶体管-晶体管逻辑的简称,TTL电平信号应用广泛,是因为其数据表示采用二进制规定,+5v等价于逻辑1,0v等价于逻辑0。 在数字电路中,由TTL电子元器件组成电路的电平是个电压范围,规定:
输出高电平>=2.4V,输出低电平<=0.4V;
输入高电平>=2.0V,输入低电平<=0.8V;
串口通信
- RXD:数据输入引脚,数据接受;STC89系列对应P3.0口
- TXD:数据发送引脚,数据发送;STC89系列对应P3.1口
- 接线方式
笔记本电脑通过TTL电平和单片机通讯:TX发送线(端口);RX接受线(端口),并使用CH340进行USB转TTL。
串口编程要素
串口的结构如下图所示,
通过定时器T1计时,由T1产生溢出率,作为波特率发生器。
-
两个数据缓冲器,SBUF。分别是发送数据的发送寄存器,读取数据的接收寄存器。
串口写入时,写入的是发送寄存器,即数据向发送寄存器SBUF写入。
向串口读时,读出的是接收寄存器,即数据由接收寄存器SBUF读出。 -
定时器1产生波特率, 串口一般使用定时器1,模式2,八位自动重装模式,来产生溢出率,从而产生波特率。而且在配置定时器相关的寄存器时不用配置定时器中断,只是使用定时器1来产生波特率的功能。
-
移位寄存器,在接受控制器的控制下,将输入的数据逐位移入接收SBUF。
-
串行控制寄存器SCON,SCON的功能是控制串行通信口的工作方式以及工作的状态。
输入 / 输出数据缓冲器都叫做 SBUF , 都用 99H 地址码,但是是两个独立的 8 位寄存器代码体现为:
- 想要接收数据 char data = SBUF(将缓存器的值读取到data,即单片机读取SBUF的数据存入到data中)
- 想要发送数据 SBUF = data (将data的值写入缓存器,即单片机发送data到SBUF中)
再次回忆UART的概念,”由于单片机和电脑,不同设备的频率和配置不同,因此CPU运行速度不同,所以各自使用各自的时钟来相互配合。” 所以双方需要约定通讯速度,这个速度就叫做波特率,对于电脑来说,别人做好了软件,鼠标点点就能配置好,而单片机的波特率则需要自己写代码。
对于电脑来说,需要点击配置的东西是:
所以在代码中,我们也需要配置波特率,校验位,停止位。那在代码中的这些如何进行设置?答案还是,和设置定时器模式一样,要操控寄存器。 而且,也和定时器一样,代码也可以从stc-isp助手里面得到: 注意!这里的波特率设置应该和之前电脑设置的波特率
串行通信口的控制寄存器
1 串行控制寄存器SCON
SCON寄存器用来控制串行通信的工作方式及反映串行口的工作状态。SCON的地址是98H,可位寻址。如下图所示。
通过配置SCON寄存器来对串行通信的工作模式进行控制。
说说几个常用的位
- SM0和SM1,组合确定串行口的模式,不同的模式下波特率不同,功能也不同,下面会详细介绍不同模式下的波特率该如何计算。
- REN,允许/禁止串行接收控制位。 由软件置位REN,即REN=1为允许串行接收状态,可启动串行口接收数据。
- TI,发送中断请求标志位。主机通过串行口发送数据发送完成后,TI会置1,向串口申请中断,如果TI为1 就表示SUBF的数据已经发送完成了,要手动将TI位清零。(即SBUF=Data)
- RI,接收中断请求标志位。从机接收到主机通过串行口接收到的数据后,RI会置1,即会串口申请中断,如果RI为1 就表示数据已经通过SBUF读取完成了,要手动将RI位清零。(即Data=SBUF)
2 电源控制寄存器PCON
这里我们只需要知道SMOD是啥就可以了。
- SMOD位,就是波特率是否加倍,若SMOD=0,波特率不变。SMOD=1,波特率加倍。SMOD默认为0。
3 配置寄存器
配置SCON寄存器
上图就是SCON寄存器的位图,下面以串口通信的模式1来举例。
模式1,那么SM0和SM1就要配置成 0 1,注意这里SM0和SM1决定串口通信的模式
REN,可置1,置1就是允许串行通信口接收数据,当不需要接收数据的时候可以置0。
后面的几位直接不看,一般用不到
TI和RI,也默认为0,当接收数据或发送数据的时候,会由硬件置1,继而触发中断,这时要由软件置1。
这样SCON寄存器就配置好了。
这样我的SCON配置如下
SCON=0x40; //方式1 REN置0 禁止数据接收的控制位 0100 0000
SCON=0x50; //方式1 REN置1 允许数据接收的控制位 0101 0000
配置PCON寄存器
我们只需要明确一点,我的波特率是否需要加倍,如果加倍,那么PCON的第一位SMOD就要置
这样我的PCON就配置好了,如加倍(不倍速可以不写)
PCON &= 0x8F; //波特率加倍
1.代码实现单片机向电脑发送一个字符
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错sbit led1 = P3^7;//根据原理图(电路图),设备变量led1指向P3组IO口的第7口
sbit led2 = P3^6;//根据原理图(电路图),设备变量led2指向P3组IO口的第6口sfr AUXR = 0x8E;//配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册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 UartInit() //9600bps@11.0592MHz
{PCON &= 0x7F; //波特率不倍速SCON = 0x50; //8位数据,可变波特率 0101 0000AUXR &= 0xBF; //定时器1时钟12T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器1模式TMOD |= 0x20; //设置定时器1为8位自动重装方式TL1 = 0xFD; //设置定时初始值TH1 = 0xFD; //设置定时重载值ET1 = 0; //禁止定时器1中断TR1 = 1; //启动定时器1
}void main()
{char data_msg = 'a';//配置C51串口通信发送方式UartInit();while(1){Delay1000ms();//往发送缓冲器写入数据,就完成数据的发送SBUF = data_msg;} }
实现效果1
2.代码实现单片机向电脑发送字符串
在SCON寄存器中,BIT1为TI:即,每发送一个BYTE(一个char变量的大小),TI就会被硬件置1,根据这个,我们可以编写使串口打印字符串:
#include "reg52.h"sbit led1 = P3^7;//根据原理图(电路图),设备变量led1指向P3组IO口的第7口
sbit led2 = P3^6;//根据原理图(电路图),设备变量led2指向P3组IO口的第6口/*
void UartInit(void) //9600bps@11.0592MHz
{PCON &= 0x7F; //波特率不倍速 SMOD在PCON里SCON = 0x50; //8位数据,可变波特率AUXR &= 0xBF; //定时器时钟12T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xFD; //设置定时初始值TH1 = 0xFD; //设置定时重载值ET1 = 0; //禁止定时器中断TR1 = 1; //定时器1开始计时
}
*/void Delay1000ms() //@11.0592MHz
{unsigned char i, j, k;i = 8;j = 1;k = 243;do{do{while (--k);} while (--j);} while (--i);
}void UartInit() //9600bps@11.0592MHz
{SCON = 0x40; //配置串口工作方式1,REN不使能接收//配置定时器1,工作方式为8位自动重载TMOD &= 0x0f;//TMOD |= 0x20;TH1 = 0xFD; TL1 = 0xFD; //9600波特率的初值TR1 = 1;//启动定时器}void Send_byte(char data_msg)
{SBUF = data_msg;//往发送缓冲器里写入数据,就完成了数据的发送while(!TI);//在请求中断时,TI= 1,既!TI=0,等待数据的发送完成;响应中断结束后TI = 0,既!TI = 1,将TI清零TI = 0;}void Send_string(char* str)
{while(*str != '\0'){Send_byte(*str);str++;}
}void main()
{char data_msg = 'a';//配置C51串口通信发送方式UartInit();while(1){Delay1000ms();//往发送缓冲器写入数据,就完成数据的发送Send_string("wdnmd\t");} }
效果实现
3.代码实现使用串口点亮单片机LED
在SCON寄存器中,BIT0为RI:即收到数据后,RI位会由硬件置1
我们需要如何接收数据呢?
设置阻塞检测接收的数据?此方法会影响程序的进程,一般不使用
还是中断?
查询手册可知,使用中断的方式接收指令可以使得响应没有延时。
所以,打开UART中断需要打开ES和EA!
下方代码块为中断服务子函数模块
// 串口中断函数模板 void Uart_Routine() interrupt 4 {if(RI==1) {RI=0;}if(TI==1){TI=0;} }
#include "reg52.h"sbit D5 = P3^7;//根据原理图(电路图),设备变量led1指向P3组IO口的第7口
sbit led2 = P3^6;//根据原理图(电路图),设备变量led2指向P3组IO口的第6口/*
void UartInit(void) //9600bps@11.0592MHz
{PCON &= 0x7F; //波特率不倍速 SMOD在PCON里SCON = 0x50; //8位数据,可变波特率AUXR &= 0xBF; //定时器时钟12T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xFD; //设置定时初始值TH1 = 0xFD; //设置定时重载值ET1 = 0; //禁止定时器中断TR1 = 1; //定时器1开始计时
}
*/void Delay1000ms() //@11.0592MHz
{unsigned char i, j, k;i = 8;j = 1;k = 243;do{do{while (--k);} while (--j);} while (--i);
}void UartInit() //9600bps@11.0592MHz
{SCON = 0x50; //配置串口工作方式1,REN使能接收//配置定时器1,工作方式为8位自动重载TMOD &= 0x0f;//TMOD |= 0x20;TH1 = 0xFD; TL1 = 0xFD; //9600波特率的初值TR1 = 1;//启动定时器}void Send_byte(char data_msg)
{SBUF = data_msg;while(!TI);//在请求中断时,TI= 1,既!TI=0,等待数据的发送完成;响应中断结束后TI = 0,既!TI = 1,将TI清零TI = 0;}void Send_string(char* str)
{while(*str != '\0'){Send_byte(*str);str++;}
}void main()
{char data_msg = 'a';char cmd;D5 = 1;//配置C51串口通信发送方式UartInit();while(1){Delay1000ms();//往发送缓冲器写入数据,就完成数据的发送Send_string("wdnmd\t");//怎么知道收到数据,查询RI的值,如果RT是1(收到数据后由硬件置一)//非中断触发/*if(RI == 1) //RI = 1时数据接收完毕{RI = 0;//软件复位cmd = SBUF; //从SBUF里面读发来的数据if(cmd == 'o'){D5 = 0;//点亮D5}if(cmd == 'c'){D5 = 1;//熄灭D5}}*/} }//中断触发
void Uart_Handler() interrupt 4
{if(RI)//中断处理函数中,对于接收中断的响应{RI = 0;//清除接收中断标志位cmd = SBUF;if(cmd == '1'){D5 = 0;//点亮D5}if(cmd == '0'){D5 = 1;//熄灭D5}}if(TI){}
}
这次试验就很好的体现了串口的"全双工“,即可以不断同时的发送和接收数据。
同时注意,在”发送缓冲区”中选择文本模式后要注意,在文本模式下输入的任何东西都会被认为字符,如果输入1,则为字符1,对应ASCII码中是数字49!!!
4.代码实现使用串口点亮单片机LED
以上的代码实现的是接收字符来点亮LED,如果是想要使用字符串来控制点亮LED,需要修改代码:
#include "reg52.h"
#include <string.h>#define SIZE 12
sbit D5 = P3^7;//根据原理图(电路图),设备变量led1指向P3组IO口的第7口
sbit led2 = P3^6;//根据原理图(电路图),设备变量led2指向P3组IO口的第6口char cmd[12];/*
void UartInit(void) //9600bps@11.0592MHz
{PCON &= 0x7F; //波特率不倍速 SMOD在PCON里SCON = 0x50; //8位数据,可变波特率AUXR &= 0xBF; //定时器时钟12T模式AUXR &= 0xFE; //串口1选择定时器1为波特率发生器TMOD &= 0x0F; //设置定时器模式TMOD |= 0x20; //设置定时器模式TL1 = 0xFD; //设置定时初始值TH1 = 0xFD; //设置定时重载值ET1 = 0; //禁止定时器中断TR1 = 1; //定时器1开始计时
}
*/void Delay1000ms() //@11.0592MHz
{unsigned char i, j, k;i = 8;j = 1;k = 243;do{do{while (--k);} while (--j);} while (--i);
}void UartInit() //9600bps@11.0592MHz
{SCON = 0x50; //配置串口工作方式1,REN使能接收//配置定时器1,工作方式为8位自动重载TMOD &= 0x0f;//TMOD |= 0x20;TH1 = 0xFD; TL1 = 0xFD; //9600波特率的初值TR1 = 1;//启动定时器ES = 1;//开启串口中断EA = 1;//开启总中断}//TI 发送请求中断标志位,是指单片机向电脑发送
//RI 接收请求中断标志位,是指单片机接收电脑的消息void Send_byte(char data_msg)
{SBUF = data_msg;while(!TI);//在请求中断时,TI= 1,既!TI=0,等待数据的发送完成;响应中断结束后TI = 0,既!TI = 1,将TI清零TI = 0;}void Send_string(char* str)
{while(*str != '\0'){Send_byte(*str);str++;}
}void main()
{char data_msg = 'a';D5 = 1;//配置C51串口通信发送方式UartInit();while(1){Delay1000ms();//往发送缓冲器写入数据,就完成数据的发送Send_string("wdnmd\t");//怎么知道收到数据,查询RI的值,如果RT是1(收到数据后由硬件置一)} }void Uart_Handler() interrupt 4
{static int i = 0;//静态变量,被初始化一次 避免每次发生中断i都会清0if(RI)//中断处理函数中,对于接收中断的响应 //如果是RI引起的中断{RI = 0;//清除接收中断标志位,软件复位cmd[i] = SBUF;i++;if(i == SIZE){i = 0;}if(strstr(cmd,"open\r\n")){D5 = 0;//点亮D5i = 0;memset(cmd,'\0',SIZE);}if(strstr(cmd,"close\r\n")){D5 = 1;//熄灭D5i = 0;memset(cmd,'\0',SIZE);}}if(TI){}
}
如此一来,在串口中输入"open“加上换行键,再按下发送数据就可以点灯;同样输入”close"加上换行键,再按下发送数据就可以关灯。
使用蓝牙模块HC08进行串口通讯
注意!蓝牙的RXD要和串口的TXD相连,同理,蓝牙的TXD要和串口的RXD相连。使用上左图的蓝牙模块(内置波特率9600,正好可以对应)再使用右图扫码小程序,搜索“HC08"即可连接成功!
注意!在串口中,是检测换行符结束字符串的
'\r’是回车,前者使光标到行首
'\n’是换行,后者使光标下移一格Unix系统里,每行结尾只有“<换行>”,即“\n”;
Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;
Mac系统里,每行结尾是“<回车>”,即“\r
以下来自ChatGPT的解答:
串口通信中,有时需要在发送的数据后面加上\r\n(回车换行符),这主要是为了满足接收端或显示设备的特定要求,确保数据能够被正确解析和显示。
具体来说,\r(回车符)的作用是使光标移动到当前行的最前面,而\n(换行符)的作用则是将光标移动到下一行的当前位置。因此,\r\n组合起来,可以实现光标跳到下一行的开始位置,从而实现数据的换行显示。
在串口通信中,如果接收端或显示设备需要按照特定的格式来解析和显示数据,那么就需要在发送的数据后面加上相应的控制符。例如,在某些情况下,接收端可能期望每接收完一行数据后进行一些处理,或者显示设备可能需要在每行数据的末尾换行以显示新的数据。在这些情况下,就需要在发送的数据后面加上\r\n来实现换行。
需要注意的是,不是所有的串口通信都需要加\r\n。是否添加以及添加的方式(是\r\n、\n还是其他控制符)取决于具体的通信协议和接收端或显示设备的要求。因此,在进行串口通信时,需要根据实际情况来确定是否需要添加\r\n以及其他控制符。