欢迎光临
我们一直在努力

STM32从零到量产开发:四路继电器工业控制模块开发-RS232 调试通信驱动模块(bsp_res232)设计说明

STM32从零到量产开发:四路继电器工业控制模块开发-RS232 调试通信驱动模块(bsp_res232)设计说明

1. 模块概述

bsp_rs232 模块是基于 STM32 HAL 库封装的 RS232 串口通信驱动,底层使用 UART4 外设(TX: PC10, RX: PC11)。该模块主要用于与上位机或串口助手进行通信,支持调试输出、参数查询及固件升级等功能。

硬件注意:该接口为 TTL 电平,外部必须连接 RS232 电平转换芯片(如 MAX3232)。

核心特性:

  • 发送机制:采用阻塞式发送(HAL_UART_Transmit),无需流控或方向控制。
  • 接收机制:采用中断驱动的单字节接收,结合环形缓冲区(Ring Buffer)实现非阻塞读取。
  • 协议解析:内置行缓冲读取功能,自动兼容 Windows (\\r\\n)、Linux (\\n) 及老设备 (\\r) 的换行格式,支持命令行交互。

2. 关键数据结构与宏定义

2.1 缓冲区配置宏

#define RS232_RX_BUF_SIZE 256u // 接收环形缓冲区存储上限
#define RS232_TX_BUF_SIZE 256u // 预留发送缓冲区宏(当前阻塞发送暂未启用)

2.2 静态环形缓冲区结构

接收端采用经典的环形缓冲区设计,通过头尾指针解耦中断写入与主循环读取:

static uint8_t s_rx_buf[RS232_RX_BUF_SIZE]; // 接收缓存数组
static volatile uint16_t s_rx_head = 0u; // 写指针(中断上下文写入)
static volatile uint16_t s_rx_tail = 0u; // 读指针(主循环上下文读取)
static uint8_t s_rx_byte = 0u; // 中断单字节临时接收变量

3. API 接口说明

3.1 初始化与中断回调

接口函数说明
void BSP_RS232_Init(void) 初始化模块,清空环形缓存读写指针,并启动 UART4 单字节中断接收。必须在硬件初始化后调用。
void BSP_RS232_OnRxByte(void) 单字节接收完成回调。将数据写入环形缓存,若缓存满则丢弃当前字节,并重新启动下一次接收。仅限中断文件调用。
void BSP_RS232_OnError(UART_HandleTypeDef *huart) 串口错误回调(溢出/帧/奇偶/噪声错误)。触发时清空脏缓存并重启接收。仅限中断文件调用。

3.2 数据发送接口

接口函数说明
uint8_t BSP_RS232_Send(const uint8_t *data, uint16_t len) 二进制阻塞发送。超时时间按 len * 20ms + 50ms 估算。返回 0 成功,非 0 失败。
void BSP_RS232_SendString(const char *str) 字符串便捷发送。内部自动计算长度并调用底层发送接口,包含空指针保护。

3.3 数据接收接口

接口函数说明
uint16_t BSP_RS232_Available(void) 查询接收缓冲区中可读的字节数。内部使用关中断保护计算,兼容缓存绕圈场景。
int16_t BSP_RS232_ReadByte(void) 非阻塞读取单字节。读到数据返回 ASCII 值,缓冲区为空返回 -1。内部使用关中断保护。
uint8_t BSP_RS232_ReadLine(uint8_t *buf, uint16_t bufsize, uint16_t *len) 尝试读取一行数据。返回 1 表示读到完整行,返回 0 表示仅有半包数据。自动过滤空回车,并在末尾追加 \\0。

4. 核心机制与注意事项

4.1 中断链路

数据接收遵循以下调用链路:
UART4_IRQHandler → HAL_UART_IRQHandler → HAL_UART_RxCpltCallback → BSP_RS232_OnRxByte

4.2 并发安全保护

在主循环读取数据(ReadByte、Available)时,代码使用了 __disable_irq() 和 __enable_irq() 进行临界区保护。这是为了防止在读取或更新 head/tail 指针时被 UART 接收中断打断,从而避免数据错乱或指针越界。

4.3 错误恢复机制

