串口基本认知
串行接口简称串口,通常指COM接口;串行接口是指数据一位一位的顺序传送,其特点就是通信线路简单,只要一对传输线就能实现双向通信(全双工),从而大大降低了成本,特别适合远距离通信,但是传送速度较慢。
串口关于电器标准和协议
RS-232
也称为标准串口,最常用的一种串行通讯接口,比如我们电脑主机的9针串口,最高速率是20kb/s,RS-232是为点对点设计的,最长传输距离为15m,适合本地设备之间的通信。
RS-422
支持点对点的双向通信,最大传输距离为1219米,最大传输速率为10Mb/s。
RS-482
是从RS-422基础上发展而来的,无论四线还是二线连接方式总线上可多接32个设备。
串口的电平
这里要提到经常听说的UART,UART是指异步串行,通过异步接收/发送,UART包括TTL电平的串口和RS-232电平的串口,所以串口的概念比UART大一点。
怎么理解异步通信?
由于单片机和电脑,不同设备的频率和配置不同,因此CPU运行速度不同,所以各自使用各自的时钟来相互配合。
RS232电平和TTL电平有什么区别?
对于RS232,逻辑1是 -3 ~ -15V的电压,逻辑0是 3 ~ 15V 的电压。
而TTL电平是晶体管-晶体管逻辑的简称,TTL电平信号应用广泛,是因为其数据表示采用二进制规定,+5v等价于逻辑1,0v等价于逻辑0。 在数字电路中,由TTL电子元器件组成电路的电平是个电压范围,规定:
输出高电平>=2.4V,输出低电平<=0.4V;
输入高电平>=2.0V,输入低电平<=0.8V;
串口的接线
笔记本电脑通过TTL电平和单片机通讯:TX发送线(端口);RX接受线(端口),并使用CH340进行USB转TTL。
RXD:数据输入引脚,数据接收;STC89系列对应P3.0口,上官一号(即我使用的STC开发板)有单独引出
TXD:数据发送引脚,数据发送;STC89系列对应P3.1口,上官一号有单独引出
注意,如果需要自己接线的化,应该交叉接线,TXD接RXD, RXD接TXD!
串口的编程要素
输入和输出的数据缓存器叫做SBUF,都用99H的地址代码,但是是两个独立的8位寄存器。
代码体现为:想要接收数据 char data = SBUF (将缓存器的值读取到data)
想要发送数据 SBUF = data; (将data的值写入缓存器)
再次回忆UART的概念,”由于单片机和电脑,不同设备的频率和配置不同,因此CPU运行速度不同,所以各自使用各自的时钟来相互配合。” 所以双方需要约定通讯速度,这个速度就叫做波特率,对于电脑来说,别人做好了软件,鼠标点点就能配置好,而单片机的波特率则需要自己写代码。
对于电脑来说,需要点击配置的东西是:
所以在代码中,我们也需要配置波特率,校验位,停止位。那在代码中的这些如何进行设置?答案还是,和设置定时器模式一样,要操控寄存器。 而且,也和定时器一样,代码也可以从stc-isp助手里面得到: 注意!这里的波特率设置应该和之前电脑设置的波特率(最左侧的红圈)相同。
PCON寄存器为电源控制寄存器,最高位BIT7为 SMOD,SMOD = 0 时,波特率不加倍; SMOD = 1 时,波特率加倍,此处设置波特率不加倍。
SCON寄存器为串行控制寄存器,BIT7和BIT6分别是SM0和SM1,其值的组合会对应不同的功能:
此处设置8位可变波特率。
SM2为多机通讯相关位;REN为串口数据接收的允许位
代码实现单片机向电脑发送一个字符
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
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(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即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 msg = 'a';
UartInit();
while(1){
SBUF = msg; //往发送缓冲器里写入数据,就完成了数据的发送
Delay1000ms();
}
}
实现效果1
代码实现单片机向电脑发送字符串
在刚刚提到的SCON寄存器中,BIT1为TI:
即,每发送一个BYTE(一个char变量的大小),TI就会被硬件置1,根据这个,我们可以编写使串口打印字符串:
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
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(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void printSTR(char *msg)
{
while(*msg != '\0'){
SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
TI =0;
msg++;
}
}
void main()
{
//char *msg = "mjm";
UartInit();
while(1){
printSTR("mjm\r\n"); //在串口中。"\r\n"才是换行
Delay1000ms();
}
}
实现效果2
代码实现使用串口点亮单片机LED
在刚刚提到的SCON寄存器中,BIT0为RI:
即收到数据后,RI位会由硬件置1。
同时,使用中断的方式接收指令可以使得响应没有延时。
为了打开中断,还需查看手册:
所以,打开UART中断需要打开ES和EA!
#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;
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(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void printSTR(char *msg)
{
while(*msg != '\0'){
SBUF = *msg; //往发送缓冲器里写入数据,就完成了数据的发送
while(TI == 0); //只有当TI为1时,才往下走,根据手册,TI只有在发送完8位数据后才会硬件自动置1
TI = 0;
msg++;
}
}
void main()
{
//char cmd;
//char *msg = "mjm";
UartInit();
D5 = 1;
ES = 1;
EA = 1; //打开中断!
while(1){
printSTR("mjm\r\n"); //在串口中。"\r\n"才是换行
Delay1000ms();
/* if(RI == 1){ //RI = 1时数据接收完毕
cmd = SBUF; //从SBUF里面读发来的数据
if(cmd == 'o'){
D5 = 0;//开灯
}else if(cmd == 'c'){
D5 = 1;//关灯
}
RI = 0;//软件复位
} */
}
}
void UARTinter() interrupt 4 //由于不管TI还是RI置1时,中断都会发生,所以为了逻辑严谨,可以在中断处理函数中添加判断
{
if(RI == 1){ //如果是RI引起的中断
char cmd;
cmd = SBUF; //从SBUF里面读发来的数据
if(cmd == 'o'){
D5 = 0;//开灯
}else if(cmd == 'c'){
D5 = 1;//关灯
}
RI = 0;//软件复位
}
}
实现效果3
这次试验就很好的体现了串口的"全双工“,即可以不断同时的发送和接收数据。
同时注意,在”发送缓冲区”中选择文本模式后要注意,在文本模式下输入的任何东西都会被认为字符,如果输入1,则为字符1,对应ASCII码中是数字49!!!
实现效果3的拓展
以上的代码实现的是接收字符来点亮LED,如果是想要使用字符串来控制点亮LED,需要修改代码:
//新定义的全局变量//
char flag;
char cmd[12];
char open[12] = "open\r\n";//由于要使用strcmp,所以将长度设置相同
char close[12] = "close\r\n";
//新定义的函数//
void Dealstr()
{
if(flag == 1){
if(strcmp(cmd,open) == 0){ //相等返回0
D5 = 0;//开灯
}else if(strcmp(cmd,close) == 0){
D5 = 1;//关灯
}
flag = 0; //将flag清0
memset(cmd,'\0',12); //将字符串清空
}
}
//新的中断处理程序//
void UARTinter() interrupt 4
{
if(RI == 1){ //如果是RI引起的中断
static int i = 0; //此时这句命令只会被执行一次。避免每次发生中断i都会清0
if(SBUF != '\n'){
cmd[i] = SBUF; //从SBUF里面读发来的数据
i++;
}else{
cmd[i] = '\n';//将刚刚判断而导致没有写上去的\n加上
cmd[i+1] = '\0';//加上字符串结束符
i = 0;
flag = 1;
}
Dealstr();
RI = 0;//软件复位
}
}
如此一来,在串口中输入"open“加上换行键,再按下发送数据就可以点灯;同样输入”close"加上换行键,再按下发送数据就可以关灯。
使用蓝牙模块HC08进行串口通讯
注意!蓝牙的RXD要和串口的TXD相连,同理,蓝牙的TXD要和串口的RXD相连。使用上左图的蓝牙模块(内置波特率9600,正好可以对应)再使用右图扫码小程序,搜索“HC08"即可连接成功!
同时!虽然在WINDOWS中的串口中,换行符是“\r\n",但是在ios系统中的串口中,换行符依然是”\r"!所以代码需要略微修改,将open和close字符串的定义中的"\r"给去掉!
//char open[12] = "open\r\n";//windows //char close[12] = "close\r\n";//windows char open[12] = "open\n";//ios char close[12] = "close\n";//ios