STM32-串口解析框架

STM32 UART 是最基础的通信接口。本文介绍一种基于 STM32 UART通信协议解析框架。与其说是一种解析框架,不如说是一种解析架构,一种解析逻辑更为准确。

测试环境:

  • Master MCU: STM32F103RCT6
  • Slave Module:尚鑫航 SXH485 H200 串口摄像机模组
  • RTOS:无,裸机系统
  • Libraries:STSW-STM32054 3.6.0 标准库

整个工程源码可参考 GitHub 仓库 UART_Parse

通信协议

简单介绍下 STM32SXH485 H200 串口摄像机模组 之间的通信协议。

D0D1D2D3D4D5D6 ~ DnC1C2
帧头1帧头2地址命令数据长度(低位)数据长度(高位)数据CRC 校验(低位)CRC 校验(高位)
1 Byte1 Byte1 Byte1 Byte1 Byte1 Byte0 Byte ~ 65535 Bytes1 Byte1 Byte
90HEBHAddrCmdLenLLenHDataCrcLCrcH

整个帧分为以下 6 个部分:

  1. 帧头 (2B)
  2. 地址 (1B)
  3. 命令 (1B)
  4. 数据长度 (2B)
  5. 数据 (0B ~ 65535B)
  6. CRC 校验 (2B)
序号域名说明
1帧头固定为 90H EBH
2地址摄像头地址,范围为 01H ~ FEH。00H 和 FFH 为保留地址(广播地址);摄像头收到非本机地址帧不做响应
3命令
4数据长度代表数据域的长度,范围为 0 ~ 65535
5数据根据命令的不同,数据域中的内容也不尽相同
6CRC 校验16 位 CRC 校验,从地址域开始直到数据域,校验长度为 = 数据长度 + 4B

本文档 重点在于串口解析框架,故这里以三种命令来介绍解析框架:

  1. 测试命令
  2. 拍照命令
  3. 分包取图命令

命令示例

测试命令

测试命令用于测试通信情况。

示例:

MCU 发:90 EB 01 01 02 00 55 AA C1 C2

SXH485 H200 返回:90 EB 01 01 03 00 00 AA 55 F6 EB

测试命令

拍照命令用于触发 SXH485 H200 拍照。

数据域大小序号域名大小说明
4 Bytes1分包大小2 Bytes分包大小(摄像头模组内部保留无实际作用)。整包时为 0,立即返回图片数据,分包时非 0 (建议直接设置为 02H 00H - 厂家反馈)
2分辨率1 Byte
3压缩比1 Byte1~10 级,越小越清,但 JPEG 图片数据越大 (1 到 5 作用比较大,往后面可能没用作了 - 厂家反馈)

示例:

MCU 发:90 EB 01 40 04 00 00 02 05 01 C1 C2

SXH485 H200 返回:90 EB 01 40 0B 00 00 BD C4 00 00 63 00 00 02 05 01 E0 EC

分包取图命令

分包取图命令用于获取图片数据。

数据域大小序号域名典型值大小说明
6 Bytes1开始地址4 Bytes指定从 JPEG 图片开始地址处取数据
2指定从开始地址处获取的数据长度2 Bytes最大不超过 4K

示例:

MCU 发:90 EB 01 48 06 00 00 00 00 00 00 04 C1 C2

SXH485 H200 返回:90 EB 01 49 00 04 FF D8 … 6B 11

解析逻辑

整个解析逻辑可以概括为:UART 中断 + 定时器 + 状态机

通过 UART 的中断标志位 RXNE 完成数据的接收;通过 UART 的中断标志位 IDLE 来判断帧结束;通过定时器来判断接收超时;通过状态机来解析接收到的帧

UART 中断接收

在 UART 中断处理函数主要完成以下 2 件事情:

  • RXNE 标志位:将从串口接收到的数据暂存到 buf 中
  • IDLE 标志位:帧接收完成,关闭 RXNE 和 IDLE 中断,关闭定时器,之后进行帧处理