当发生 ORE(溢出错误)、FE(帧错误)等硬件异常时,BSP_RS232_OnError 会被触发。该函数会强制复位环形缓冲区的读写指针,并重新调用 HAL_UART_Receive_IT 启动接收,确保串口在异常后能够自动恢复工作。

4.4 使用限制

  • BSP_RS232_OnRxByte 和 BSP_RS232_OnError 属于底层中断回调,业务代码严禁直接调用,否则会破坏环形缓存的指针状态。
  • 当前发送为阻塞模式,若在发送过程中有高频中断或长耗时任务,可能会导致发送超时,需根据实际波特率合理评估超时时间。

/**
******************************************************************************
* @file bsp_rs232.h
* @brief RS232 调试通信驱动 – UART4 (PC10_TX / PC11_RX)
*
* 用途: 与上位机/电脑串口助手通信, 用于调试输出、参数查询、固件升级等。
* 注意: 此接口为 TTL 电平, 外部需接 RS232 电平转换芯片 (如 MAX3232)。
*
* 功能:
* – 阻塞发送 (printf 重定向基础)
* – 中断接收 (环形缓冲区)
* – 行缓冲读取 (支持 \\r\\n 结尾的命令行交互)
*
* 宏说明:
* RS232_RX_BUF_SIZE:接收环形缓冲区存储上限
* RS232_TX_BUF_SIZE:预留发送缓冲区宏,当前底层发送为阻塞模式暂未启用,后续扩展TX FIFO使用
******************************************************************************
*/

#ifndef __BSP_RS232_H
#define __BSP_RS232_H
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "usart.h"

/* RS232 接收环形缓冲区大小 */
#define RS232_RX_BUF_SIZE 256u
/* RS232 发送缓冲区大小,预留宏,当前阻塞发送逻辑未使用 */
#define RS232_TX_BUF_SIZE 256u

/**
* @brief 初始化 RS232 (UART4), 启动中断接收
*/

void BSP_RS232_Init(void);

/**
* @brief 发送数据 (阻塞式, 无方向控制需求)
* @param data: 数据指针
* @param len: 长度
* @retval 0=成功, 非0=失败
*/

uint8_t BSP_RS232_Send(const uint8_t *data, uint16_t len);

/**
* @brief 发送字符串 (阻塞式, 自动计算长度)
* @param str: 以 '\\0' 结尾的字符串
*/

void BSP_RS232_SendString(const char *str);

/**
* @brief 查询接收缓冲区可读字节数
* @note 搭配 ReadByte / ReadLine 使用,优先判断缓存有数据再读取,减少无效调用
*/

uint16_t BSP_RS232_Available(void);

/**
* @brief 读取一个字节 (非阻塞, 无数据返回 -1)
* @retval >=0 读到字节ASCII值,-1 缓冲区为空
*/

int16_t BSP_RS232_ReadByte(void);

/**
* @brief 尝试读取一行 (以 \\r 或 \\n 结尾)
* @param buf: 输出缓冲区
* @param bufsize: 缓冲区大小,内部自动预留1字节存放字符串结束符'\\0'
* @param len: 输出实际读取长度 (不含结尾换行符)
* @retval 1=读到完整行, 0=无完整行,仅半包数据
* @note 自动兼容 Windows(\\r\\n) / Linux(\\n) / 老设备(\\r) 三种换行格式,自动过滤空回车
*/

uint8_t BSP_RS232_ReadLine(uint8_t *buf, uint16_t bufsize, uint16_t *len);

/**
* @brief UART 接收完成单字节回调 (由 HAL_UART_RxCpltCallback 调用)
* @note 仅 stm32f1xx_it.c 中断文件调用,业务代码禁止直接调用本函数,会破坏环形缓存指针
*/

void BSP_RS232_OnRxByte(void);

/**
* @brief UART 错误回调 (溢出/帧/奇偶/噪声错误恢复)
* @param huart: 当前报错串口句柄
* @note 仅 stm32f1xx_it.c 错误中断分支调用,业务代码禁止直接调用
*/

void BSP_RS232_OnError(UART_HandleTypeDef *huart);

#ifdef __cplusplus
}
#endif
#endif /* __BSP_RS232_H */

