【嵌入式项目应用】__UART自定义通信协议代码实现方法

news2024/11/24 4:05:42

目录

前言

一、什么是通信协议

二、简单通信协议的问题

三、通信协议的常见内容

1. 帧头

2. 设备地址/类型

3. 命令/指令

4. 命令类型/功能码

5. 数据长度

 6. 数据

7.帧尾

8.校验码

四、通信协议代码实现

1. 消息数据发送

a. 通过串口直接发送每一个字节

b. 通过消息队列发送

c. 用“结构体”代替“数组SendBuf”方式 

d. 其他更多

2. 消息数据接收

a. 常规中断接收

b. 增加超时检测

c. 更多

三、结束语

(* ̄︶ ̄)创作不易!期待你们的 点赞、收藏和评论喔。


前言

我们学习单片机,首先接触的可能是点灯(GPIO),再次就是串口(UART)。

串口是常用的一种通信接口,也是学嵌入式必备掌握的一项知识,但我发现有很多小伙伴只知道用串口输出或者打印一些数据,却不知道如何用串口进行数据传输和通信。

这里就给大家分享一下串口通信协议、自定义通信协议,以及实现的原理。

一、什么是通信协议

通信协议不难理解,就是两个(或多个)设备之间进行通信,必须要遵循的一种协议。

百度百科的解释:

通信协议:是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。

交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。

应该有很多读者都买过一些基于串口通信的模块,市面上很多基于串口通信的模块都是自定义通信协议,有的比较简单,有的相对复杂一点。

举一个很简单的串口通信协议的例子:比如只传输一个温度值,只有三个字节的通信协议:

  1. 帧头
  2. 温度值
  3. 帧尾
5A一字节数值3B

这种看起来是不是很简单?它也是一种通信协议。

只是说这种通信协议应用的场合相对比较简单(一对一两个设备之间),同时,它存在很多弊端。

二、简单通信协议的问题

上面那种只有三个字节的通信协议,相信大家都看明白了。虽然它也能通信,也能传输数据,但它存在一系列的问题。

比如:多个设备连接在一条总线(比如485)上,怎么判断传输给谁?(没有设备信息)

还比如:处于一个干扰环境,你能保障传输数据正确吗?(没有校验信息)

再比如:我想传输多个不确定长度的数据,该怎么办?(没有长度信息)。

上面这一系列问题,相信做过自定义通信的朋友都了解。

所以,在通信协议里面要约定更多的“协议信息”,这样才能保证通信的完整。

三、通信协议的常见内容

基于串口的通信协议通常不能太复杂,因为串口通信速率、抗干扰能力以及其他各方面原因,相对于TCP/IP这种通信协议,是一种很轻量级的通信协议。

所以,基于串口的通信,除了一些通用的通信协议(比如:Modubs、MAVLink)之外,很多时候,工程师都会根据自己项目情况,自定义通信协议。

下面简单描述下常见自定义通信协议的一些要点内容。

(这是一些常见的协议内容,可能不同情况,其协议内容不同)

1. 帧头

帧头,就是一帧通信数据的开头。

有的通信协议帧头只有一个,有的有两个,比如:5A、A5作为帧头。

2. 设备地址/类型

设备地址或者设备类型,通常是用于多种设备之间,为了方便区分不同设备。

这种情况,需要在协议或者附录中要描述各种设备类型信息,方便开发者编码查询。

当然,有些固定的两种设备之间通信,可能没有这个选项。

3. 命令/指令

命令/指令比较常见,一般是不同的操作,用不同的命令来区分。

举例:温度:0x01;湿度:0x02;

4. 命令类型/功能码

这个选项对命令进一步补充。比如:读、写操作。

举例:读Flash:0x01; 写Flash:0x02;

5. 数据长度

数据长度这个选项,可能有的协议会把该选项提到前面设备地址位置,把命令这些信息算在“长度”里面。

这个主要是方便协议(接收)解析的时候,统计接收数据长度。