尽量不要在 RXNE 标志位中去进行帧处理,RXNE 标志位只负责接收数据到 buf 中,中断处理尽量做到简洁。在 IDLE 标志位进行帧处理是因为已提前关闭了 RXNE 和 IDLE 中断,所以中断不会再次触发,可以进行帧处理

/*** @brief  This function handles USARTy global interrupt request.* @param  None* @retval None*/
void USART2_IRQHandler(void)
{if (USART_GetITStatus(SXH485_H200_UART, USART_IT_RXNE) != RESET) {USART_ClearITPendingBit(SXH485_H200_UART, USART_IT_RXNE);/* Read one byte from the receive data register */g_arr_rx_buf[g_u16_rx_buf_thread_size++] = USART_ReceiveData(SXH485_H200_UART);if (g_u16_rx_buf_thread_size == SXH485_H200_UART_RX_BUFF_SIZE) {/* Disable the USARTy Receive interrupt */USART_ITConfig(SXH485_H200_UART, USART_IT_RXNE, DISABLE);}} else if (USART_GetITStatus(SXH485_H200_UART, USART_IT_IDLE) != RESET) {/* Clear IDLE bit */USART_ReceiveData(SXH485_H200_UART);USART_ClearITPendingBit(SXH485_H200_UART, USART_IT_IDLE);USART_ITConfig(SXH485_H200_UART, USART_IT_RXNE, DISABLE);USART_ITConfig(SXH485_H200_UART, USART_IT_IDLE, DISABLE);sxh485_h200_timer_stop();sxh485_h200_frame_handle();}
}

状态机 (帧处理)

整个帧处理基于 状态机 来完成。一个一个字节进行处理,当一个状态完成时才切换到下一个状态。当整个解析完成时,会设置串口接收标志。

typedef enum
{SXH485_H200_FRAME_HEADER_1_STATUS,SXH485_H200_FRAME_HEADER_2_STATUS,SXH485_H200_FRAME_ADDR_STATUS,SXH485_H200_FRAME_CMD_STATUS,SXH485_H200_FRAME_LEN_L_STATUS,SXH485_H200_FRAME_LEN_H_STATUS,SXH485_H200_FRAME_DATA_STATUS,SXH485_H200_FRAME_CRC_L_STATUS,SXH485_H200_FRAME_CRC_H_STATUS,SXH485_H200_FRAME_MAX_STATUS,
} sxh485_h200_frame_status;
/*** @brief  Command handle.* @retval None.* @retval None.*/
void sxh485_h200_frame_handle(void)
{uint16_t len = 0;uint16_t recv_crc = 0;uint16_t calc_crc = 0;while (1) {switch (s_st_sxh485_h200_frame_status) {case SXH485_H200_FRAME_HEADER_1_STATUS: {if (g_arr_rx_buf[SXH485_H200_FRAME_HEADER_1_INDEX] != SXH485_H200_FRAME_HEADER_1) {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_1_STATUS;sxh485_h200_recv_flag_set(SXH485_H200_RECV_ERROR);return;}s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_2_STATUS;break;}case SXH485_H200_FRAME_HEADER_2_STATUS: {if (g_arr_rx_buf[SXH485_H200_FRAME_HEADER_2_INDEX] != SXH485_H200_FRAME_HEADER_2) {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_1_STATUS;sxh485_h200_recv_flag_set(SXH485_H200_RECV_ERROR);return;}s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_ADDR_STATUS;break;}case SXH485_H200_FRAME_ADDR_STATUS: {if (g_arr_rx_buf[SXH485_H200_FRAME_ADDR_INDEX] != s_u8_addr) {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_1_STATUS;sxh485_h200_recv_flag_set(SXH485_H200_RECV_ERROR);return;}s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_CMD_STATUS;break;}case SXH485_H200_FRAME_CMD_STATUS: {if (g_arr_rx_buf[SXH485_H200_FRAME_CMD_INDEX] != s_u8_cmd) {if ((g_arr_rx_buf[SXH485_H200_FRAME_CMD_INDEX] == SXH485_H200_FRAME_CMD_PIC_GET_RECV) &&(SXH485_H200_FRAME_CMD_PIC_GET == s_u8_cmd)) {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_LEN_L_STATUS;} else {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_1_STATUS;sxh485_h200_recv_flag_set(SXH485_H200_RECV_ERROR);return;}} else {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_LEN_L_STATUS;}break;}case SXH485_H200_FRAME_LEN_L_STATUS: {len = 0;len = g_arr_rx_buf[SXH485_H200_FRAME_LEN_L_INDEX];s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_LEN_H_STATUS;break;}case SXH485_H200_FRAME_LEN_H_STATUS: {uint16_t len_h = g_arr_rx_buf[SXH485_H200_FRAME_LEN_H_INDEX];len |= (len_h << 8);s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_DATA_STATUS;break;}case SXH485_H200_FRAME_DATA_STATUS: {s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_CRC_L_STATUS;break;}case SXH485_H200_FRAME_CRC_L_STATUS: {recv_crc = 0;recv_crc = g_arr_rx_buf[SXH485_H200_FRAME_DATA_INDEX + len];s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_CRC_H_STATUS;break;}case SXH485_H200_FRAME_CRC_H_STATUS: {uint16_t recv_crc_h = g_arr_rx_buf[SXH485_H200_FRAME_DATA_INDEX + len + 1];recv_crc |= (recv_crc_h << 8);calc_crc = crc16(&g_arr_rx_buf[SXH485_H200_FRAME_ADDR_INDEX], len + 4);if (recv_crc != calc_crc) {sxh485_h200_recv_flag_set(SXH485_H200_RECV_CRC_ERROR);} else {sxh485_h200_cmd_handle(s_u8_cmd, &g_arr_rx_buf[SXH485_H200_FRAME_DATA_INDEX]);sxh485_h200_recv_flag_set(SXH485_H200_RECV_OK);}s_st_sxh485_h200_frame_status = SXH485_H200_FRAME_HEADER_1_STATUS;return;}default:break;}}
}