/**
******************************************************************************
* @file bsp_rs232.c
* @brief RS232 调试通信驱动实现 (基于 UART4)
* @details 接收: 环形缓冲区 + 中断逐字节无阻塞接收
* 发送: 阻塞式 HAL_UART_Transmit (RS232 全双工, 无方向控制)
* 中断链路:
* UART4_IRQHandler(芯片底层中断入口)
* -> HAL_UART_IRQHandler(HAL 库统一中断处理函数)
* -> HAL_UART_RxCpltCallback(HAL 库弱回调函数)
* -> BSP_RS232_OnRxByte(业务接收函数)
* 链路错误恢复:UART4_IRQHandler -> HAL_UART_IRQHandler
* -> HAL_UART_ErrorCallback -> BSP_RS232_OnError
******************************************************************************
*/

#include "bsp_rs232.h"
#include <string.h>

/* ===================== 模块静态环形缓存与单字节变量 ===================== */
/* 接收环形缓冲区 */
static uint8_t s_rx_buf[RS232_RX_BUF_SIZE]; /* 接收环形缓存数组 */
static volatile uint16_t s_rx_head = 0u; /* 写指针(中断写入) */
static volatile uint16_t s_rx_tail = 0u; /* 读指针(主循环读取) */
static uint8_t s_rx_byte = 0u; /* 中断单字节临时缓存 */

/* ===================== 对外公共接口(按数据流:初始化 → 中断回调 → 读取 → 发送) ===================== */
/**
* @brief RS232模块初始化,重置缓存并开启单字节接收中断
* @note 【关联指引】必须在 UART4 硬件初始化 MX_UART4_Init 之后调用;
* 环形缓存读写指针清零,HAL_UART_Receive_IT 启动中断逐字节接收
*/

void BSP_RS232_Init(void)
{
/* 清空环形缓存读写指针 */
s_rx_head = 0u;
s_rx_tail = 0u;
/* 启动UART4单字节中断接收 */
/* HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) */
HAL_UART_Receive_IT(&huart4, &s_rx_byte, 1u);
}

/**
* @brief UART4串口错误回调
* @param huart: 报错串口句柄
* @note 出现溢出/帧/奇偶/噪声错误时清空脏缓存,重启接收;
* 仅在 stm32f1xx_it.c 错误中断分支调用,业务层禁止直接调用
*/

void BSP_RS232_OnError(UART_HandleTypeDef *huart)
{
s_rx_head = 0u;
s_rx_tail = 0u;
HAL_UART_Receive_IT(huart, &s_rx_byte, 1u);
}

/**
* @brief 单字节接收中断回调,写入环形缓存
* @note 缓存满则丢弃当前字节,持续开启下一次接收;
* 仅 stm32f1xx_it.c 的 HAL_UART_RxCpltCallback 调用
*/

void BSP_RS232_OnRxByte(void)
{
uint16_t next = (s_rx_head + 1u) % RS232_RX_BUF_SIZE;
if (next != s_rx_tail)
{
s_rx_buf[s_rx_head] = s_rx_byte;
s_rx_head = next;
}
HAL_UART_Receive_IT(&huart4, &s_rx_byte, 1u);
}

/**
* @brief 非阻塞读取单个字节
* @retval >=0读到字节,-1无数据
* @note 关中断保护读写指针,读取后立即恢复中断,避免数据错乱
*/

int16_t BSP_RS232_ReadByte(void)
{
__disable_irq(); /* 关闭全部可屏蔽中断,防止读取中途被接收中断改写 head/tail */
int16_t ret = –1;
if (s_rx_tail != s_rx_head)
{
ret = (int16_t)s_rx_buf[s_rx_tail];
s_rx_tail = (s_rx_tail + 1u) % RS232_RX_BUF_SIZE;
}
__enable_irq(); /* 打开全部可屏蔽中断,恢复串口接收 */
return ret;
}

/**
* @brief 读取一整行(\\r/\\n结尾)
* @param buf: 输出缓存
* @param bufsize: 缓存最大长度
* @param len: 输出有效字符长度
* @retval 1读到完整行,0仅半包数据
* @note 自动兼容CRLF、单独\\r、单独\\n,过滤空回车;
* 1. Windows:\\r 紧接着\\n(两个字符代表一行结束)
* 2. Linux/Mac:只有\\n
* 3. 部分老设备:只有\\r
*/

