一个简单的基础通信协议的设计与实现
- 一种常见的通信协议格式
- 搭建串口收发环境
-
- 通信协议的实现
不同设备之间的通信,都需要设计自己的通信协议。为了保证设备与设备之间的数据的稳定传输,通信协议的设计需要考虑很多的问题。当然应对不同的应用场景,可以有针对性的设计不同的通信协议。
一种常见的通信协议格式
这是一种我们比较常见的通信协议格式
帧头 | 地址位 | 功能位 | 帧序号 | 数据长度 | 数据内容 | 校验位 | 帧尾 |
---|
1/2字节 | 1字节 | 1字节 | 2字节 | 2字节 | n字节 | 1/2字节 | 1/2字节 |
而为了应对不同的情况,可以依照情况做删改,例如减少帧头和帧尾,减少帧序号等等。
而本篇实现的通信协议如下,这里将几个部分都做了,实际中可能并不需要这么冗余的帧,可以按需求适当删改:
地址位 | 功能位 | 帧序号 | 数据长度 | 数据内容 | 校验位 |
---|
1字节 | 1字节 | 1字节 | 1字节 | n字节 | 1字节 |
本篇例程使用的开发板是STM32F103VET6,应用工具是MDK-ARM v5.33,STM32CubeMX V6.1.1
注:STM32CubeMX需要安装JAVA环境(JRE)。
搭建串口收发环境
参考:https://blog.csdn.net/u014470361/article/details/79206352#comments
使用串口1,DMA方式收发数据
注:DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
配置STM32CubeMX
打开STM32CubeMX,File->New Project->Start Project
RCC->打开外部时钟
USART1->Asynchronous 异步通信
下面NVIC Settings->Enabled 使能串口中断
还是下面DMA Setthing->ADD->USART1_RX/USART_TX->Priority 使能DMA收发模式,高优先级
SYS->Dubug-Serial Wire 启用调试引脚,因为我使用ST-Link进行调试,不使能调试引脚的话没法调试。
上面的Clock Configuration时钟配置可以忽略,使用默认8MHz即可,然后是第三个选项Project Manager->Project Name设置工程名->Project Location设置工程路径,然后选择IDE->MDK-ARM
注意工程名和路径都不要出现中文字符
最后点击GENERATE CODE生成工程文件,如果失败的话,可以尝试更换JAVA环境。
添加USART部分代码
在main.h宏定义一个最大接收字节数1024
#define UART_RX_LEN 1024
打开工程,并在main.c中添加部分代码
定义接收数组,接收数据长度以及标识。UART_RX_STA的0-14位存储数据长度,第15位表示接收状态。
uint8_t UART_RX_BUF[UART_RX_LEN];
__IO uint16_t UART_RX_STA;
注意位置DMA初始化需在MX_USART1_UART_Init();
串口初始化之后。
HAL_UART_Receive_DMA(&huart1, UART_RX_BUF, UART_RX_LEN);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
在while循环中添加DMA发送指令,将接收到的数据发送回去
if(UART_RX_STA & 0X8000)
{
HAL_UART_Transmit_DMA(&huart1, UART_RX_BUF, UART_RX_STA & 0X7FFF);
UART_RX_STA = 0;
}
打开stm32f1xx_it.c文件添加代码
extern uint8_t UART_RX_BUF[UART_RX_LEN];
extern __IO uint16_t UART_RX_STA;
拉到底,找到USART1中断。修改如下
void USART1_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET&&(UART_RX_STA&0x8000==0))
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_DMAStop(&huart1);
UART_RX_STA = UART_RX_LEN - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
UART_RX_BUF[UART_RX_STA] = 0;
UART_RX_STA |= 0X8000;
HAL_UART_Receive_DMA(&huart1, UART_RX_BUF, UART_RX_LEN);
}
HAL_UART_IRQHandler(&huart1);
}
编译->生成->- 0 Error(s), 0 Warning(s).
下载,烧录,打开串口调试助手->波特率115200->随便发送几个字节,查看接收
到此,一个基本的串口DMA收发环境就搭建好了。下面就是通信协议的内容了。
通信协议的实现
新建一个protocol.h文件
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include "main.h"
#define MY_ADDRESS 1
#define M_FRAME_CHECK_SUM 0
#define M_FRAME_CHECK_XOR 1
#define M_FRAME_CHECK_CRC8 2
#define M_FRAME_CHECK_CRC16 3
typedef enum
{
MR_OK=0,
MR_FRAME_FORMAT_ERR = 1,
MR_FRAME_CHECK_ERR = 2,
MR_FUNC_ERR = 3,
MR_TIMEOUT = 4,
}m_result;
__packed typedef struct
{
u8 address;
u8 function;
u8 count;
u8 datalen;
u8 data[UART_RX_LEN];
u16 chkval;
}m_frame_typedef;
extern m_protocol_dev_typedef m_ctrl_dev;
extern u8 COUNT;
void my_packsend_frame(m_frame_typedef *fx);
m_result my_unpack_frame(m_frame_typedef *fx);
m_result my_deal_frame(m_frame_typedef *fx);
void My_Func_1(void);
#endif
再建一个protocol.c文件
#include "main.h"
uint8_t COUNT;
extern UART_HandleTypeDef huart1;
uint8_t checkmode=M_FRAME_CHECK_SUM;
extern uint8_t UART_RX_BUF[UART_RX_LEN];
extern __IO uint16_t UART_RX_STA;
m_result my_unpack_frame(m_frame_typedef *fx)
{
uint16_t rxchkval=0;
uint16_t calchkval=0;
uint8_t datalen=0;
datalen=UART_RX_STA & 0X7FFF;
if(datalen<5)
{
UART_RX_STA=0;
return MR_FRAME_FORMAT_ERR;
}
switch(checkmode)
{
case M_FRAME_CHECK_SUM:
calchkval=mc_check_sum(UART_RX_BUF,datalen-1);
rxchkval=UART_RX_BUF[datalen-1];
break;
case M_FRAME_CHECK_XOR:
calchkval=mc_check_xor(UART_RX_BUF,datalen-1);
rxchkval=UART_RX_BUF[datalen-1];
break;
case M_FRAME_CHECK_CRC8:
calchkval=mc_check_crc8(UART_RX_BUF,datalen-1);
rxchkval=UART_RX_BUF[datalen-1];
break;
case M_FRAME_CHECK_CRC16:
calchkval=mc_check_crc16(UART_RX_BUF,datalen-2);
rxchkval=((uint16_t)UART_RX_BUF[datalen-2]<<8)+UART_RX_BUF[datalen-1];
break;
}
if(calchkval==rxchkval)
{
fx->address=UART_RX_BUF[0];
fx->function=UART_RX_BUF[1];
fx->count=UART_RX_BUF[2];
fx->datalen=UART_RX_BUF[3];
if(fx->datalen)
{
for(datalen=0;datalen<fx->datalen;datalen++)
{
fx->data[datalen]=UART_RX_BUF[4+datalen];
}
}
fx->chkval=rxchkval;
}else
{
UART_RX_STA=0;
return MR_FRAME_CHECK_ERR;
}
UART_RX_STA=0;
return MR_OK;
}
void my_packsend_frame(m_frame_typedef *fx)
{
uint16_t i;
uint16_t calchkval=0;
uint16_t framelen=0;
uint8_t sendbuf[UART_RX_LEN];
if(checkmode==M_FRAME_CHECK_CRC16)framelen=6+fx->datalen;
else framelen=5+fx->datalen;
sendbuf[0]=fx->address;
sendbuf[1]=fx->function;
sendbuf[2]=fx->count;
sendbuf[3]=fx->datalen;
for(i=0;i<fx->datalen;i++)
{
sendbuf[4+i]=fx->data[i];
}
switch(checkmode)
{
case M_FRAME_CHECK_SUM:
calchkval=mc_check_sum(sendbuf,fx->datalen+4);
break;
case M_FRAME_CHECK_XOR:
calchkval=mc_check_xor(sendbuf,fx->datalen+4);
break;
case M_FRAME_CHECK_CRC8:
calchkval=mc_check_crc8(sendbuf,fx->datalen+4);
break;
case M_FRAME_CHECK_CRC16:
calchkval=mc_check_crc16(sendbuf,fx->datalen+4);
break;
}
if(checkmode==M_FRAME_CHECK_CRC16)
{
sendbuf[4+fx->datalen]=(calchkval>>8)&0XFF;
sendbuf[5+fx->datalen]=calchkval&0XFF;
}else sendbuf[4+fx->datalen]=calchkval&0XFF;
HAL_UART_Transmit_DMA(&huart1, sendbuf, framelen);
}
m_result my_deal_frame(m_frame_typedef *fx)
{
if(fx->address == MY_ADDRESS)
{
switch(fx->function)
{
case 1:
{
My_Func_1();
}break;
case 2:
{
}break;
case 3:
{
}break;
case 4:
{
}break;
case 5:
{
}break;
case 6:
{
}break;
case 7:
{
}break;
case 8:
{
}break;
default:
return MR_FUNC_ERR;
}
}return MR_OK;
}
void My_Func_1(void)
{
m_frame_typedef txbuff;
txbuff.address=MY_ADDRESS;
txbuff.function=1;
txbuff.count=(COUNT++)%255;
txbuff.datalen=3;
txbuff.data[0]=0x01;
txbuff.data[1]=0x02;
txbuff.data[2]=0x03;
my_packsend_frame(&txbuff);
}
然后和校验、或校验、CRC8和CRC16校验的代码就不贴了,可以点击本文末尾的链接查看。
最后打开main.c将void main函数修改如下:
int main(void)
{
m_frame_typedef fx;
m_result res;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
HAL_UART_Receive_DMA(&huart1, UART_RX_BUF, UART_RX_LEN);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
while (1)
{
if(UART_RX_STA & 0X8000)
{
res=my_unpack_frame(&fx);
if(res==MR_OK)
{
my_deal_frame(&fx);
}
UART_RX_STA = 0;
}
}
}
可以编译运行一下,如果有错误可以查看一下头文件是否完整,左侧是否将你的新文件添加进来了。还有记得在main.h的适当位置include你的protocol.h和check.h文件。
最后的运行结果就是这个样子的->(勾选16进制发送与接收)
再编辑一条0x01的功能码命令,例如
地址位 | 功能位 | 帧序号 | 数据长度 | 数据内容 | 校验位 |
---|
01 | 01 | 01 | 01 | 00 | 04 |
一个简易的通信协议的设计就完成了,一般需要注意以下几个点,就是一般接收到一帧数据之后,将数据的各个部分都分别拆解到结构体中,这样可以非常方便的做后面的处理,同样打包一帧数据也是如此,只需要将结构体的各个部分写好,然后将数据帧结构体提交给发送函数就可以了,基本的思路就是这样的。
github:https://github.com/luodeb/USART1
如果有啥错误或不合理之处,请在评论区指出。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)