STM32从零到量产开发:四路继电器工业控制模块开发-RS485 半双工通信底层驱动模块(bsp_res485)设计说明

1. 模块概述

本模块(bsp_rs485)是基于 STM32 HAL 库开发的 RS485 半双工通信底层驱动,主要基于 USART1 实现。模块采用“单字节接收中断 + IDLE 总线空闲中断”的机制进行 Modbus 帧的分割与接收,并使用阻塞式发送配合 DE 方向引脚自动控制完成半双工换向。支持运行时动态切换波特率,并提供配套的字符串查询接口供 Shell 调试打印。

2. 硬件接口配置

  • TX (发送引脚): PA9
  • RX (接收引脚): PA10
  • DE (发送使能引脚): PA8(高电平有效,拉高进入发送模式,拉低进入接收模式)

3. 核心数据流

3.1 接收数据流

  1. RS485 总线数据到达 USART1,触发单字节接收中断。
  2. 进入 BSP_RS485_OnRxByte(),将字节线性写入 s_rx_buf,并使 s_rx_index 递增。
  3. 总线空闲触发 IDLE 中断,进入 BSP_RS485_OnIdleIRQ()
  4. 置位 s_frame_ready 并记录 s_frame_len,标记整帧接收完成。
  5. 主循环通过 BSP_RS485_IsFrameReady() 查询到就绪标志后,调用 BSP_RS485_GetFrame() 取走完整数据。
  6. 上层协议栈(如 modbus_rtu)对取出的数据进行解析。

3.2 发送数据流

  1. 上层协议栈调用 BSP_RS485_Send() 发起发送。
  2. 模块自动拉高 DE 引脚,切换为发送模式,并关闭接收中断(防止总线回环干扰)。
  3. 执行阻塞式发送,轮询等待 TC(发送完成)标志。
  4. 发送完成后拉低 DE 引脚,恢复接收模式。
  5. 清空接收缓存中的脏数据,重新开启单字节接收中断,恢复总线监听。

4. 关键变量与缓冲区

变量/宏名称 类型/大小 说明
RS485_RX_BUF_SIZE 宏 (256u) RS485 接收线性缓冲区最大字节数(适配 Modbus 最大帧长)
RS485_TX_BUF_SIZE 宏 (256u) RS485 发送缓冲区最大字节数
s_rx_buf 静态数组 线性接收缓存,存放当前正在接收的一帧原始报文
s_rx_index 静态变量 接收缓存写入下标
s_frame_ready volatile 静态变量 完整帧就绪标志(中断置位,主循环清零)
s_frame_len volatile 静态变量 完整帧字节长度
s_baud_tbl 静态常量数组 波特率索引到硬件寄存器数值的映射表
g_baud_str 静态常量数组 波特率索引到打印字符串的映射表(仅本文件私有)

5. API 接口说明

5.1 初始化与配置

  • void BSP_RS485_Init(void)

    • 功能: RS485 外设初始化。
    • 说明: 初始化串口,DE 引脚默认拉低进入接收模式,开启 IDLE 空闲中断与单字节接收中断。
  • int8_t BSP_RS485_SetBaudrate(uint8_t baud_index)

    • 功能: 动态重新配置 RS485 串口波特率。
    • 参数: baud_index (波特率索引,范围 BAUD_INDEX_9600 ~ BAUD_INDEX_115200)
    • 返回值: 0 配置成功;-1 索引参数非法或初始化失败。
    • 说明: 会重新初始化串口,清空接收缓存,重启中断接收。

5.2 数据收发

  • uint8_t BSP_RS485_Send(const uint8_t *data, uint16_t len)

    • 功能: RS485 阻塞发送一帧数据,自动控制 DE 发送引脚。
    • 参数: data (待发送二进制数据指针),len (发送字节长度)
    • 返回值: 0 发送成功;1 空指针/长度超限/发送超时失败。
    • 注意: 阻塞式发送,禁止在中断上下文调用。
  • uint8_t BSP_RS485_IsFrameReady(void)

    • 功能: 查询是否收到完整一帧数据。
    • 返回值: 1 存在完整帧;0 无完整接收帧。
  • void BSP_RS485_GetFrame(uint8_t *buf, uint16_t *len)

    • 功能: 读取已接收完整 Modbus 帧,读取后自动清空帧标志并重启接收。
    • 参数: buf (接收数据存储缓冲区),len (实际接收字节长度输出)

