深入串口@通信
串口通信简介
你好! 如果这是你第一次使用 串口 ,那么了解一些串口是什么以及怎么用吧。
串口是什么
一般来说,单片机与上位机之间以串口通信为主,就串口通信而言。常用的几种通信方式,包括串口自定义协议、Modbus协议、CAN总线。重点介绍一下串口通信。
IDEA想法
现在小铭有一台电脑和一个外设,现在他想让外设给电脑发送一个字母A。怎么办呢?我们知道计算机的世界只有0和1。那么用0和1怎么发送A甚至是任意数据呢?
假设我们每次发送一个字节那么它要么是0要么是1,接下来我们一次发送8个字节则依然是每一位为0或者1。但是注意到
也就是说第一位可以代表1个数字,第二位代表2个数字…第八位可以代表128个数字,那么这八个字节可以代表1+2+4+8+16+32+64+128=255个数字。如果对照ASCII表
基本上可以发送所有字符。
通信
好了,现在我们按照ASCII上对应关系开始发送数据吧。还是以A为例。A对应的十进制是65。 65=1+64。那么我们可以让第一位是1,第7位也是1,其他位为0。
现在我们已经成功的发送了A。
此外还得规定通信速率也就是波特率每秒发送的位数。这里就不详细介绍了。
单片机实现串口通信
51单片机
要想使用串口就要了解一下两个寄存器和定时器。
(1)串口控制寄存器 SCON
SCON=0x40 工作方式1;0100 0000 串口不接受数据
SCON=0x50 工作方式1;0101 0000 串口接受数据
(2)电源控制寄存器 PCON
PCON=0X80;波特率加倍
PCON=0X00;波特率不加倍
(3)TMOD 计数器
这什么玩意?要不我们换一张图看看吧!
1)
TMOD|=0X20; //设置计数器工作方式 2
TH1=baud; //计数器初始值设置
TL1=baud
2)
TMOD|=0X01;//选择为定时器 0 模式,工作方式1
TH0=初值
TL0=初值
硬核知识-串口发送字节
了解完以上内容现在我们就可以写一个串口发送的函数了。我们定义函数为uart_init()
在这个函数里我们做这几件事情:
①确定 T1 的工作方式(TMOD 寄存器);
②确定串口工作方式(SCON 寄存器);
③计算 T1 的初值(设定波特率),装载 TH1、TL1;
④启动 T1(TCON 中的 TR1 位);
⑤如果使用中断,需开启串口中断控制位(IE 寄存器)。
如果你要实现不同的功能那么这个初始化的函数也会不一样。这里我们写几个典型的例子:
实现功能:串口把接收到的数据发送回单片机
void uart_init(void)
{
TMOD|=0X20; //设置计数器工作方式 2
SCON=0X50; //设置为工作方式 1
PCON=0X80; //波特率加倍 4800倍后为9600
TH1=0XFA; //计数器初始值设置
TL1=OXFA;
ES=1; //打开接收中断
EA=1; //打开总中断
TR1=1; //打开计数器
}
void uart() interrupt 4//串口通信中断函数
{
u8 rec_data;
RI = 0; //清除接收中断标志位
rec_data=SBUF; //存储接收到的数据
SBUF=rec_data; //将接收到的数据放入到发送寄存器
while(!TI); //等待发送数据完成
TI=0; //清除发送完成标志位
}
串口功能:发送传感器数据,无需中断
void UART_Init(void)
{
SCON=0x50; //串口通信工作方式1
TMOD=0x20; //定时器1的工作方式2
PCON=0X00; //不加倍
TH1=0xfd,TL1=0xfd; //9600baud
TI=1; //这里一定要注意
TR1=1;
}
//如果你有一个返回ADC的函数adc_value()
float buff;
buff=adc_value();
printf("data is %f",buff);
//你就可以成功的把数据在串口助手中显示了
这里我重点介绍一下printf函数
printf 打印调试把数据显示在串口助手中或者是oled屏幕上。
配合上面那个串口初始化函数,然后在头文件中包含#Include “stdio.h”就可以使用了。
printf("welcome to the digatal world");
但是使用printf唯一的不好就是如果要与上位机通信它发送的数据位数不一样;比如说你的数据是3.23;45.13;100.64;数据位数不一样在于上位机通信时会给软件编程造成很大的麻烦。最好是003.23;045.13;100.64;怎么做到呢?小白我编程能力有限,如果有大佬会请留言谢谢!
下面介绍用寄存器收发数据。
void UART_Init(void)
{
TMOD|=0X20; //设置计数器工作方式2
SCON=0X50; //设置为工作方式1
PCON=0X00; //波特率加倍
TH1=0xfd; //计数器初始值设置
TL1=0xfd;
ES=1; //打开接收中断
EA=1; //打开总中断
TR1=1; //打开计数器
}
void UART_Sendchar(unsigned char d) //发送一个字节的数据,形参d即为待发送数据。
{
SBUF=d; //将数据写入到串口缓冲
while(!TI);
TI=0;
}
void UART_SendString(unsigned char *String)
{
while(*String)
{
UART_Sendchar(*String);
String++;
}
}
UART_Sendchar(65); //发送A十进制对应的符号是A
UART_Sendchar('a');//发送a 注意只能单引号
UART_Sendchar(0x65);//发送e十六进制65对应的符号是e
UART_SendString("小七");//发送字符串
/*****在这里你应该理解了吧串口发送就是按照ASCII表的规则来的。无论你发的是十进制还是十六进制最后都会转换成对应的符号
//发送浮点数
float b=3.2;//带转化数据
uchar conver_t[4];//一定要定义成**数组**类型
float b=3.2;
sprintf(conver_t,"%f",b);//通过%.1f /%.2f可以改变小数位数
UART_SendString(conver_t);
串口接收中断的设置;一般用于上位机控制
根据接受的数据做出响应,如灯的翻转,之类的。
直接上代码!
//串口控制led亮灭
#include "reg52.h"
#include "stdio.h"
sbit led=P1^5;
unsigned char rec;
void receive_dat( rec)
{
if(rec==0x00)
{
led=0;
}
else if(rec==0x01)
{
led=1;
}
else if(rec==0x02)
{
led=0;
}
else
{
led=1;
}
}
void UART_Sendchar(unsigned char d) //发送一个字节的数据,形参d即为待发送数据。
{
SBUF=d; //将数据写入到串口缓冲
while(!TI);
TI=0;
}
void uart_init(void)
{
TMOD|=0X20; //设置计数器工作方式 2
SCON=0X50; //设置为工作方式 1
PCON=0X00; //波特率加倍 4800倍后为9600
TH1=0XFd; //计数器初始值设置
TL1=0xfd;
TR1=1; //打开计数器;
ES=1; //打开串口中断
EA=1; //打开总中断
TR1=1; //打开计数器
ET1=0;
}
void main()
{
uart_init();
UART_Sendchar('E');
while(1)
{
}
}
void Uart() interrupt 4
{
if(RI==1)
{ RI=0;
rec=SBUF;
receive_dat(rec);
}
}
注意,本代码不是万能!如果你的程序很简单那么这些代码基本没问题,但是小白我就曾经遇到了一个问题。我在一个项目中用到了两个中断,定时器、串口中断冲突。结果接收上位机的数据相关代码一直不起作用。我以为是串口接收中断函数有问题。在网上查了一下,可能的原因很多。解决办法嘛主流的是增加中断时间,然后还有一个就是改中断号优先级。我都尝试过对我的程序来说没有用。最后无意中在一个帖子看到降低串口波特率。我把9600改为4800后果然有用。遇到问题,多去查查。14亿中国人,几十亿外国人,他们就做的全都对嘛?也会有类似的问题对吧。