比如:有时候传输一个有效数据,有时候要传输多个有效数据,甚至传输一个数组的数据。这个时候,传输的一帧数据就是不定长数据,就必须要有数据长度来约束。

有的长度是一个字节,其范围:0x01 ~ 0xFF,有的可能要求一次性传输更多,就用两个字节表示,其范围0x0001 ~ 0xFFFFF。

当然,有的通信长度是固定的长度(比如固定只传输、温度、湿度这两个数据),其协议可能没有这个选项。

 6. 数据

数据就不用描述了,就是你传输的实实在在的数据,比如温度:25℃。

7.帧尾

有些协议可能没有帧尾,这个应该是可有可无的一个选项。

8.校验码

校验码是一个比较重要的内容,一般正规一点的通信协议都有这个选项,原因很简单,通信很容易受到干扰,或者其他原因,导致传输数据出错。

如果有校验码,就能比较有效避免数据传输出错的的情况。

校验码的方式有很多,校验和、CRC校验算是比较常见的,用于自定义协议中的校验方式。

还有一点,有的协议可能把校验码放在倒数第二,帧尾放在最后位置。

四、通信协议代码实现

自定义通信协议,代码实现的方式有很多种,怎么说呢,“条条大路通罗马”你只需要按照你协议要写实现代码就行。

当然,实现的同时,需要考虑你项目实际情况,比如通信数据比较多,要用消息队列(FIFO),还比如,如果协议复杂,最好封装结构体等。

下面分享一些以前用到的代码,可能没有描述更多细节,但一些思想可以借鉴。

1. 消息数据发送

a. 通过串口直接发送每一个字节

这种对于新手来说都能理解,这里分享一个之前DGUS串口屏的例子:


#define DGUS_FRAME_HEAD1          0xA5                     //DGUS屏帧头1
#define DGUS_FRAME_HEAD2          0x5A                     //DGUS屏帧头2

#define DGUS_CMD_W_REG            0x80                     //DGUS写寄存器指令
#define DGUS_CMD_R_REG            0x81                     //DGUS读寄存器指令
#define DGUS_CMD_W_DATA           0x82                     //DGUS写数据指令
#define DGUS_CMD_R_DATA           0x83                     //DGUS读数据指令
#define DGUS_CMD_W_CURVE          0x85                     //DGUS写曲线指令

/* DGUS寄存器地址 */
#define DGUS_REG_VERSION          0x00                     //DGUS版本
#define DGUS_REG_LED_NOW          0x01                     //LED背光亮度
#define DGUS_REG_BZ_TIME          0x02                     //蜂鸣器时长
#define DGUS_REG_PIC_ID           0x03                     //显示页面ID
#define DGUS_REG_TP_FLAG          0x05                     //触摸坐标更新标志
#define DGUS_REG_TP_STATUS        0x06                     //坐标状态
#define DGUS_REG_TP_POSITION      0x07                     //坐标位置
#define DGUS_REG_TPC_ENABLE       0x0B                     //触控使能
#define DGUS_REG_RTC_NOW          0x20                     //当前RTCS

//往DGDS屏指定寄存器写一字节数据
void DGUS_REG_WriteWord(uint8_t RegAddr, uint16_t Data)
{
  DGUS_SendByte(DGUS_FRAME_HEAD1);
  DGUS_SendByte(DGUS_FRAME_HEAD2);
  DGUS_SendByte(0x04);

  DGUS_SendByte(DGUS_CMD_W_REG);                 //指令
  DGUS_SendByte(RegAddr);                        //地址

  DGUS_SendByte((uint8_t)(Data>>8));             //数据
  DGUS_SendByte((uint8_t)(Data&0xFF));
}

