本文最后更新于:7 个月前
不同设备之间的通信,都需要设计自己的通信协议。为了保证设备与设备之间的数据的稳定传输,通信协议的设计需要考虑很多的问题。当然应对不同的应用场景,可以有针对性的设计不同的通信协议。
一种常见的通信协议格式
这是一种我们比较常见的通信协议格式
帧头部 |
地址位 |
功能位 |
帧序号 |
数据长度 |
数据内容 |
校验位 |
帧尾 |
1/2字节 |
1字节 |
1字节 |
1字节 |
1字节 |
n字节 |
1字节 |
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
打开工程,并在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中断。修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #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文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| #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函数修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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上,可以点击这里查看:USART1
如果有啥错误或不合理之处,请在评论区指出。