5.3 状态查询与辅助

  • uint8_t BSP_RS485_GetBaudIndex(void)

    • 功能: 获取当前生效的波特率索引。
  • const char* RS485_GetBaudStr(uint8_t index)

    • 功能: 根据波特率索引获取对应打印字符串(供 RS232 shell 调试输出)。
    • 参数: index (波特率索引 0~4)
    • 返回值: 波特率文本,索引越界返回 "?"

5.4 底层中断回调(内部使用)

  • void BSP_RS485_OnIdleIRQ(void): USART IDLE 空闲中断回调,标记一帧接收完成。仅由 stm32f1xx_it.c 内部调用。
  • void BSP_RS485_OnRxByte(void): 单字节接收完成 HAL 回调分发函数。由 HAL_UART_RxCpltCallback 统一入口调用。

6. 架构设计与解耦说明

  1. HAL 回调统一分发: 本文件承载了 HAL 串口回调的总分发入口(RxCplt / Error),通过判断 Instance 区分 USART1(RS485) 与 UART4(RS232),分别转交各自模块处理,避免回调冲突。
  2. DE 换向时序封装: DE 方向引脚的换向时序完全封装在 BSP_RS485_Send 内部,上层业务只管发数据,无需关心底层硬件时序。
  3. 中断与主循环解耦: 中断上下文仅做“存字节”与“打帧标记”等极简操作,所有复杂的帧解析与业务处理全部放在主循环执行。
  4. 字符串映射解耦: 波特名字符串映射统一存放在本模块内部,对外仅提供 RS485_GetBaudStr 只读接口供 Shell 调用,消除了 rs232_shell 内部重复定义波特表的耦合问题。
  5. 线性缓冲设计: 针对 Modbus RS485 通信特性,未采用标准环形 FIFO,而是采用线性缓冲 + s_rx_index 设计,配合 IDLE 中断实现精准的帧分割。
/**
  ******************************************************************************
  * @file    bsp_rs485.h
  * @brief   RS485 半双工通信底层驱动 - USART1
  * @details 硬件:PA9(TX) PA10(RX) PA8(DE发送使能高有效)
  *          接收:单字节中断 + IDLE帧空闲中断检测一帧结束
  *          发送:阻塞发送,自动拉高/拉低DE方向引脚
  *          波特率配置可运行时动态切换,配套字符串查询接口供shell打印
  ******************************************************************************
  */
#ifndef __BSP_RS485_H
#define __BSP_RS485_H
#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"
#include "usart.h"

/* RS485接收环形缓冲区最大字节数 Modbus最大帧256 */
#define RS485_RX_BUF_SIZE     256u
/* RS485发送缓冲区最大字节数 */
#define RS485_TX_BUF_SIZE     256u

/* 波特率索引枚举宏,与Flash参数保持寄存器一一对应 */
#define BAUD_INDEX_9600       0u
#define BAUD_INDEX_19200      1u
#define BAUD_INDEX_38400      2u
#define BAUD_INDEX_57600      3u
#define BAUD_INDEX_115200     4u
#define BAUD_INDEX_NUM        5u
/** 波特率索引最大值 */
#define BAUD_INDEX_MAX        (BAUD_INDEX_NUM - 1u)

/**
 * @brief  RS485外设初始化
 * @note  初始化串口、DE引脚默认接收模式,开启IDLE空闲中断与单字节接收中断
 */
void BSP_RS485_Init(void);

/**
 * @brief  动态重新配置RS485串口波特率
 * @param[in] baud_index 波特率索引 BAUD_INDEX_9600~BAUD_INDEX_115200
 * @retval int8_t 0:配置成功;-1:索引参数非法
 * @note  会重新初始化串口,清空接收缓存,重启中断接收
 */
int8_t BSP_RS485_SetBaudrate(uint8_t baud_index);

/**
 * @brief  获取当前生效波特率索引
 * @retval uint8_t 当前baud_index
 */
uint8_t BSP_RS485_GetBaudIndex(void);

/**
 * @brief  根据波特率索引获取对应打印字符串(供RS232 shell调试输出)
 * @param[in] index 波特率索引0~4
 * @retval const char* 波特率文本,索引越界返回"?"
 * @note  字符串映射表仅存在bsp_rs485.c内部,上层仅通过本接口读取,消除跨文件耦合
 */
