2026-03-27 10:09:13 +08:00
|
|
|
|
/**
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @file uart2_print.c
|
|
|
|
|
|
* @brief UART2调试打印模块实现
|
|
|
|
|
|
* @author Application Layer
|
|
|
|
|
|
* @version 1.1
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
* @attention
|
|
|
|
|
|
* 本模块实现基于环形缓冲区的非阻塞调试信息输出
|
|
|
|
|
|
* 关键特性:
|
|
|
|
|
|
* 1. 环形缓冲区避免数据丢失
|
|
|
|
|
|
* 2. 中断安全,支持ISR中调用
|
|
|
|
|
|
* 3. 非阻塞发送,不影响实时性
|
2026-03-27 16:21:00 +08:00
|
|
|
|
*
|
2026-03-27 10:09:13 +08:00
|
|
|
|
* 修订历史:
|
|
|
|
|
|
* v1.1 - 修复审查报告高危-1/2/3,中危-4
|
|
|
|
|
|
******************************************************************************
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "uart2_print.h"
|
|
|
|
|
|
#include "usart.h"
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 调试宏定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/* DEBUG_PRINT_ENABLED: 调试日志开关,置1时启用模块自测日志输出 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_PRINT_ENABLED 1
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG_PRINT_ENABLED
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 调试日志宏,带模块前缀"[UART2]"方便过滤日志 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[UART2] " fmt "\r\n", ##__VA_ARGS__)
|
|
|
|
|
|
#else
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 禁用时为空宏,避免无用代码生成 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
#define DEBUG_LOG(fmt, ...)
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 数据结构定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 环形发送缓冲区数据结构
|
|
|
|
|
|
* @note 采用Ring Buffer(环形缓冲区)设计,实现FIFO队列管理
|
|
|
|
|
|
*
|
|
|
|
|
|
* 设计目的:
|
|
|
|
|
|
* 解决UART发送与CPU执行速度不匹配的问题。当UART正在发送数据时,
|
|
|
|
|
|
* 后续数据先存入缓冲区,待发送完成后再取出发送,实现异步非阻塞打印。
|
|
|
|
|
|
* 环形缓冲区相比线性缓冲区的优势是无需数据搬移,head和tail指针
|
|
|
|
|
|
* 循环递增,到达末尾后自动回绕到开头。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 字段说明:
|
|
|
|
|
|
* - buffer: 存放数据的字节数组,大小由UART2_TX_BUFFER_SIZE定义
|
|
|
|
|
|
* - head: 写入位置指针,指向下一个待写入位置(生产者指针)
|
|
|
|
|
|
* - tail: 读取位置指针,指向下一个待读取位置(消费者指针)
|
|
|
|
|
|
* - count: 当前缓冲区中有效数据字节数
|
|
|
|
|
|
* - is_sending: 发送忙标志,表示UART硬件是否正在发送数据
|
|
|
|
|
|
* - overflow_count: 溢出错误计数,缓冲区满时丢弃数据的次数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 索引计算规则:
|
|
|
|
|
|
* head和tail均采用模运算实现回绕: new_index = (old_index + 1) % buffer_size
|
|
|
|
|
|
* 这确保了指针在达到缓冲区末尾时自动回到开头,形成"环形"效果
|
|
|
|
|
|
*
|
|
|
|
|
|
* 线程安全说明:
|
|
|
|
|
|
* 所有修改共享资源的代码段均使用__disable_irq/__enable_irq暂时禁用中断,
|
|
|
|
|
|
* 防止在临界区内被ISR打断导致数据竞争(race condition)
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
typedef struct {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
uint8_t buffer[UART2_TX_BUFFER_SIZE]; /**< 发送数据缓冲区,静态分配避免动态内存 */
|
|
|
|
|
|
volatile uint16_t head; /**< 写指针,下一个数据写入位置 */
|
|
|
|
|
|
volatile uint16_t tail; /**< 读指针,下一个数据读取位置 */
|
|
|
|
|
|
volatile uint16_t count; /**< 缓冲区中有效数据字节计数 */
|
|
|
|
|
|
volatile bool is_sending; /**< UART发送忙标志,防止重复启动发送 */
|
|
|
|
|
|
volatile uint16_t overflow_count; /**< 溢出错误计数,统计因缓冲区满丢弃的数据 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
} ring_buffer_t;
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 全局变量定义
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief 环形发送缓冲区实例
|
|
|
|
|
|
* @note static修饰确保仅本文件内可访问,初始化为全零
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
static ring_buffer_t tx_ring = {0};
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 公共函数实现
|
|
|
|
|
|
*============================================================================*/
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART2打印模块初始化
|
|
|
|
|
|
* @note 在系统启动或UART外设初始化后调用,重置环形缓冲区状态
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 功能说明:
|
|
|
|
|
|
* 1. 重置所有指针(head/tail/count)为初始状态
|
|
|
|
|
|
* 2. 清除发送忙标志
|
|
|
|
|
|
* 3. 清零溢出错误计数器
|
|
|
|
|
|
*
|
|
|
|
|
|
* 初始化安全:
|
|
|
|
|
|
* 此函数应在UART硬件初始化完成之后调用,确保huart2已正确配置
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_Init(void)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 重置环形缓冲区各状态变量 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
tx_ring.head = 0;
|
|
|
|
|
|
tx_ring.tail = 0;
|
|
|
|
|
|
tx_ring.count = 0;
|
|
|
|
|
|
tx_ring.is_sending = false;
|
|
|
|
|
|
tx_ring.overflow_count = 0;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 输出模块初始化完成日志 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
DEBUG_LOG("Init OK, buffer size: %d", UART2_TX_BUFFER_SIZE);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送数据到UART2(核心写入函数)
|
|
|
|
|
|
* @note 将数据写入环形缓冲区,若UART空闲则自动启动首次发送
|
|
|
|
|
|
* 此函数是线程安全的,可从ISR或主线程调用
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param data: 待发送数据缓冲区的指针(输入)
|
|
|
|
|
|
* @param len: 待发送数据字节数(输入)
|
|
|
|
|
|
* @return 无返回值
|
|
|
|
|
|
*
|
|
|
|
|
|
* 算法说明 - 生产者逻辑:
|
|
|
|
|
|
* 1. 遍历待发送数据的每个字节
|
|
|
|
|
|
* 2. 检查缓冲区是否有空间(count < buffer_size)
|
|
|
|
|
|
* 3. 如有空间,将数据写入buffer[head],head递增并回绕
|
|
|
|
|
|
* 4. 如缓冲区满,放弃剩余数据并增加overflow_count
|
|
|
|
|
|
*
|
|
|
|
|
|
* 发送触发机制:
|
|
|
|
|
|
* - 如果缓冲区有数据且UART当前空闲(is_sending=false),
|
|
|
|
|
|
* 设置is_sending=true并立即触发首次发送(Kickoff)
|
|
|
|
|
|
* - 后续发送由TxCpltCallback中断回调驱动,形成连续发送直到缓冲区清空
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
* - 防止在检查count和写入buffer之间被ISR打断
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_Send(const uint8_t *data, uint16_t len)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 参数合法性检查,防止空指针或零长度 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (len == 0 || data == NULL) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint16_t written = 0;
|
|
|
|
|
|
bool needs_kickoff = false;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 临界区:禁用中断,保护共享缓冲区的写入操作
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
for (uint16_t i = 0; i < len; i++) {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 检查缓冲区是否有空间 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (tx_ring.count >= UART2_TX_BUFFER_SIZE) {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 缓冲区已满,丢弃数据并计数 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
tx_ring.overflow_count++;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/* 将数据写入环形缓冲区,更新写指针(模缓冲区大小回绕) */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
tx_ring.buffer[tx_ring.head] = data[i];
|
|
|
|
|
|
tx_ring.head = (tx_ring.head + 1) % UART2_TX_BUFFER_SIZE;
|
|
|
|
|
|
tx_ring.count++;
|
|
|
|
|
|
written++;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 判断是否需要触发首次发送
|
|
|
|
|
|
* 条件:成功写入数据 AND UART当前处于空闲状态
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (written > 0 && !tx_ring.is_sending) {
|
|
|
|
|
|
tx_ring.is_sending = true;
|
|
|
|
|
|
needs_kickoff = true;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__enable_irq(); /* 退出临界区 */
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 启动首次发送(Kickoff)
|
|
|
|
|
|
* 从缓冲区取出第一个字节通过DMA/中断方式发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (needs_kickoff) {
|
|
|
|
|
|
uint8_t byte;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 再次禁用中断以读取尾指针(消费者操作) */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
byte = tx_ring.buffer[tx_ring.tail];
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 启动UART中断发送,发送完成后会触发TxCpltCallback */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 发送字符串到UART2
|
|
|
|
|
|
* @note 封装UART2_Print_Send,自动计算字符串长度
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param str: 待发送的以'\0'结尾的字符串指针(输入)
|
|
|
|
|
|
* @return 无返回值
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* - str必须为有效指针且以'\0'结尾
|
|
|
|
|
|
* - strlen计算长度时不包括终止符,所以实际发送的也不包括
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_String(const char *str)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 空指针保护 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (str == NULL) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 使用strlen获取字符串长度并发送 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
UART2_Print_Send((const uint8_t *)str, strlen(str));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 格式化打印到UART2
|
|
|
|
|
|
* @note 仿printf风格,支持可变参数格式化输出
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param fmt: 格式化字符串,与printf语法兼容(输入)
|
|
|
|
|
|
* @param ...: 可变参数列表,对应格式化字符串中的占位符(输入)
|
|
|
|
|
|
* @return 无返回值
|
|
|
|
|
|
*
|
|
|
|
|
|
* 实现说明:
|
|
|
|
|
|
* 1. 使用va_list/va_start/va_end处理可变参数
|
|
|
|
|
|
* 2. vsnprintf将格式化参数写入临时缓冲区(128字节)
|
|
|
|
|
|
* 3. 将格式化后的字符串通过UART2_Print_Send发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 缓冲区限制:
|
|
|
|
|
|
* - 最大格式化输出为127字节(128-1留作字符串终止符)
|
|
|
|
|
|
* - 超出部分会被截断,不报错
|
|
|
|
|
|
*
|
|
|
|
|
|
* 线程安全:
|
|
|
|
|
|
* 此函数本身是中断安全的,但内部调用UART2_Print_Send,
|
|
|
|
|
|
* 多线程并发调用时输出可能交叉
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_Printf(const char *fmt, ...)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 格式化字符串合法性检查 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (fmt == NULL) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
char buffer[128]; /* 临时格式化缓冲区,大小固定 */
|
|
|
|
|
|
va_list args; /* 可变参数列表变量 */
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 可变参数处理:初始化va_list并执行格式化
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
va_start(args, fmt);
|
|
|
|
|
|
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
|
|
|
|
|
|
va_end(args);
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 发送格式化后的字符串
|
|
|
|
|
|
* vsnprintf返回值len:
|
|
|
|
|
|
* - >=0: 成功格式化,需要发送的字符数(不含终止符)
|
|
|
|
|
|
* - <0: 格式化失败,不发送任何数据
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (len >= 0) {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 边界检查:若len超过缓冲区实际容量则截断 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (len >= (int)sizeof(buffer)) {
|
|
|
|
|
|
len = sizeof(buffer) - 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
UART2_Print_Send((const uint8_t *)buffer, len);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART2打印任务(轮询模式驱动)
|
|
|
|
|
|
* @note 在主循环或定时器中调用,驱动环形缓冲区数据发送
|
|
|
|
|
|
* 与TxCpltCallback配合形成轮询+中断混合发送模式
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 工作原理:
|
|
|
|
|
|
* 正常发送流程依赖TxCpltCallback中断回调驱动。但若某些平台
|
|
|
|
|
|
* 或情况下中断被屏蔽,此函数作为备用发送机制,从缓冲区取出
|
|
|
|
|
|
* 数据启动新的发送。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用时机:
|
|
|
|
|
|
* 建议在主循环中周期性调用(如每次循环或10ms定时),
|
|
|
|
|
|
* 配合UART空闲中断或DMA完成中断实现高效发送
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护临界区
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_Task(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t byte;
|
|
|
|
|
|
uint16_t current_tail;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 临界区:检查是否可以发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
__disable_irq();
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 条件:不正在发送 且 缓冲区有数据 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (tx_ring.is_sending || tx_ring.count == 0) {
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 16:21:00 +08:00
|
|
|
|
return; /* 条件不满足,不执行发送 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
}
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 从缓冲区取出数据,更新读指针
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
current_tail = tx_ring.tail;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE; /* 环形回绕 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
tx_ring.count--;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
tx_ring.is_sending = true; /* 标记为发送中,防止重复启动 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
byte = tx_ring.buffer[current_tail];
|
2026-03-27 16:21:00 +08:00
|
|
|
|
__enable_irq(); /* 退出临界区 */
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 启动UART中断发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief UART发送完成回调函数
|
|
|
|
|
|
* @note 应在UART TX完成中断中调用,负责驱动环形缓冲区连续发送
|
|
|
|
|
|
* 此函数是发送流程的核心驱动引擎
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return 无
|
|
|
|
|
|
*
|
|
|
|
|
|
* 工作原理 - 消费者逻辑:
|
|
|
|
|
|
* 1. 清除is_sending标志,表示UART硬件已空闲
|
|
|
|
|
|
* 2. 检查环形缓冲区是否还有待发送数据
|
|
|
|
|
|
* 3. 如有,取出下一字节并启动新的发送
|
|
|
|
|
|
* 4. 重复上述过程直到缓冲区清空
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用__disable_irq/__enable_irq保护共享数据访问
|
|
|
|
|
|
* - 在中断上下文调用,必须确保操作原子性
|
|
|
|
|
|
*
|
|
|
|
|
|
* 调用约定:
|
|
|
|
|
|
* 此函数需要与HAL库中断处理正确关联,通常在
|
|
|
|
|
|
* HAL_UART_TxCpltCallback中断回调中调用
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
void UART2_Print_TxCpltCallback(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint8_t byte;
|
|
|
|
|
|
uint16_t current_tail;
|
|
|
|
|
|
bool has_more = false;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 第一步:标记UART为空闲状态
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
__disable_irq();
|
|
|
|
|
|
tx_ring.is_sending = false;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 第二步:检查缓冲区是否有更多数据待发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (tx_ring.count > 0) {
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/* 取出下一字节,更新读指针(环形回绕) */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
current_tail = tx_ring.tail;
|
|
|
|
|
|
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE;
|
|
|
|
|
|
tx_ring.count--;
|
2026-03-27 16:21:00 +08:00
|
|
|
|
tx_ring.is_sending = true; /* 立即标记为发送中 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
byte = tx_ring.buffer[current_tail];
|
2026-03-27 16:21:00 +08:00
|
|
|
|
has_more = true; /* 标记有待发送数据 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
}
|
|
|
|
|
|
__enable_irq();
|
2026-03-27 16:21:00 +08:00
|
|
|
|
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
|
|
|
|
* 第三步:如有待发送数据,立即启动下一次发送
|
|
|
|
|
|
*----------------------------------------------------------*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
if (has_more) {
|
|
|
|
|
|
HAL_UART_Transmit_IT(&huart2, &byte, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 查询发送模块忙状态
|
|
|
|
|
|
* @note 用于判断是否有数据正在发送或缓冲区中是否有待发数据
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return bool: true=忙(正在发送或缓冲区有数据),false=空闲
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 在进入低功耗模式前检查是否有数据待发送
|
|
|
|
|
|
* - 在系统关机前确认所有调试日志已发送完毕
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用临界区保护,确保检查到返回之间数据不被修改
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
bool UART2_Print_IsBusy(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool busy;
|
|
|
|
|
|
__disable_irq();
|
|
|
|
|
|
busy = tx_ring.is_sending || (tx_ring.count > 0);
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
return busy;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 查询发送缓冲区可用空间
|
|
|
|
|
|
* @note 返回环形缓冲区当前剩余可写入的空间大小
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return uint16_t: 可用字节数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 计算公式:available = buffer_size - count
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用场景:
|
|
|
|
|
|
* - 在发送大数据块前检查缓冲区是否足够容纳
|
|
|
|
|
|
* - 实现流量控制逻辑
|
|
|
|
|
|
*
|
|
|
|
|
|
* 中断安全:
|
|
|
|
|
|
* - 使用临界区保护
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint16_t UART2_Print_Available(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
uint16_t available;
|
|
|
|
|
|
__disable_irq();
|
|
|
|
|
|
available = UART2_TX_BUFFER_SIZE - tx_ring.count;
|
|
|
|
|
|
__enable_irq();
|
|
|
|
|
|
return available;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief 获取溢出错误计数
|
|
|
|
|
|
* @note 统计因缓冲区满导致数据丢弃的次数,用于诊断
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param 无
|
|
|
|
|
|
* @return uint16_t: 溢出错误累计次数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 使用说明:
|
|
|
|
|
|
* - 此计数器仅在缓冲区满时递增
|
|
|
|
|
|
* - 若计数持续增长,说明UART发送速度跟不上数据产生速度
|
|
|
|
|
|
* - 可通过增大UART2_TX_BUFFER_SIZE或降低打印频率解决
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
uint16_t UART2_Print_GetOverflowCount(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
return tx_ring.overflow_count;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/*==============================================================================
|
|
|
|
|
|
* 标准库printf重定向实现
|
|
|
|
|
|
*============================================================================*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
/**
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* @brief printf重定向函数 (Keil MDK编译器)
|
|
|
|
|
|
* @note 将标准库printf输出重定向到UART2
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ch: 待发送字符(输入)
|
|
|
|
|
|
* @param f: 文件指针(未使用,Keil MDK参数要求)
|
|
|
|
|
|
* @return int: 发送的字符
|
|
|
|
|
|
*
|
|
|
|
|
|
* 编译器条件编译:
|
|
|
|
|
|
* 此函数仅在__CC_ARM(或__ARMCC_VERSION)定义时编译,
|
|
|
|
|
|
* 即Keil MDK-ARM/ARMCC编译器环境下生效
|
|
|
|
|
|
*
|
|
|
|
|
|
* 实现说明:
|
|
|
|
|
|
* 标准库printf最终会调用fputc输出每个字符,
|
|
|
|
|
|
* 此处重定向到UART2_Print_Send实现串口打印
|
2026-03-27 10:09:13 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#if defined(__CC_ARM) || defined(__ARMCC_VERSION)
|
|
|
|
|
|
int fputc(int ch, FILE *f)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
(void)f; /* 未使用参数,避免编译器警告 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
UART2_Print_Send((uint8_t *)&ch, 1);
|
|
|
|
|
|
return ch;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-27 16:21:00 +08:00
|
|
|
|
* @brief printf重定向函数 (GCC编译器 - 单字符版本)
|
|
|
|
|
|
* @note 将标准库printf输出重定向到UART2
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param ch: 待发送字符(输入)
|
|
|
|
|
|
* @return int: 发送的字符
|
|
|
|
|
|
*
|
|
|
|
|
|
* 编译器条件编译:
|
|
|
|
|
|
* 此函数仅在__GNUC__定义时编译,即GCC/Clang编译器环境下生效
|
|
|
|
|
|
*
|
|
|
|
|
|
* 实现说明:
|
|
|
|
|
|
* 新一代ARM GCC使用__io_putchar作为printf输出目标,
|
|
|
|
|
|
* 此处将其重定向到UART2_Print_Send
|
2026-03-27 10:09:13 +08:00
|
|
|
|
*/
|
|
|
|
|
|
#if defined(__GNUC__)
|
|
|
|
|
|
int __io_putchar(int ch)
|
|
|
|
|
|
{
|
|
|
|
|
|
UART2_Print_Send((uint8_t *)&ch, 1);
|
|
|
|
|
|
return ch;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:21:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief write系统调用重定向 (GCC编译器)
|
|
|
|
|
|
* @note 将文件系统write调用重定向到UART2
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param file: 文件描述符(未使用,仅为兼容标准接口)
|
|
|
|
|
|
* @param ptr: 数据缓冲区指针(输入)
|
|
|
|
|
|
* @param len: 数据长度(输入)
|
|
|
|
|
|
* @return int: 已发送的字节数
|
|
|
|
|
|
*
|
|
|
|
|
|
* 实现说明:
|
|
|
|
|
|
* 有些GCC配置下printf会调用_write而非__io_putchar,
|
|
|
|
|
|
* 此函数提供完整的write接口兼容
|
|
|
|
|
|
*/
|
2026-03-27 10:09:13 +08:00
|
|
|
|
int _write(int file, char *ptr, int len)
|
|
|
|
|
|
{
|
2026-03-27 16:21:00 +08:00
|
|
|
|
(void)file; /* 忽略文件描述符,只处理标准输出 */
|
2026-03-27 10:09:13 +08:00
|
|
|
|
UART2_Print_Send((uint8_t *)ptr, len);
|
|
|
|
|
|
return len;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|