定时器 (超时处理)

MCU 每发送一帧数据给 SXH485 H200 后,启动定时器。在定时器中断处理中完成以下事情:

  1. 关闭 RXNE 和 IDLE 中断
  2. 停止定时器
  3. 将接收标志设置为超时
/*** @brief  This function handles TIMx global interrupt request.* @param  None* @retval None*/
void TIM6_IRQHandler(void)
{if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) {TIM_ClearITPendingBit(TIM6 , TIM_FLAG_Update);USART_ITConfig(SXH485_H200_UART, USART_IT_RXNE, DISABLE);USART_ITConfig(SXH485_H200_UART, USART_IT_IDLE, DISABLE);USART_ClearITPendingBit(SXH485_H200_UART, USART_IT_RXNE);USART_ReceiveData(SXH485_H200_UART);USART_ClearITPendingBit(SXH485_H200_UART, USART_IT_IDLE);sxh485_h200_timer_stop();sxh485_h200_recv_flag_set(SXH485_H200_RECV_TIMEOUT);}
}

应用

以测试命令为例,当 MCU 发送测试命令之后,将串口接收标志设置为 receiving 状态, 一直等待串口接收标志变为非 receiving 状态后,说明帧处理完毕或者超时,返回结果。

/*** @brief  Check SXH485 H200 status* @param  status: Pointer to SXH485 H200 status* @param  timeout: Timeout. Unit in seconds.* @retval SXH485 H200 operation status.*   This parameter can be a value of @ref sxh485_h200_ops_status.*/
sxh485_h200_ops_status sxh485_h200_check(sxh485_h200_status* status, uint32_t timeout)
{uint8_t send_size = 0;sxh485_h200_recv_status recv_status = SXH485_H200_RECEIVING;s_u8_cmd = SXH485_H200_FRAME_CMD_TEST;sxh485_h200_buff_init();send_size = sxh485_h200_test_frame_init(s_u8_addr, g_arr_tx_buf);sxh485_h200_send(g_arr_tx_buf, send_size);sxh485_h200_recv_flag_set(SXH485_H200_RECEIVING);sxh485_h200_timer_cfg(timeout);recv_status = sxh485_h200_recv_flag_get();while (recv_status == SXH485_H200_RECEIVING) {recv_status = sxh485_h200_recv_flag_get();}if (recv_status == SXH485_H200_RECV_OK) {*status = g_st_sxh485_h200_status;return SXH485_H200_OK;} else {return SXH485_H200_FAIL;}
}

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

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

