本文为博主 日月同辉,与我共生,csdn原创首发。希望看完后能对你有所帮助,不足之处请指正!一起交流学习,共同进步!
> 发布人:@日月同辉,与我共生_单片机-CSDN博客
> 欢迎你为独创博主日月同辉,与我共生点赞❤❤❤+关注+收藏+评论☺。
系列专栏: CSDN-单片机串口通信学习系列
> 我的格言是:“尽最大努力,做最好的自己!
要转载,请提前告知!!!
版权声明:本文为CSDN博主「日月同辉,与我共生」的原创文章,CSDN独一份。
目录
一、数据帧
1.1数据类型
1.2帧尾
1.3ASCII码
二、系统设计
2.1设计要求
2.2系统原理
2.2.1接收原理&正确性判断原理
2.2.2持续时间原理
三、硬件设计
3.1串口设计
3.2LED模块设计
3.3蜂鸣器模块设计
四、软件设计
4.1串口初始化模块
4.2接收模块
4.3定时器初始化模块
4.4LED+蜂鸣器功能模块
4.5主程序模块
4.6发送数据模块
4.7清除缓冲模块
4.8uart.h
五、结果
发送到数据帧可以细分成【帧头】、【地址】、【类型】、【长度】、【数据块】、【效验】、【帧尾】7种,但并不是每次发送的数据都要有这7种。
像【地址】,一般只有多机通信会用到,单机通信不需要有。多机通信时,会给每台机编一个地址,确保数据不会发送到其他的机。还有,【长度】并不是每次都要有,如果发的数据长度是固定的,一般不需要有,发送到数据长度不固定时,才需要有。
数据类型一般决定处理某个模块。比如我与用户约定好,数据类型01,处理LED,数据类型02,处理蜂鸣器,那么我的设备发送数据01给对方,则对方去处理LED,发送数据02给对方,则对方去处理蜂鸣器。
一次通信的结束,一般用一些特殊符号(比如回车等)表示,以防止帧尾前面的数据与帧尾一样,导致通信出现故障。
ASCII码是一种字符编码方式,共有128个字符,包括英文字母、数字、标点符号和一些控制字符等。每个字符用7个二进制位表示,这些二进制位可以转换成十进制数,也可以转换成十六进制数。
字母 A 在 ASCII 码中的二进制表示为 01000001,十进制表示为 65,十六进制表示为 0x41。
数字 0 在 ASCII 码中的二进制表示为 00110000,十进制表示为 48,十六进制表示为 0x30。
控制字符包括换行符、回车符、制表符等,它们的 ASCII 码值在 0~31 和 127 中。
虚拟串口com3发送数据给51单片机com1,com1发回数据给com3,同时处理好接收到的数据。
这次数据帧有帧头+类型+数据块+帧尾组成。帧头为AA 55,数据类型分为01/02,数据块分为高8位和低8位,帧尾为0x0D(回车)。
数据类型为01时,LED灯工作,数据类型为02时,蜂鸣器工作。不论01还是02,持续时间与数据块有关(高8位、低8位)。
上两篇文章我们用定时器实现串口通信,本质是先接收后处理数据。
回顾上两篇文章:
串口通信(6)应用定时器中断+串口中断实现接收一串数据-CSDN博客
串口通信(7)判断数据帧头来接收一串数据-CSDN博客
今天我们讲述如何一边接收数据一边处理数据。
定义一个数组recv_buf,用于存储接收到数据,采用switch-case语句,每接收一个【帧头】数据,立刻用if语句判断【帧头】是否正确,若不正确,则重头判断。
若帧头AA 55数据均正确(recv_buf[0]=AA,recv_buf[1]=55),则进入下一步骤。
在分支2(case 2),接收【类型】(recv_buf[2])和【数据块】(recv_buf[3]和recv_buf[4])。
接着在分支3中,如果帧尾(recv_buf[5]=0x0D)正确,则开始处理LED灯和蜂鸣器。
分别定义两个控制持续时间的变量led_data、beep_cnt。
持续时间=高8位<<8+低8位
在定时器定时1ms的中断中,利用if语句来控制LED灯/蜂鸣器的持续时间。
至于是LED灯还是蜂鸣器工作,由recv_buf[2]决定。
com1与com3通信,com2是两者的通信媒介,所以单片机com1的发送端TXD接com2的TXD,接收端RXD则接com2的RXD即可。
LED灯采用共阳极接法,左端接电源(高电平1),右端通过电阻接P1^0,P1^0低电平时,LED导通,发亮;P1^0高电平时,LED不导通,不亮(熄灭)。
R2右端接到P1^7,P1^7为低电平时,三极管导通,+5V电源(提供电压电流的作用)与蜂鸣器连接,蜂鸣器正极为高电平;P1^7为高电平,三极管不导通,蜂鸣器正极为低电平。如图,黄色波形为R2右端(buzzor),蓝色为蜂鸣器正极波形,蜂鸣器发声的原理就是高低电平不断切换。
蜂鸣器模块电路图:
波特率为9600b/s,采用中断法。
回顾中断法:UART通信-中断法
void UartInit(void) //[email protected]
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFD; //设置定时初始值
TH1 = 0xFD; //设置定时重载值
ET1 = 0; //禁止定时器中断
ES=1; //串口中断打开
TR1 = 1; //定时器1开始计时
}
可以看看本文2.2.1讲述的原理。
void ES_timers() interrupt 4 //接收中断
{
static unsigned char machine_step=0;//状态变量
if(RI)
{
RI=0;
switch(machine_step)
{
case 0:recv_buf[0]=SBUF;//接收第一个数据
if(recv_buf[0]==0xAA)
{
machine_step=1;//帧头第一个数据正确,状态变为1
}
else
{
machine_step=0;//帧头第一个数据错误,重新判断下一个是否正确
}
break;
case 1:recv_buf[1]=SBUF;//接收第二个数据
if(recv_buf[1]==0x55)
{
machine_step=2;//帧头第二个数据正确,状态变为2
recv_cnt=2;
}
else
{
machine_step=0;//帧头第一个数据错误,重新重头判断是否正确
}
break;
case 2:recv_buf[recv_cnt]=SBUF;//接收第3、4、5个数据
recv_cnt++;
if(recv_cnt>4)
{
machine_step=3;
}
else
{
machine_step=2;
}
break;
case 3:recv_buf[recv_cnt]=SBUF;//接收第6个数据
if(recv_buf[recv_cnt]==0x0D)//帧尾 0x0D
{
switch(recv_buf[2])//通过数据类型判断处理LED还是蜂鸣器
{
case 0x01:
led_data=recv_buf[3]<<8;
led_data=led_data+recv_buf[4];//高8位<<8+低8位=LED控制时
//间
led_cnt=0;
break;
case 0x02:
beep_data=recv_buf[3]<<8;
beep_data=beep_data+recv_buf[4];//高8位<<8+低8位=蜂鸣器控
//制时间
beep_cnt=beep_data;
break;
default:break;
}
machine_step=0;
recv_cnt=0;
recv_flag=1;//接收完成标志位,=1时表明接收完成
}
break;
default:break;
}
}
}
定时器T0定时1ms:
回顾定时器:
中断&定时计数器-CSDN博客
单片机笔记(3)定时计数与中断-CSDN博客
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
ET0=1; //定时器0中断打开
TR0 = 1; //定时器0开始计时
}
定时1ms,则每1ms中断1次:
void T0_timer() interrupt 1 //利用1ms计数,判断是否接收完成
{
TR0=0;
if(recv_flag==1)//接收完毕
{
if(led_cnt
void main()
{
UartInit(); //调用串口初始化函数
Timer0_Init(); //调用定时器T0初始化
EA=1; //总中断允许
while(1)
{
if(recv_flag==1)//接收完数据后单片机发送回数据给com3
{
sendString(recv_buf);//发送数据,数组recv_buf存储接收到的数据
clr_recvbuffer(recv_buf);//清除缓冲
}
}
}
不懂指针和数组的,可以试着回顾前面写的文章的3.2节:
串口通信(5)-一串固定长度数据的接收-CSDN博客
void sendByte(unsigned char dat) //发送一帧数据功能函数
{
SBUF=dat;
while(!TI);
TI=0;
}
void sendString(unsigned char *dat)//发送字符串函数
{
while(*dat != '\0')
{
sendByte(*dat++);
}
}
unsigned char *buf=recv_buf,则buf[i]=0是将第i+1个数据清0。
void clr_recvbuffer(unsigned char *buf)
{
unsigned char i;
for(i=0;i
#ifndef __UART_H__
#define __UART_H__
#include
#include
#define MAX_REX_NUM 20
#define MAX_timer_cnt 5
extern unsigned char recv_buf[MAX_REX_NUM];
extern unsigned char recv_cnt;
extern unsigned char start_timer;
extern unsigned char recv_timer_cnt;
extern unsigned char recv_flag;
extern unsigned char led_data;
extern unsigned char beep_data;
extern unsigned char led_cnt;
extern unsigned char beep_cnt;
void UartInit(void);
void sendByte(unsigned char dat);
void sendString(unsigned char *dat);
char putchar(char c);
void clr_recvbuffer(unsigned char *buf);
#endif
本次设计完整代码,请关注不白吃并在评论区回复666!!!
@日月同辉,与我共生-不白吃CSDN博客
LED灯:
蜂鸣器:
下一文将着重串口中断即时解析用户自定义通讯协议--接收数据字节固定情况,亲爱的读者敬请期待,下一文更精彩!!!
一日不读书,胸臆无佳想。我叫不白吃,喜欢我的,可以支持我@日月同辉,与我共生!!!
欢迎大家的阅读和交流,也可以自由查阅博主其他文章哦!!!
@日月同辉,与我共生_单片机基础,单片机串口通信-CSDN博客@日月同辉,与我共生擅长单片机基础,单片机串口通信,等方面的知识,@日月同辉,与我共生关注stm32,c语言,51单片机,proteus,单片机领域.https://blog.csdn.net/LIN___IT?spm=1000.2115.3001.5343