const char* RS485_GetBaudStr(uint8_t index);

/**
 * @brief  RS485阻塞发送一帧数据,自动控制DE发送引脚
 * @param[in] data 待发送二进制数据指针
 * @param[in] len 发送字节长度
 * @retval uint8_t 0:发送成功;1:空指针/长度超限/发送超时失败
 */
uint8_t BSP_RS485_Send(const uint8_t *data, uint16_t len);

/**
 * @brief  查询是否收到完整一帧数据
 * @retval uint8_t 1:存在完整帧;0:无完整接收帧
 */
uint8_t BSP_RS485_IsFrameReady(void);

/**
 * @brief  读取已接收完整Modbus帧,读取后自动清空帧标志重启接收
 * @param[out] buf 接收数据存储缓冲区,长度≥RS485_RX_BUF_SIZE
 * @param[out] len 实际接收字节长度输出
 */
void BSP_RS485_GetFrame(uint8_t *buf, uint16_t *len);

/**
 * @brief  USART IDLE空闲中断回调函数
 * @note  仅stm32f1xx_it.c中断服务函数内部调用,业务层禁止直接调用
 *        总线空闲时标记一帧接收完成,置帧就绪标志
 */
void BSP_RS485_OnIdleIRQ(void);

/**
 * @brief  单字节接收完成HAL回调分发函数
 * @note  由HAL_UART_RxCpltCallback统一入口调用,仅底层中断文件使用
 */
void BSP_RS485_OnRxByte(void);

#ifdef __cplusplus
}
#endif
#endif /* __BSP_RS485_H */

/**
  ******************************************************************************
  * @file    bsp_rs485.c
  * @brief   RS485 半双工通信底层驱动实现 (基于 USART1)
  *
  * 模块功能:
  *   1. 接收: 单字节接收中断 + IDLE 总线空闲中断做 Modbus 帧分割。
  *   2. 发送: 阻塞式发送, 自动拉高 / 拉低 DE 方向引脚完成半双工换向。
  *   3. 波特率运行时动态切换, 并提供波特字符串只读查询接口。
  *
  * 数据流:
  *   接收: RS485 总线 -> USART1 接收中断 -> BSP_RS485_OnRxByte()
  *         -> 线性写入 s_rx_buf (s_rx_index 递增)
  *         -> 总线空闲触发 IDLE 中断 -> BSP_RS485_OnIdleIRQ()
  *         -> 置 s_frame_ready / s_frame_len 标记整帧完成
  *         -> 主循环 BSP_RS485_IsFrameReady() 查询, BSP_RS485_GetFrame() 取走
  *         -> 上层 modbus_rtu 解析
  *   发送: modbus_rtu -> BSP_RS485_Send() -> 拉高 DE -> 阻塞发送
  *         -> 轮询等待 TC 发送完成 -> 拉低 DE -> 清脏缓存 -> 恢复接收中断
  *
  * 解耦优化:
  *   波特名字符串映射统一存放本文件, 提供 RS485_GetBaudStr 只读接口供 shell 调用,
  *   消除 rs232_shell 内部重复定义波特表的耦合问题。
  *   Modbus 485 不适用环形 FIFO, 采用线性缓冲 + s_rx_index 设计。
  *
  * 解耦说明:
  *   1. 本文件同时承载 HAL 串口回调的总分发入口 (RxCplt / Error), 按 Instance
  *      区分 USART1(RS485) 与 UART4(RS232), 分别转交各自模块处理。
  *   2. DE 方向引脚的换向时序完全封装在 BSP_RS485_Send 内部, 上层只管发数据。
  *   3. 中断上下文只做 "存字节" 与 "打帧标记", 帧解析全部放主循环。
  ******************************************************************************
  */
#include "bsp_rs485.h"
#include "bsp_rs232.h"
#include "main.h"
#include <string.h>