//往DGDS屏指定地址写一字节数据
void DGUS_DATA_WriteWord(uint16_t DataAddr, uint16_t Data)
{
  DGUS_SendByte(DGUS_FRAME_HEAD1);
  DGUS_SendByte(DGUS_FRAME_HEAD2);
  DGUS_SendByte(0x05);

  DGUS_SendByte(DGUS_CMD_W_DATA);                //指令

  DGUS_SendByte((uint8_t)(DataAddr>>8));         //地址
  DGUS_SendByte((uint8_t)(DataAddr&0xFF));

  DGUS_SendByte((uint8_t)(Data>>8));             //数据
  DGUS_SendByte((uint8_t)(Data&0xFF));
}

b. 通过消息队列发送

在上面基础上,用一个buf装下消息,然后“打包”到消息队列,通过消息队列的方式(FIFO)发送出去。

static uint8_t  sDGUS_SendBuf[DGUS_PACKAGE_LEN];

//往DGDS屏指定寄存器写一字节数据
void DGUS_REG_WriteWord(uint8_t RegAddr, uint16_t Data)
{
  sDGUS_SendBuf[0] = DGUS_FRAME_HEAD1;           //帧头
  sDGUS_SendBuf[1] = DGUS_FRAME_HEAD2;
  sDGUS_SendBuf[2] = 0x06;                       //长度
  sDGUS_SendBuf[3] = DGUS_CMD_W_CTRL;            //指令
  sDGUS_SendBuf[4] = RegAddr;                    //地址
  sDGUS_SendBuf[5] = (uint8_t)(Data>>8);         //数据
  sDGUS_SendBuf[6] = (uint8_t)(Data&0xFF);

  DGUS_CRC16(&sDGUS_SendBuf[3], sDGUS_SendBuf[2] - 2, &sDGUS_CRC_H, &sDGUS_CRC_L);
  sDGUS_SendBuf[7] = sDGUS_CRC_H;                //校验
  sDGUS_SendBuf[8] = sDGUS_CRC_L;

  DGUSSend_Packet_ToQueue(sDGUS_SendBuf, sDGUS_SendBuf[2] + 3);
}

//往DGDS屏指定地址写一字节数据
void DGUS_DATA_WriteWord(uint16_t DataAddr, uint16_t Data)
{
  sDGUS_SendBuf[0] = DGUS_FRAME_HEAD1;           //帧头
  sDGUS_SendBuf[1] = DGUS_FRAME_HEAD2;
  sDGUS_SendBuf[2] = 0x07;                       //长度
  sDGUS_SendBuf[3] = DGUS_CMD_W_DATA;            //指令
  sDGUS_SendBuf[4] = (uint8_t)(DataAddr>>8);     //地址
  sDGUS_SendBuf[5] = (uint8_t)(DataAddr&0xFF);
  sDGUS_SendBuf[6] = (uint8_t)(Data>>8);         //数据
  sDGUS_SendBuf[7] = (uint8_t)(Data&0xFF);

  DGUS_CRC16(&sDGUS_SendBuf[3], sDGUS_SendBuf[2] - 2, &sDGUS_CRC_H, &sDGUS_CRC_L);
  sDGUS_SendBuf[8] = sDGUS_CRC_H;                //校验
  sDGUS_SendBuf[9] = sDGUS_CRC_L;

  DGUSSend_Packet_ToQueue(sDGUS_SendBuf, sDGUS_SendBuf[2] + 3);
}

c. 用“结构体代替数组SendBuf”方式 

结构体对数组更方便引用,也方便管理,所以,结构体方式相比数组buf更高级,也更实用。

(当然,如果成员比较多,如果用临时变量方式也会导致占用过多堆栈的情况)

比如:

typedef struct
{
  uint8_t  Head1;                 //帧头1
  uint8_t  Head2;                 //帧头2
  uint8_t  Len;                   //长度
  uint8_t  Cmd;                   //命令
  uint8_t  Data[DGUS_DATA_LEN];   //数据
  uint16_t CRC16;                 //CRC校验
}DGUS_PACKAGE_TypeDef;

d. 其他更多

串口发送数据的方式有很多,比如用DMA的方式替代消息队列的方式。

