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. 如果前导符1正确进入前导2
  2. 如果前导2正确进入地址
  3. 如果地址正确进入功能号
  4. 接收完功能号进入长度
  5. 接收完长度进入数据;如果长度为0(没有数据)进入校验
  6. 数据接收完进入校验;如果没有接收完继续接收
  7. 如果校验正确反馈接收完成标志,清空接收长度和接收数据计数
    (需要校验的数据有地址、功能号、长度、所有数据域数据)
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;				//发送数据计数

主函数步骤:

  1. 检测接收完成标志
  2. 比较数据(接收数据和学号)
  3. 计算校验结果
  4. 设置发送长度
  5. 延时100ms
  6. 开始发送数据

比较数据有两种:
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);
	}
}
  • 34
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值