STM32-串口解析框架

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

测试环境:

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

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

通信协议

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

|--------|--------|--------|--------|----------|----------|-----------------------|------------|------------|
| D0 | D1 | D2 | D3 | D4 | D5 | D6 ~ Dn | C1 | C2 |
| 帧头1 | 帧头2 | 地址 | 命令 | 数据长度(低位) | 数据长度(高位) | 数据 | CRC 校验(低位) | CRC 校验(高位) |
| 1 Byte | 1 Byte | 1 Byte | 1 Byte | 1 Byte | 1 Byte | 0 Byte ~ 65535 Bytes | 1 Byte | 1 Byte |
| 90H | EBH | Addr | Cmd | LenL | LenH | Data | CrcL | CrcH |

整个帧分为以下 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 | 数据 | 根据命令的不同,数据域中的内容也不尽相同 |
| 6 | CRC 校验 | 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 Bytes | 1 | 分包大小 | 2 Bytes | 分包大小(摄像头模组内部保留无实际作用)。整包时为 0,立即返回图片数据,分包时非 0 (建议直接设置为 02H 00H - 厂家反馈) |
| 4 Bytes | 2 | 分辨率 | 1 Byte | |
| 4 Bytes | 3 | 压缩比 | 1 Byte | 1~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 Bytes | 1 | 开始地址 | | 4 Bytes | 指定从 JPEG 图片开始地址处取数据 |
| 6 Bytes | 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 中断,所以中断不会再次触发,可以进行帧处理

c 复制代码
/**
  * @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();
    }
}

状态机 (帧处理)

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

c 复制代码
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;
c 复制代码
/**
  * @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. 将接收标志设置为超时
c 复制代码
/**
  * @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 状态后,说明帧处理完毕或者超时,返回结果。

c 复制代码
/**
  * @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;
    }
}
相关推荐
CoderBob3 分钟前
【EmbeddedGUI】脏矩阵设计说明
c语言·单片机
中云DDoS CC防护蔡蔡1 小时前
为什么海外服务器IP会被封
服务器·经验分享
陌夏微秋1 小时前
51单片机基础02 动态数码管显示-并串转换
arm开发·单片机·嵌入式硬件·51单片机·硬件工程·信息与通信·信号处理
面包板扎1 小时前
51单片机应用开发---LCD1602显示应用
单片机·嵌入式硬件·51单片机
棱角~~1 小时前
10款PDF合并工具讲解与推荐!!!
人工智能·经验分享·其他·pdf·学习方法
面包板扎2 小时前
51单片机应用开发(进阶)---定时器应用(电子时钟)
单片机
好想有猫猫2 小时前
【51单片机】LCD1602液晶显示屏
c语言·单片机·嵌入式硬件·51单片机·1024程序员节
陌夏微秋2 小时前
51单片机基础01 单片机最小系统
单片机·嵌入式硬件·51单片机·硬件工程·信息与通信
南暮思鸢3 小时前
Node.js is Web Scale
经验分享·web安全·网络安全·node.js·ctf题目·hackergame 2024
亦世凡华、8 小时前
【HarmonyOS】鸿蒙系统在租房项目中的项目实战(一)
经验分享·harmonyos·harmonyos next·arkui·鸿蒙开发