2. 消息数据接收

串口消息接收,通常串口中断接收的方式居多,当然,也有很少情况用轮询的方式接收数据。

a. 常规中断接收

还是以DGUS串口屏为例,描述一种简单又常见的中断接收方式:

void DGUS_ISRHandler(uint8_t Data)
{
  static uint8_t sDgus_RxNum = 0;                //数量
  static uint8_t sDgus_RxBuf[DGUS_PACKAGE_LEN];
  static portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;

  sDgus_RxBuf[gDGUS_RxCnt] = Data;
  gDGUS_RxCnt++;

  /* 判断帧头 */
  if(sDgus_RxBuf[0] != DGUS_FRAME_HEAD1)       //接收到帧头1
  {
    gDGUS_RxCnt = 0;
    return;
  }
  if((2 == gDGUS_RxCnt) && (sDgus_RxBuf[1] != DGUS_FRAME_HEAD2))
  {
    gDGUS_RxCnt = 0;
    return;
  }

  /* 确定一帧数据长度 */
  if(gDGUS_RxCnt == 3)
  {
    sDgus_RxNum = sDgus_RxBuf[2] + 3;
  }

  /* 接收完一帧数据 */
  if((6 <= gDGUS_RxCnt) && (sDgus_RxNum <= gDGUS_RxCnt))
  {
    gDGUS_RxCnt = 0;

    if(xDGUSRcvQueue != NULL)                    //解析成功, 加入队列
    {
      xQueueSendFromISR(xDGUSRcvQueue, &sDgus_RxBuf[0], &xHigherPriorityTaskWoken);
      portEND_SWITCHING_ISR(xHigherPriorityTaskWoken);
    }
  }
}

b. 增加超时检测

接收数据有可能存在接收了一半,中断因为某种原因中断了,这时候,超时检测也很有必要。

比如:用多余的MCU定时器做一个超时计数的处理,接收到一个数据,开始计时,超过1ms没有接收到下一个数据,就丢掉这一包(前面接收的)数据。

static void DGUS_TimingAndUpdate(uint16_t Nms)
{
  sDGUSTiming_Nms_Num = Nms;
  TIM_SetCounter(DGUS_TIM, 0);                   //设置计数值为0
  TIM_Cmd(DGUS_TIM, ENABLE);                     //启动定时器
}

void DGUS_COM_IRQHandler(void)
{
  if((DGUS_COM->SR & USART_FLAG_RXNE) == USART_FLAG_RXNE)
  {
    DGUS_TimingAndUpdate(5);                     //更新定时(防止超时)
    DGUS_ISRHandler((uint8_t)USART_ReceiveData(DGUS_COM));
  }
}

c. 更多

接收和发送一样,实现方法有很多种,比如接收同样也可以用结构体方式。但有一点,都需要结合你实际需求来编码。

三、结束语

以上自定义协议内容仅供参考,最终用哪些、占用几个字节都与你实际需求有关。

基于串口的自定义通信协议,有千差万别,比如:MCU处理能力、设备多少、通信内容等都与你自定义协议有关。

有的可能只需要很简单的通信协议就能满足要求。有的可能需要更复杂的协议才能满足。

注意的是:

  1. 以上举例并不是完整的代码(有些细节没有描述出来),主要是供大家学习这种编程思想,或者实现方式。
  2. 一份好的通信协议代码,必定有一定容错处理,比如:发送完成检测、接收超时检测、数据出错检测等等。所以说,以上代码并不是完整的代码。

 


(* ̄︶ ̄)创作不易!期待你们的 点赞收藏评论喔。

本文来源网络,免费分享知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1161904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

16. 机器学习 - 决策树

Hi&#xff0c;你好。我是茶桁。 在上一节课讲SVM之后&#xff0c;再给大家将一个新的分类模型「决策树」。我们直接开始正题。 决策树 我们从一个例子开始&#xff0c;来看下面这张图&#xff1a; 假设我们的x1 ~ x4是特征&#xff0c;y是最终的决定&#xff0c;打比方说是…

