开篇先说一句废话····
本旺名字叫萨摩耶,,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,此时规定是 0x57
RxStus = 1; //进入前导符2
break; //退出判断
case 1 : //前导
if(tmp == 0x4a) //自定义
RxStus = 2;
break;
case 2 : //地址
if(tmp == 0x0a){ //自定义(题目规定学号) 10
RxStus = 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; //数据域
else
TxBuf[5] = 0x55;
TxBuf[6] = TxBuf[2];
for(i = 3;i <= 5;i++) //从功能号 ---> 数据域
TxBuf[6] ^= TxBuf[i]; //进行异或校验
TxLth = 7; //0~6 数组有效长度7
Delay_ms(100);
SBUF = TxBuf[Txcounter++]; //开始发送
}
因为主函数开始发送,接下来要在中断中接着发
if(TI){
TI = 0;
if(Txcounter < TxLth){ //如果没有发完度,接着发
SBUF = TxBuf[Txcounter++];
}
else{
Txcounter = 0; //发送完毕后,计数和长度清零
TxLth = 0;
}
}
因为反馈时前导、地址、功能号不变,所以可以在初始化时初始化好,所以在初始化函数中,除了串口初始化还需要添加前导、地址、功能号。
void MCU_Init()
{
//串口初始化 默认方式1
SCON = 0x50;
TMOD &= 0x0f;
TMOD |= 0x20;
TH1 = TL1 = 0xfd; //9600
TR1 = 1;
ES = 1;
//发送数据
TxBuf[0] = 0x4a; //前导 0x4a
TxBuf[1] = 0x43; //前导 0x43
TxBuf[2] = 0x0a; //地址 0x0a 学号:10
TxBuf[3] = 0x01; //功能号 1
TxBuf[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; //数据域
else
TxBuf[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){ //10
RxStus = 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()
{
//串口初始化 默认方式1
SCON = 0x50;
TMOD &= 0x0f;
TMOD |= 0x20;
TH1 = TL1 = 0xfd; //9600
TR1 = 1;
ES = 1;
TxBuf[0] = 0x4a; //前导 0x4a
TxBuf[1] = 0x43; //前导 0x43
TxBuf[2] = 0x0a; //地址 0x0a 学号:10
TxBuf[3] = 0x01; //功能号 1
TxBuf[4] = 0x01; //长度 1
}
void Delay_ms(u16 ms)
{
unsigned char i, j;
while(ms--)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}