相关文章

运维之道—生产环境安装Redis

目录 1.前言 2.环境准备 2.1 安装gcc依赖 3.部署安装 3.1 下载redis安装包 3.2 解压并编译安装redis 3.3 配置redis ​编辑3.4 启动redis并测试 4. 总结 1.前言 大家好,运维之道的系列文章继续进行,我们今天整理的是Redis生产环境的安装,Redis的安装以及生产环境的…

【数据结构与算法】1.数据结构绪论

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有限&#xff0c;欢迎各位大佬指点&…

10-数组-区域和检索-数组不可变

这是数组的第10篇算法&#xff0c;力扣链接。 给定一个整数数组 nums&#xff0c;处理以下类型的多个查询: 计算索引 left 和 right &#xff08;包含 left 和 right&#xff09;之间的 nums 元素的 和 &#xff0c;其中 left < right 实现 NumArray 类&#xff1a; NumArr…

EOCR电机保护器带煤电厂的具体应用

EOCR系列电动机智能保护器在煤电厂中有广泛的应用。这些保护器具有齐全的保护功能、直观的测量参数、快速的反应灵敏度、可靠的行动以及与上位机通讯构成远程监控的能力&#xff0c;使其成为理想的低压电动机保护及远程监控产品。 在煤电厂中&#xff0c;电动机保护器需要具备过…

【Linux多线程】生产者消费者模型

目录 生产者消费者模型 1. 生产者消费者模式的概念 2. 生产者消费者模型优点 ​编辑3. 生产者消费者模型的特点 基于BlockingQueue&#xff08;阻塞队列&#xff09;的生产者消费者模型 1.BlockingQueue 2. 使用CSTL中的queue来模拟实现阻塞队列 3. 基于任务的生产者消费…

三天吃透Java集合面试八股文

内容摘自我的学习网站&#xff1a;topjavaer.cn 常见的集合有哪些&#xff1f; Java集合类主要由两个接口Collection和Map派生出来的&#xff0c;Collection有三个子接口&#xff1a;List、Set、Queue。 Java集合框架图如下&#xff1a; List代表了有序可重复集合&#xff0c…

Modbus网关BL101 既实现Modbus转MQTT,还能当串口服务器使用

随着工业4.0的迅猛发展&#xff0c;人们深刻认识到在工业生产和生活中&#xff0c;实时、可靠、安全的数据传输至关重要。在此背景下&#xff0c;高性能的工业电力数据传输解决方案——协议转换网关应运而生&#xff0c;广泛应用于工业自动化系统、远程监控和物联网应用应用环境…

Linux第34步_TF-A移植的第2步_修改设备树和tf-a.tsv

在虚拟机中&#xff0c;使用VSCode打开linux /atk-mp1/atk-mp1/my-tfa/目录下tf-a.code-workspace”&#xff1b; 找到“tf-a-stm32mp-2.2.r1/fdts”目录&#xff0c;就是设备树文件所在的目录。 见下图&#xff1a; 一、修改“stm32mp157d-atk.dts” 修改后&#xff0c;见下…

【VTKExamples::PolyData】第十一期 ExtractPolyLinesFromPolyData

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享VTK样例ExtractPolyLinesFromPolyData,并解析vtkCutter & vtkStripper,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)…

课题学习(十九)----Allan方差:陀螺仪噪声分析