十年JAVA搬砖路——Linux搭建Ldap服务器。

1.安装命令 yum -y install openldap compat-openldap openldap-clients openldap-servers openldap-servers-sql openldap-devel2.启动ldap systemctl start slapd systemctl enable slapd3.修改密码 slappasswd Aa123456获得返回的密码加密密码串&#xff1a; {SSHA}DkSw0…

SQLServer数据库透明加密 安当加密

安当TDE透明加密组件是一种用于数据保护的解决方案&#xff0c;它对数据进行加密&#xff0c;以防止未经授权的访问和数据泄露。 以下是安当TDE透明加密组件的主要功能介绍&#xff1a; 数据保护&#xff1a;安当TDE透明加密组件可以对数据库中的敏感数据进行加密&#xff0c;…

软件无线电处理平台解决方案:330-基于FMC接口的Kintex-7 XC7K325T PCIeX4 3U PXIe接口卡

基于FMC接口的Kintex-7 XC7K325T PCIeX4 3U PXIe接口卡 一、板卡概述 本板卡基于Xilinx公司的FPGAXC7K325T-2FFG900 芯片&#xff0c;pin_to_pin兼容FPGAXC7K410T-2FFG900 &#xff0c;支持PCIeX8、64bit DDR3容量2GByte&#xff0c;HPC的FMC连接器&#xff0c;北京太速科…

TypeScript之命名空间与模块

一、模块 TypeScript 与ECMAScript 2015 一样&#xff0c;任何包含顶级 import 或者 export 的文件都被当成一个模块 相反地&#xff0c;如果一个文件不带有顶级的import或者export声明&#xff0c;那么它的内容被视为全局可见的 例如我们在在一个 TypeScript 工程下建立一个…

Linux 网络流量监控利器 iftop命令详解及实战

简介 iftop 是什么 在 Linux 系统下即时监控服务器的网络带宽使用情况&#xff0c;有很多工具&#xff0c;比如 iptraf、nethogs 等等&#xff0c;但是推荐使用小巧但功能很强大的 iftop 工具。 iftop 是 Linux 系统一个免费的网卡实时流量监控工具&#xff0c;类似于 top 命令…

智能防雷浪涌保护器的行业应用

智能浪涌保护器是一种能够自动监测和控制电涌保护器&#xff08;SPD&#xff09;的工作状态&#xff0c;实现SPD的自我保护和远程管理的设备。智能防雷是一种将云计算、移动互联网和物联网技术引入到综合防雷措施中&#xff0c;实现雷电预警、智能化防雷、智能化监测的系统。这…

群晖 | Synology Directory Server 批量导入用户 文件模板格式

目录 错误写法 正确写法 错误写法 在网上找到过类似的教程&#xff0c;但是一律都以失败告终&#xff1a; 正确写法 其实并不是只要写上前面的属性即可&#xff0c; 就算后面不写也需要使用 tab 补齐 &#xff0c;所有的属性&#xff1a; 1.名称 2.密码 3.描述 4…

开源问答平台网站源码系统商业运营版源码系统 带完整的搭建教程

在我们现在的日常生活中&#xff0c;人们对于获取信息的需求越来越大&#xff0c;而问答平台作为一种快速、有效的信息获取方式&#xff0c;受到了广泛的关注和使用。同时&#xff0c;随着开源技术的普及和成熟&#xff0c;越来越多的开发者选择使用开源技术进行网站的开发和维…

几种常见的接地类型详解

接地作为一种应用最为广泛的电气安全措施&#xff0c;是指电力系统和电气装置的中性点、电气设备的外露导电部分和装置外导电部分经由导体与大地相连。接地的作用主要是防止人身遭受电击、设备和线路遭受损坏、预防火灾和防止雷击、防止静电损害和保障电力系统正常运行。按其功…

关于 iOS 报Multiple commands produceMultiple 错误的解决方案

