51单片机学习篇-- --基于51单片机的串口通信协议
开篇先说一句废话····
本旺名字叫萨摩耶,,Please 叫我旺财,,,哈哈,招财进宝嘛!
开篇
计算机按照下行数据通信协议,串口发送数据,地址为自己的学号(十六进制),单片机收到后(收到的是数据,不是地址),如果和自己的学号比较,如果相等,则延时100ms后根据自己拟定的通信协议应答,其中数据域为0xAA,如果不是自己的学号,则应答的数据域为0x55。
单片机 ------> 计算机* 前导符 : 2* 地址 : 1* 功能号 : 1* 长度 : 1* 数据域 : 自定* 校验 : 1
计算机 ------> 单片机* 前导符 : 2* 地址 : 1* 功能号 : 1* 长度 : 1* 数据域 : 自定* 校验 : 1
项目分析
项目中自定义部分,再次说明(16进制数)
计算机 -------> 单片机部分
前导符1: 57
前导符2: 4a
地址 : 0a
功能号 : 01
长度 : 0c(12)
数据 :32 30 31 39 31 38 30 36 30 32 31 30(学号 ,ASCii码)
校验 :01
单片机 -------> 计算机部分
前导符1: 57
前导符2: 4a
地址 : 0a
功能号 : 01
长度 : 01(1)
数据 :AA 或者 55(取决于判断结果)
校验 :A0 或者 5F
串口通信协议
因为单片机每次收发只能是一个字节,而正常使用不会只发一个字节,而是很多字节一起,为了确保数据包的正确性,双方约定好一个数据包的每一个字节的意思(串口通讯协议);为了统一,所以每个数据包里会大致规定。在此项目中,一个数据包中,前导字节有两位;地址一位;功能号一位;长度一位;校验位一位。题目要求接收数据要与学号比较,所以先规定学号是12个字节,即计算机发送到单片机的数据域为12个字节数据;在比较后按数据域响应 0xAA或者0x55.(判断结果)
接收部分
根据题目要求,先是计算机给单片机按照通信协议发送数据,对于单片机而言,我们先是接收。
接收时,接收的数据有不同的意思,有前导(2个字节)、地址、功能号、长度、数据、校验。每一个意思需要各自的操作,所以需要定义一个变量用表示每一个意思。
u8 RxStus = 0;
前导和地址和校验只需要判断,不需要保存起来使用;而长度,功能号需要保存起来,(长度接受数据时使用,功能号在判断功能时使用);数据是变化的,所以定义一个数组保存。接收数据时需要判断接收了多少了,毕竟不能一直接收,所以也需要一个变量。所有的数据接收完还要给一个标志。
u8 RxLth = 0; //长度
u8 RxFunc = 0; //接收功能号
u8 RxBuf[16]; //接收数据数组
u8 Rxcounter = 0; //接收数据当前个数
bit bRxFlag = 0; //接收完成标志
因为我们是接收,所以数据不确定什么会从计算机发来,所以所有接收部分都应该在中断中处理。
步骤:
- 如果前导符1正确进入前导2
- 如果前导2正确进入地址
- 如果地址正确进入功能号
- 接收完功能号进入长度
- 接收完长度进入数据;如果长度为0(没有数据)进入校验
- 数据接收完进入校验;如果没有接收完继续接收
- 如果校验正确反馈接收完成标志,清空接收长度和接收数据计数
(需要校验的数据有地址、功能号、长度、所有数据域数据)
void UART_ISR(void) interrupt 4
{u8 tmp; //用来保存每次进入接收部分的数据(前导、地址等数据)static u8 sum = 0; //用来保存校验结果,以便于与最后校验部分比较if(RI){RI = 0;tmp = SBUF; //保存数据switch(RxStus){ //判断此时是哪种状态case 0 : //前导if(tmp == 0x57) //自定义前导符1,此时规定是 0x57RxStus = 1; //进入前导符2break; //退出判断case 1 : //前导if(tmp == 0x4a) //自定义RxStus = 2;break;case 2 : //地址if(tmp == 0x0a){ //自定义(题目规定学号) 10RxStus = 3;sum = tmp; //开始校验}break;case 3 : //功能号RxFunc = tmp;RxStus = 4;sum ^= tmp; //校验功能号break;case 4 : //长度RxLth = tmp;RxStus = 5;sum ^= tmp; //校验长度if(tmp == 0) //没有数据RxStus = 6;break;case 5 : //数据sum ^= tmp; //校验数据RxBuf[Rxcounter++] = tmp;if(Rxcounter >= RxLth) //接收数据域完成RxStus = 6;break;case 6 : //校验if(tmp == sum) //接收回来的校验数据和根据需要校验的数据进行校验后的结果比较bRxFlag = 1; //接收完成标志置位RxStus = 0;Rxcounter = 0;break;default :RxStus = 0;Rxcounter = 0;break;}}
}
其实这个接收部分采用的状态机,而里面提到的每一个意思就是状态机里的每个状态。(长知识,嘿嘿)
比较数据是否正确及发送数据
接收部分完成,在主函数中判断接收完成标志是否置位,如果置位说明一个数据包接收完成,开始比较数据是否与学号相等,如果相等则返回数据域0xAA,否则返回0x55.
因为要发送数据,所以把所有的数据(包括前导符、地址等)保存到一个数组中,来发送。因为要比较,所以需要一个数组存放学号,发送需要发送长度,发送计数(保存当前发送个数)
u8 TxBuf[16]; //保存所有发送数据
u8 StuNum[] = "201918060210"; //保存学号,用来比较
u8 TxLth = 0; //发送数据长度
u8 Txcounter = 0; //发送数据计数
主函数步骤:
- 检测接收完成标志
- 比较数据(接收数据和学号)
- 计算校验结果
- 设置发送长度
- 延时100ms
- 开始发送数据
比较数据有两种:
1.使用C库函数 strcmp() 字符比较函数,当它相等时返回0,具体可以自行搜索查看
2. 用for循环判断,设置一标志位,如果相等标志位置位,否则标志位清除并退出循环(只要有错就跳出,不在检查剩余数据)(若使用此方法,在中断接收后,接收长度不能清零(在判断完后清空就好),或者赋给专门的变量)
TxBuf数组
------------------------------------------------
| [0] | [1] | [2]| [3] | [4] | [5] |[6] |
------------------------------------------------
| 前导符1 |前导符2 |地址 |功能号 |长度 |数据域 |校验 |
------------------------------------------------
if(bRxFlag){ //判断数据是否正确bRxFlag = 0;if(strcmp(RxBuf,StuNum) == 0) //采用第一种方式比较TxBuf[5] = 0xAA; //数据域elseTxBuf[5] = 0x55;TxBuf[6] = TxBuf[2];for(i = 3;i <= 5;i++) //从功能号 ---> 数据域 TxBuf[6] ^= TxBuf[i]; //进行异或校验TxLth = 7; //0~6 数组有效长度7Delay_ms(100);SBUF = TxBuf[Txcounter++]; //开始发送
}
因为主函数开始发送,接下来要在中断中接着发
if(TI){TI = 0;if(Txcounter < TxLth){ //如果没有发完度,接着发SBUF = TxBuf[Txcounter++];}else{Txcounter = 0; //发送完毕后,计数和长度清零TxLth = 0;}
}
因为反馈时前导、地址、功能号不变,所以可以在初始化时初始化好,所以在初始化函数中,除了串口初始化还需要添加前导、地址、功能号。
void MCU_Init()
{//串口初始化 默认方式1SCON = 0x50;TMOD &= 0x0f;TMOD |= 0x20;TH1 = TL1 = 0xfd; //9600TR1 = 1;ES = 1;//发送数据TxBuf[0] = 0x4a; //前导 0x4a TxBuf[1] = 0x43; //前导 0x43TxBuf[2] = 0x0a; //地址 0x0a 学号:10TxBuf[3] = 0x01; //功能号 1TxBuf[4] = 0x01; //长度 1
}
到此,串口协议已经完成。
串口调试助手发送数据
添加测试数据时,别手残哦。手底快点昂······
接着发送即可。
理论效果是:
测试错误数据,即学号不同(学号修改后需要重新校验,步骤如上(不校验前导))
效果:
完整代码
#include "main.h"u8 RxBuf[16];
u8 TxBuf[16];
u8 StuNum[] = "201918060210";
bit bRxFlag = 0;
u8 RxLth = 0;
u8 TxLth = 0;
u8 Rxcounter = 0;
u8 Txcounter = 0;
u8 RxStus = 0;
u8 RxFunc = 0;void main(void)
{u8 i;MCU_Init();EA = 1;while(1){if(bRxFlag){ //判断数据是否正确bRxFlag = 0;if(strcmp(RxBuf,StuNum) == 0)TxBuf[5] = 0xAA; //数据域elseTxBuf[5] = 0x55;TxBuf[6] = TxBuf[2];for(i = 3;i <= 5;i++)TxBuf[6] ^= TxBuf[i];TxLth = 7;Delay_ms(100);SBUF = TxBuf[Txcounter++];}}
}void UART_ISR(void) interrupt 4
{u8 tmp;static u8 sum = 0;if(RI){RI = 0;tmp = SBUF;switch(RxStus){case 0 : //前导if(tmp == 0x57)RxStus = 1;break;case 1 : //前导if(tmp == 0x4a)RxStus = 2;break;case 2 : //地址if(tmp == 0x0a){ //10RxStus = 3;sum = tmp;}break;case 3 : //功能号RxFunc = tmp;RxStus = 4;sum ^= tmp;break;case 4 : //长度RxLth = tmp;RxStus = 5;sum ^= tmp;if(tmp == 0) //没有数据RxStus = 6;break;case 5 : //数据RxBuf[Rxcounter++] = tmp;sum ^= tmp;if(Rxcounter >= RxLth)RxStus = 6;break;case 6 : //校验if(tmp == sum)bRxFlag = 1;RxStus = 0;Rxcounter = 0;break;default :RxStus = 0;Rxcounter = 0;break;}}if(TI){TI = 0;if(Txcounter < TxLth){SBUF = TxBuf[Txcounter++];}else{Txcounter = 0;TxLth = 0;}}
}void MCU_Init()
{//串口初始化 默认方式1SCON = 0x50;TMOD &= 0x0f;TMOD |= 0x20;TH1 = TL1 = 0xfd; //9600TR1 = 1;ES = 1;TxBuf[0] = 0x4a; //前导 0x4a TxBuf[1] = 0x43; //前导 0x43TxBuf[2] = 0x0a; //地址 0x0a 学号:10TxBuf[3] = 0x01; //功能号 1TxBuf[4] = 0x01; //长度 1
}void Delay_ms(u16 ms)
{unsigned char i, j;while(ms--){_nop_();i = 2;j = 199;do{while (--j);} while (--i);}
}
51单片机学习篇-- --基于51单片机的串口通信协议相关推荐
- 51单片机学习杂记——基于STC89C52RC
51单片机学习杂记--基于STC89C52RC 我是看的b站郭天祥老师的课,说实话,我觉得我能力不是很够,所以记得很杂.废物了属于是. 接下来就是正文了 基本的元器件以及字母符号含义: 电容:帮助晶振 ...
- 51单片机五层电梯控制器 基于51单片机的五层电梯控制系统
51单片机五层电梯控制器 基于51单片机的五层电梯控制系统 包括源代码和proteus仿真 系统硬件由51单片机最小系统.蜂鸣器电路.指示灯电路.内部按键电路.外部按键电路.直流电机.内部显示电路.外 ...
- 【51毕设案例】基于51单片机-定时喂食供氧智能鱼缸
目录 功能介绍: 整体功能简述: 硬件框图: 软件流程图: 原理图: 仿真图: 元器件清单: 主要部分代码展示:void main() 工程管理展示: 下载内容展示: 更详细代码视频讲解及代码下载请看 ...
- 单片机stm32LED流水灯C语言,STM32单片机入门 篇二:单片机编程:从点亮LED灯开始(二)...
STM32单片机入门 篇二:单片机编程:从点亮LED灯开始(二) 2019-04-26 13:21:48 19点赞 73收藏 19评论 写在前面的话:本篇是承接上一篇文章,属于小白向. 没看过的朋友点 ...
- 51单片机计算器_基于51单片机的倒计时温度检测报警器
基于51单片机的倒计时温度检测报警器 学习单片机断断续续半年了,在学长学姐的教学帮助下,完成了51单片机的入门学习,开始实践做项目,在一周时间内设计和制作出个人项目.起初,我设计和选择的是12864显 ...
- c语言51单片机计算器,新基于51单片机的简易计算器
<新基于51单片机的简易计算器>由会员分享,可在线阅读,更多相关<新基于51单片机的简易计算器(24页珍藏版)>请在人人文库网上搜索. 1.基于51单片机的简易计算器1.前言: ...
- 51单片机开发实例 基于51单片机的光控灯
一.系统设计 通过光敏电阻模块检测环境中的光强,系统会根据程序设定的阈值完成小灯的控制,环境较暗且低于控制阈值小灯就会亮起.光敏电阻模块可以根据不同的光照强度输出相应的模拟电压信号,模拟信号会通过AD ...
- 万年历设计单片机c语言,基于51单片机的电子万年历的设计
基于51单片机的电子万年历的设计(论文9400字) 功能要求 1. 万年历能用数码管显示阳历年.月.日.星期.[小]时.分.秒并设置指定时间的闹铃. 2. 数字式温度计要求测温范围-50~100°C, ...
- 项目篇 | 基于STM32单片机NBIOT定位实战项目
前言 绘制基于 STM32 单片机的 NBIOT 实战项目. 文章目录 前言 一.原理图 1.绘制 1)电源供电 a.USB 转 TTL 电路 b.锂电池充电管理电路 c.3.3V电压转换电路 d.一 ...
最新文章
- 关于IOS的屏幕适配(iPhone)——资源适配
- raid5数据恢复成功案例
- 使用RBTool自动提交code review请求
- 第三届山东大数据-威海赛区-民宿空置预测-排行榜
- boost::phoenix::delete和using boost::phoenix::new_相关的测试程序
- FPGA学习之路—应用程序—原码二位乘法器及Verilog代码分析
- 【HDU - 1326】Box of Bricks(模拟水题)
- 超级vga显示卡_VGA视频采集卡常见故障分析
- matlab2c使用c++实现matlab函数系列教程-exppdf函数
- hdu acm2549
- 【零基础】PostgreSQL从入门到精通
- 上传本地新项目到SVN服务器
- 技术点:使用vue和element实现地图选点功能
- cdlinux中minidwep的使用
- markdown段落首行缩进2个字符
- mysql replace into 异常1365 - Division by 0
- 计算机体系结构基础2(究竟什么是冯诺依曼瓶颈(von Neumann Bottleneck)?)
- 股票学习-量柱和k线-第五天
- 项目经理的岗位职责和要求
- 编写简单的中文分词程序