/* -------------------------- API 快速导航 --------------------------
 * void        HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);       HAL 接收完成总回调 (中断上下文)
 * void        HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);        HAL 串口错误总回调 (中断上下文)
 * void        BSP_RS485_Init(void);                                     初始化, 开 IDLE + 单字节接收中断
 * int8_t      BSP_RS485_SetBaudrate(uint8_t baud_index);                动态切换波特率 (0 成功 / -1 非法)
 * uint8_t     BSP_RS485_Send(const uint8_t *data, uint16_t len);        阻塞发送一帧, 自动控制 DE
 * uint8_t     BSP_RS485_IsFrameReady(void);                             查询是否有完整帧就绪
 * void        BSP_RS485_GetFrame(uint8_t *buf, uint16_t *len);          读取完整帧并清标志
 * uint8_t     BSP_RS485_GetBaudIndex(void);                             读取当前波特率索引
 * const char *RS485_GetBaudStr(uint8_t index);                          索引转波特字符串 (越界返回 "?")
 * void        BSP_RS485_OnIdleIRQ(void);                                IDLE 空闲中断处理 (中断上下文)
 * void        BSP_RS485_OnRxByte(void);                                 单字节接收中断处理 (中断上下文)
 * ------------------------------------------------------------------ */

/* -------------------------- 区块 1:static const 只读常量 / 映射查表 -------------------------- */
/**
 * @brief  波特率索引→硬件寄存器数值映射表,底层串口初始化使用
 */
static const uint32_t s_baud_tbl[BAUD_INDEX_NUM] =
{
    9600u, 19200u, 38400u, 57600u, 115200u
};

/**
 * @brief  波特率索引→打印字符串映射表,仅本文件static私有
 * @note  上层rs232_shell通过RS485_GetBaudStr接口读取,不直接访问此数组
 */
static const char *const g_baud_str[BAUD_INDEX_NUM] =
{
    "9600", "19200", "38400", "57600", "115200"
};

/* -------------------------- 区块 2:模块内部静态全局变量 -------------------------- */
/* 接收环形缓存 */
static uint8_t  s_rx_buf[RS485_RX_BUF_SIZE];  /* 线性接收缓存,存放当前正在接收的一帧原始报文 */
static uint16_t s_rx_index = 0u;       /* 接收写入下标 */
static uint8_t  s_rx_byte  = 0u;       /* 中断单字节临时缓存 */
static volatile uint8_t  s_frame_ready = 0u; /* 完整帧就绪标志
                                              * volatile:由 IDLE 中断置位、主循环查询清零,
                                              * 禁止编译器把轮询判断优化为寄存器缓存 */
static volatile uint16_t s_frame_len   = 0u;  /* 完整帧字节长度
                                               * volatile:与 s_frame_ready 同在中断中写入,
                                               * 主循环取帧时必须读到最新值 */

/* 当前生效波特率索引 */
static uint8_t  s_baud_index = BAUD_INDEX_9600;
/* 发送临时缓存 */
static uint8_t  s_tx_buf[RS485_TX_BUF_SIZE];

/* -------------------------- 区块 3:HAL 库系统全局回调钩子(统一分发入口) -------------------------- */
/**
 * @brief  HAL库串口接收完成统一回调入口
 * @param[in] huart 串口句柄指针
 * @retval 无
 * @note  区分USART1(RS485) / UART4(RS232)分发到各自接收处理函数
 * @note  运行在中断上下文,禁止耗时操作
 * 【关联指引】本钩子覆盖 HAL 弱定义,是全工程串口收字节的唯一入口,
 *             分别转交 BSP_RS485_OnRxByte() 与 BSP_RS232_OnRxByte()。
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        BSP_RS485_OnRxByte();
    }
    else if (huart->Instance == UART4)
    {
        BSP_RS232_OnRxByte();
    }
}

/**
 * @brief  HAL库串口错误中断统一回调入口
 * @param[in] huart 串口句柄指针
 * @retval 无
 * @note  RS485发生帧错误/溢出时清空缓存重启接收
 * @note  运行在中断上下文,禁止耗时操作
 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)
    {
        /* 出错帧内容已不可信:直接丢弃半帧数据并重启接收 */
        s_rx_index    = 0u;
        s_frame_ready = 0u;
        HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);
    }
    else if (huart->Instance == UART4)
    {
        BSP_RS232_OnError(huart);
    }
}

/* -------------------------- 区块 4:对外公共业务 API -------------------------- */
/**
 * @brief  RS485模块初始化
 * @retval 无
 * @note  需在 MX_USART1_UART_Init 之后调用;DE 默认拉低进入接收模式
 */