今天在运行一个RN老项目的时候&#xff0c;报了一个下面的错误。 对应的信息如下&#xff1a; Multiple commands produce /Users/xzh/RN/work/cgv_app/ios/build/Debug-iphonesimulator/cgv_app.app/Entypo.ttf Multiple commands produce /Users/xzh/RN/work/cgv_app/ios/bu…

EASYX实现多物体运动

eg1:单个物体运动使用easyx实现单个小球的运动 #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #include <stdlib.h> #include <conio.h> #include <time.h> #define PI 3.14 #define NODE_WIDTH 4…

应用在阀门控制中的直流有刷驱动芯片

控制阀又称阀门&#xff0c;是流体运送系统中的控制部件&#xff0c;具有导流、截流、调节、节流、防止倒流、分流或溢流卸压等功能。阀门是一种用于控制流体&#xff08;液体、气体、粉体等&#xff09;流动的装置&#xff0c;广泛应用于工业生产、建筑、农业、能源等领域。 …

提高效率!5个顶级网页开发工具助你事半功倍!

1.WordPress–一站式网站建设工具 价格&#xff1a;基本版终身免费&#xff0c;个人版HK$30&#xff0c;高级版HK$63&#xff0c;商务版HK$1993 推荐指数&#xff1a;★★★★ WordPress是一个使用PHP语言开发的博客平台。它可以在PHP和MySQL数据库的服务器上设置自己的网站&…

从项目开始学习Vue——01

目录标题 一、官方文档二、搭建环境三、停止服务四、使用 webstorm 一键创建项目创建过程可能出现错误 五、启动和访问项目&#xff08;一&#xff09;启动&#xff08;二&#xff09;访问项目六、项目目录讲解 一、官方文档 https://cn.vuejs.org/ 二、搭建环境 参考&#…

新版开源UI千月影视APP源码/后端基于ThinkPHP框架/前后端完美匹配

源码简介&#xff1a; 开源UI千月影视APP源码&#xff0c;它是基于ThinkPHP框架&#xff0c;而且前后端完美匹配。这是一个广泛使用的PHP开发框架&#xff0c;具有稳定性和安全性方面的优势。 2023版本UI千月影视APP是一款提供电影、电视剧、综艺节目等视频内容的应用程序&am…

如何使用ps制作ico图标文件

如何使用ps制作ico图标文件 Chapter1 如何使用ps制作ico图标文件Chapter2 ICOFormat.8bi&#xff08;Photoshop Ico、Cur插件&#xff09;的下载使用1. ICOFormat.8bi的作用2. ICOFormat.8bi使用 Chapter3 ps手机计算机图标教程,手绘设计精美手机APP软件图标的PS教程步骤 01 制…

ArcGIS Pro怎么生成高程点

一般情况下&#xff0c;我们从公开渠道获取到的高程数据都是DEM数据&#xff0c;但是如果要用到CAD等软件内则需要用到高程点&#xff0c;那么如何从DEM提取高程点呢&#xff0c;这里为大家介绍一下生成方法&#xff0c;希望能对你有所帮助。 数据来源 本教程所使用的数据是…

Java中的继承和多态

目录 1. 继承 1.1 为什么需要继承 1.2 继承概念 1.3 继承的语法 1.4 父类成员访问 1.4.1 子类中访问父类的成员变量 1.4.2 子类中访问父类的成员方法 1.5 super关键字 1.6 子类构造方法 1.7 super和this 1.8 再谈初始化 1.9 protected 关键字 1.10 继承方式…

【ElasticSearch系列-03】ElasticSearch的高级句法查询Query DSL

ElasticSearch系列整体栏目 内容链接地址【一】ElasticSearch下载和安装https://zhenghuisheng.blog.csdn.net/article/details/129260827【二】ElasticSearch概念和基本操作https://blog.csdn.net/zhenghuishengq/article/details/134121631【二】ElasticSearch的高级查询Quer…