uint8_t BSP_RS232_ReadLine(uint8_t *buf, uint16_t bufsize, uint16_t *len)
{
uint16_t i = 0u; /* i = 已经存了多少个字符到buf */
int16_t ch; /* 存每次读到的1个字节,-1代表没数据 */
*len = 0u; /* 先把输出长度清零 */

while ((ch = BSP_RS232_ReadByte()) >= 0)
{
/* 判断:读到回车\\r 或者 换行\\n,代表一行结束 */
if (ch == '\\r' || ch == '\\n')
{
/* 如果是 Windows 格式 \\r\\n,多读后面的 \\n 扔掉 */
if (ch == '\\r')
{
int16_t nxt = BSP_RS232_ReadByte(); /* nxt 为读取字符的ASCII数值 */
if (nxt == '\\n')
{
/* 读到配对\\n,直接丢弃,不存进字符串 */
}
}
/* 过滤空行:只敲回车、没有任何文字,跳过不返回 */
if (i == 0u)
{
continue;
}
buf[i] = '\\0'; /* 字符串末尾加结束符,方便打印 */
*len = i; /* 把有效字符数量传给上层 */
return 1u; /* 返回1:成功读到完整一行 */
}
/* 不是换行符,是普通文字,存进buf */
/* bufsize-1 留1个位置给最后的'\\0',防止溢出 */
if (i < bufsize – 1u)
{
buf[i++] = (uint8_t)ch;
}
/* 超出缓存长度的字符直接丢掉,避免数组越界 */
}
/* 循环走完,缓冲区读完了也没碰到换行,只有半段不完整文字 */
*len = i;
return 0u; /* 返回0:没有完整一行,只有半包数据 */
}

/**
* @brief 查询缓存有效未读字节数量
* @retval 当前可读取字节数
* @note 关中断保护计算,避免 head/tail 中途被改写;兼容缓存绕圈场景
*/

uint16_t BSP_RS232_Available(void)
{
__disable_irq(); /* 关闭全部可屏蔽中断,防止计算中途串口中断改写head/tail,数据错乱 */
uint16_t count;
/* 写指针head在读指针tail后面,缓存没绕圈 */
if (s_rx_head >= s_rx_tail)
{
count = s_rx_head – s_rx_tail;
}
/* head绕到缓存开头,超过tail,数据分段存在数组头尾 */
else
{
count = RS232_RX_BUF_SIZE – s_rx_tail + s_rx_head;
}
__enable_irq(); /* 计算完成,重新打开中断,恢复串口接收 */
return count; /* 返回剩余未读字节总数 */
}

/**
* @brief 二进制阻塞发送底层接口
* @param data: 待发送数据指针
* @param len: 数据长度
* @retval 0成功 / 1失败(空指针/超时)
* @note 超时按每字节 20ms 估算 +50ms 兜底;RS232 全双工无需方向控制
*/

uint8_t BSP_RS232_Send(const uint8_t *data, uint16_t len)
{
if (len == 0u || data == NULL)
{
return 1u;
}
uint32_t timeout = (uint32_t)len * 20u + 50u; /* 每个字节20ms, +50总时长兜底 */
/* HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout) */
HAL_StatusTypeDef st = HAL_UART_Transmit(&huart4, data, len, timeout);
return (st == HAL_OK) ? 0u : 1u;
}

/**
* @brief 便捷字符串发送封装,打印输出工具,简化调用,无需返回值
* @param str: 以'\\0'结尾字符串
* @note 空指针内部保护;将 char* 字符串强转为底层无符号字节数组接口
*/

void BSP_RS232_SendString(const char *str)
{
if (str == NULL)
{
return;
}
/* 将char*字符串强转为底层无符号字节数组接口 */
BSP_RS232_Send((const uint8_t *)str, (uint16_t)strlen(str));
}

赞(0)
未经允许不得转载:171主机测评 » STM32从零到量产开发:四路继电器工业控制模块开发-RS232 调试通信驱动模块(bsp_res232)设计说明
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址