void BSP_RS485_Init(void)
{
    s_rx_index    = 0u;
    s_frame_ready = 0u;
    s_frame_len   = 0u;
    s_baud_index  = BAUD_INDEX_9600;

    /* DE引脚默认低电平,接收模式 */
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);
    /* 使能总线空闲IDLE中断 */
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
    /* 启动单字节中断接收 */
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);
}

/**
 * @brief  动态重新配置RS485串口波特率
 * @param[in] baud_index 波特率索引 BAUD_INDEX_9600~BAUD_INDEX_115200
 * @retval int8_t 0:配置成功;-1:索引参数非法
 * @note  会重新初始化串口,清空接收缓存,重启中断接收
 * 【关联指引】索引经 s_baud_tbl 查表转换为真实波特率数值;
 *             调用方通常是 flash_param 恢复配置或 rs232_shell 手动设置。
 */
int8_t BSP_RS485_SetBaudrate(uint8_t baud_index)
{
    /* 入参校验:索引越界直接拒绝,避免查表越界 */
    if (baud_index >= BAUD_INDEX_NUM)
    {
        return -1;
    }
    /* 注销串口重新配置波特率 */
    HAL_UART_DeInit(&huart1);   /* 注销当前串口外设,关闭时钟、清除寄存器状态; */
    huart1.Init.BaudRate = s_baud_tbl[baud_index];
    if (HAL_UART_Init(&huart1) != HAL_OK)
    {
        return -1;
    }
    s_baud_index = baud_index;

    /* 重新开启IDLE总线空闲中断(Modbus帧分割依赖) */
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);
    s_rx_index    = 0u;
    s_frame_ready = 0u;
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);   /* 重启单字节接收中断,恢复数据接收 */
    return 0;
}

/**
 * @brief  RS485阻塞发送一帧数据,自动控制DE发送引脚
 * @param[in] data 待发送二进制数据指针
 * @param[in] len 发送字节长度
 * @retval uint8_t 0:发送成功;1:空指针/长度超限/发送超时失败
 * @note  阻塞式发送,禁止在中断上下文调用
 * 【关联指引】发送期间会关闭接收中断并在结束后清空 s_rx_buf 脏数据,
 *             避免半双工总线回环把自身报文当成接收帧。
 */
uint8_t BSP_RS485_Send(const uint8_t *data, uint16_t len)
{
    /* 入参兜底:零长度、超过发送缓存容量、空指针一律判失败 */
    if (len == 0u || len > RS485_TX_BUF_SIZE || data == NULL)
    {
        return 1u;
    }
    memcpy(s_tx_buf, data, len);

    /*手动DE硬件专属:关闭接收中断,防止发送回环干扰接收*/
    HAL_UART_AbortReceive_IT(&huart1);
    /* 拉高DE引脚,485芯片切换为发送模式 */
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_SET);

    uint32_t timeout = (uint32_t)len * 20u + 50u;   /* 计算发送超时时间:每字节20ms基础耗时 + 50ms保底超时 */
    HAL_StatusTypeDef st = HAL_UART_Transmit(&huart1, s_tx_buf, len, timeout);  /* 阻塞式同步发送,CPU等待硬件发送完成或超时 */

    /* 轮询等待串口TC传输完成标志,确保最后一字节全部推送到总线 */
    uint32_t tickstart = HAL_GetTick();
    while (!__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC))
    {
        /* 等待10ms仍无TC标志则跳出,避免死循环 */
        if ((HAL_GetTick() - tickstart) > 10u)
        {
            break;
        }
    }

    /* 恢复接收模式,拉低DE,重启接收中断 */
    HAL_GPIO_WritePin(RS485_DE_GPIO_Port, RS485_DE_Pin, GPIO_PIN_RESET);
    /* 清空接收缓存脏数据(发送时总线回扰产生无效字节) */
    s_rx_index    = 0u;
    s_frame_ready = 0u;
    /* 重新开启单字节接收中断,恢复总线监听 */
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);

    return (st == HAL_OK) ? 0u : 1u;
}

/**
 * @brief  查询是否存在完整接收帧
 * @retval uint8_t 1:存在完整帧;0:无完整接收帧
 * 【关联指引】标志由 BSP_RS485_OnIdleIRQ 在 IDLE 中断中置位,
 *             主循环查询到 1 后应调用 BSP_RS485_GetFrame 取走整帧。
 */
uint8_t BSP_RS485_IsFrameReady(void)
{
    return s_frame_ready;
}