一、介绍 Allan方差是一种分析时域数据序列的方法&#xff0c;用于测量振荡器的频率稳定性。该方法还可用于确定系统中作为平均时间函数的本征噪声。该方法易于计算和理解&#xff0c;是目前最流行的识别和量化惯性传感器数据中存在的不同噪声项的方法之一。该方法的结果与适用…

Window安装Python和开发Pycharm

准备&#xff1a; 1&#xff1a;安装Python环境 https://www.python.org/downloads/windows/ 2: 下载Pycharm https://www.jetbrains.com/pycharm/download/other.html

openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在

文章目录 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-修改索引时只调用索引名提示索引不存在203.1 修改索引时只调用索引名提示索引不存在203.1.1 问题现象203.1.2 原因分析203.1.3 处理办法 openGauss学习笔记-203 openGauss 数据库运维-常见故障定位案例-…

Oracle1 数据库管理

Oracle的安装 一、基础表的创建 1.1 切换到scott用户 用sys 账户 登录 解锁scott账户 alter user scott account unlock;conn scott/tiger;发现并不存在scott账户&#xff0c;自己创建一个&#xff1f; 查找资料后发现&#xff0c;scott用户的脚本需要自己执行一下 C:\ap…

C++ //练习 2.28 说明下面的这些定义是什么意思,挑出其中不合法的。

C Primer&#xff08;第5版&#xff09; 练习 2.28 练习 2.28 说明下面的这些定义是什么意思&#xff0c;挑出其中不合法的。 ( a ) int i, *const cp; ( b ) int *p1, *const p2; ( c ) const int ic, &r ic; ( d ) const int *const p3; ( e ) const int *p; 环境…

多服务器对外提供一个浮动ip

浅调研了下浮动ip方案。主要是用来做高可用/灾备切换&#xff0c;同一时间只有一个服务器提供服务。 三个问题 无论是什么方案&#xff0c;都要解决这三个问题&#xff1a; 如何配置浮动ip如何实现故障检测如何切换浮动ip 浮动IP方案 第一种&#xff1a;主备手动配置ip&am…

cfssl简单使用

1、安装 方式1&#xff1a;直接下载 详见&#xff1a;手动生成证书 | Kubernetes # 1、下载cfssl、cfssljson、cfssl-certinfo # cfssl&#xff1a;用于签发证书 # cfssljson&#xff1a;将cfssl签发生成的证书(json格式)变成文件承载式文件 # cfssl-certinfo&#xff1a;验…

Docker设置获取环境变量

在Dockerfile中设置环境变量 在构建Docker镜像时&#xff0c;可以在Dockerfile中使用ENV指令来设置环境变量 ENV MY_ENV_VAR"ABC123" ENV指令用于设置环境变量&#xff0c;语法为 ENV <key> <value> ENV <key><value> 使用docker run命令…

【100个 Unity实用技能】☀️ | Unity中 过滤透明区域的点击事件

Unity 小知识 大智慧 &#x1f3ac; 博客主页&#xff1a;https://xiaoy.blog.csdn.net &#x1f3a5; 本文由 呆呆敲代码的小Y 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;Unity系统学习专栏 &#x1f332; 游戏制作专栏推荐&#x…

ARP攻击

设备因处理大量ARP报文而导致CPU负荷过重&#xff0c;同时设备学习大量的ARP报文可能导致设备ARP表项资源被无效的ARP条目耗尽&#xff0c;造成合法用户的ARP报文不能继续生成ARP条目&#xff0c;导致用户无法正常通信。 伪造的ARP报文将错误地更新设备ARP表项&#xff0c;导致…

【Flink-1.17-教程】-【四】Flink DataStream API(1)源算子(Source)

【Flink-1.17-教程】-【四】Flink DataStream API&#xff08;1&#xff09;源算子&#xff08;Source&#xff09; 1&#xff09;执行环境&#xff08;Execution Environment&#xff09;1.1.创建执行环境1.2.执行模式&#xff08;Execution Mode&#xff09;1.3.触发程序执行…