/**
 * @brief  读取完整Modbus接收帧,读取后清空帧标志
 * @param[out] buf 输出缓存,存放读取到的整帧报文
 * @param[out] len 输出帧长度,记录本帧有效字节数
 * @retval 无
 * @note  拷贝期间关中断,避免与接收中断竞争 s_rx_buf / s_rx_index
 * 【关联指引】数据由 BSP_RS485_OnRxByte 在中断中写入 s_rx_buf,
 *             本函数在主循环中取走并交给 modbus_rtu 解析。
 */
void BSP_RS485_GetFrame(uint8_t *buf, uint16_t *len)
{
    __disable_irq();
    *len = s_frame_len;
    /* 长度合法性双重校验:防止异常长度导致 memcpy 越界 */
    if (s_frame_len > 0u && s_frame_len <= RS485_RX_BUF_SIZE)
    {
        /* 将模块私有接收缓存内完整报文拷贝到上层提供的buf */
        memcpy(buf, s_rx_buf, s_frame_len);
    }
    /* 清除帧就绪标志、重置接收写入下标,释放缓存用于下一帧接收 */
    s_frame_ready = 0u;
    s_rx_index    = 0u;
    __enable_irq();
    /* 重新开启单字节接收中断,持续监听485总线数据 */
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);
}

/**
 * @brief  获取当前波特率索引
 * @retval uint8_t 当前生效的 baud_index
 */
uint8_t BSP_RS485_GetBaudIndex(void)
{
    return s_baud_index;
}

/**
 * @brief  根据索引获取波特打印字符串,对外只读接口
 * @param[in] index 波特率索引 0~4
 * @retval const char* 波特率文本,索引越界返回 "?"
 * @note  字符串表为本文件私有,上层通过本接口读取,消除跨文件重复定义
 */
const char* RS485_GetBaudStr(uint8_t index)
{
    /* 越界返回哨兵字符串 "?",保证上层打印不会拿到野指针 */
    if (index >= BAUD_INDEX_NUM)
    {
        return "?";
    }
    return g_baud_str[index];
}

/* -------------------------- 区块 5:内部私有底层处理函数(中断处理,放文件末尾) -------------------------- */
/**
 * @brief  IDLE总线空闲中断处理,标记一帧接收完成
 * @retval 无
 * @note  运行在中断上下文,禁止耗时操作
 * @note  仅 stm32f1xx_it.c 中断服务函数内部调用,业务层禁止直接调用
 * 【关联指引】置位的 s_frame_ready / s_frame_len 由主循环
 *             BSP_RS485_IsFrameReady / BSP_RS485_GetFrame 消费。
 */
void BSP_RS485_OnIdleIRQ(void)
{
    /* 判断串口IDLE空闲标志是否置1 */
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
    {
        /* 清除IDLE中断标志 */
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        /* 至少收到1字节才算有效帧,过滤总线毛刺误触发IDLE */
        if (s_rx_index > 0u)
        {
            s_frame_len   = s_rx_index;     /* 记录这一帧总长度 */
            s_frame_ready = 1u;             /* 标记:完整帧就绪 */
            s_rx_index    = 0u;             /* 下标重置,准备接收下一帧 */
        }
    }
}

/**
 * @brief  单字节接收中断处理,存入线性接收缓存(本工程未使用标准环形FIFO)
 * @retval 无
 * @note  运行在串口接收完成中断上下文,执行必须极简;仅存储字节、重启接收
 * @note  运行在中断上下文,禁止耗时操作
 * @note  跨文件被 HAL_UART_RxCpltCallback 调用,故不加 static,不对外暴露头文件声明
 */
void BSP_RS485_OnRxByte(void)
{
    /* 判断当前写入下标未达到缓存最大长度,缓存未满 */
    if (s_rx_index < RS485_RX_BUF_SIZE)
    {
        /* 将本次中断收到的字节存入缓存,下标自增指向下一空位置 */
        s_rx_buf[s_rx_index++] = s_rx_byte;
    }
    else
    {
        /* 缓存溢出:新数据持续涌入,旧数据全部丢弃,从头覆盖存储 */
        s_rx_index = 0u;
        s_rx_buf[s_rx_index++] = s_rx_byte;
    }
    /* 持续开启单字节中断接收,等待下一个总线字节到来 */
    HAL_UART_Receive_IT(&huart1, &s_rx_byte, 1u);
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