Files
433_STM32/Core/Src/uart2_print.c

510 lines
18 KiB
C
Raw Permalink Normal View History

/**
******************************************************************************
* @file uart2_print.c
* @brief UART2调试打印模块实现
* @author Application Layer
* @version 1.1
******************************************************************************
* @attention
*
*
* 1.
* 2. ISR中调用
* 3.
*
*
* v1.1 - -1/2/3-4
******************************************************************************
*/
#include "uart2_print.h"
#include "usart.h"
#include <string.h>
#include <stdio.h>
/*==============================================================================
*
*============================================================================*/
/* DEBUG_PRINT_ENABLED: 调试日志开关置1时启用模块自测日志输出 */
#define DEBUG_PRINT_ENABLED 1
#if DEBUG_PRINT_ENABLED
/* 调试日志宏,带模块前缀"[UART2]"方便过滤日志 */
#define DEBUG_LOG(fmt, ...) UART2_Print_Printf("[UART2] " fmt "\r\n", ##__VA_ARGS__)
#else
/* 禁用时为空宏,避免无用代码生成 */
#define DEBUG_LOG(fmt, ...)
#endif
/*==============================================================================
*
*============================================================================*/
/**
* @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)
*/
typedef struct {
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; /**< 溢出错误计数,统计因缓冲区满丢弃的数据 */
} ring_buffer_t;
/*==============================================================================
*
*============================================================================*/
/**
* @brief
* @note static修饰确保仅本文件内可访问
*/
static ring_buffer_t tx_ring = {0};
/*==============================================================================
*
*============================================================================*/
/**
* @brief UART2打印模块初始化
* @note UART外设初始化后调用
*
* @param
* @return
*
*
* 1. (head/tail/count)
* 2.
* 3.
*
*
* UART硬件初始化完成之后调用huart2已正确配置
*/
void UART2_Print_Init(void)
{
/* 重置环形缓冲区各状态变量 */
tx_ring.head = 0;
tx_ring.tail = 0;
tx_ring.count = 0;
tx_ring.is_sending = false;
tx_ring.overflow_count = 0;
/* 输出模块初始化完成日志 */
DEBUG_LOG("Init OK, buffer size: %d", UART2_TX_BUFFER_SIZE);
}
/**
* @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打断
*/
void UART2_Print_Send(const uint8_t *data, uint16_t len)
{
/* 参数合法性检查,防止空指针或零长度 */
if (len == 0 || data == NULL) {
return;
}
uint16_t written = 0;
bool needs_kickoff = false;
/*----------------------------------------------------------
*
*----------------------------------------------------------*/
__disable_irq();
for (uint16_t i = 0; i < len; i++) {
/* 检查缓冲区是否有空间 */
if (tx_ring.count >= UART2_TX_BUFFER_SIZE) {
/* 缓冲区已满,丢弃数据并计数 */
tx_ring.overflow_count++;
break;
}
/* 将数据写入环形缓冲区,更新写指针(模缓冲区大小回绕) */
tx_ring.buffer[tx_ring.head] = data[i];
tx_ring.head = (tx_ring.head + 1) % UART2_TX_BUFFER_SIZE;
tx_ring.count++;
written++;
}
/*----------------------------------------------------------
*
* AND UART当前处于空闲状态
*----------------------------------------------------------*/
if (written > 0 && !tx_ring.is_sending) {
tx_ring.is_sending = true;
needs_kickoff = true;
}
__enable_irq(); /* 退出临界区 */
/*----------------------------------------------------------
* (Kickoff)
* DMA/
*----------------------------------------------------------*/
if (needs_kickoff) {
uint8_t byte;
/* 再次禁用中断以读取尾指针(消费者操作) */
__disable_irq();
byte = tx_ring.buffer[tx_ring.tail];
__enable_irq();
/* 启动UART中断发送发送完成后会触发TxCpltCallback */
HAL_UART_Transmit_IT(&huart2, &byte, 1);
}
}
/**
* @brief UART2
* @note UART2_Print_Send
*
* @param str: '\0'()
* @return
*
* 使
* - str必须为有效指针且以'\0'
* - strlen计算长度时不包括终止符
*/
void UART2_Print_String(const char *str)
{
/* 空指针保护 */
if (str == NULL) {
return;
}
/* 使用strlen获取字符串长度并发送 */
UART2_Print_Send((const uint8_t *)str, strlen(str));
}
/**
* @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
* 线
*/
void UART2_Print_Printf(const char *fmt, ...)
{
/* 格式化字符串合法性检查 */
if (fmt == NULL) {
return;
}
char buffer[128]; /* 临时格式化缓冲区,大小固定 */
va_list args; /* 可变参数列表变量 */
/*----------------------------------------------------------
* va_list并执行格式化
*----------------------------------------------------------*/
va_start(args, fmt);
int len = vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
/*----------------------------------------------------------
*
* vsnprintf返回值len:
* - >=0: ()
* - <0:
*----------------------------------------------------------*/
if (len >= 0) {
/* 边界检查若len超过缓冲区实际容量则截断 */
if (len >= (int)sizeof(buffer)) {
len = sizeof(buffer) - 1;
}
UART2_Print_Send((const uint8_t *)buffer, len);
}
}
/**
* @brief UART2打印任务()
* @note
* TxCpltCallback配合形成轮询+
*
* @param
* @return
*
*
* TxCpltCallback中断回调驱动
*
*
*
*
* (10ms定时)
* UART空闲中断或DMA完成中断实现高效发送
*
*
* - 使__disable_irq/__enable_irq保护临界区
*/
void UART2_Print_Task(void)
{
uint8_t byte;
uint16_t current_tail;
/*----------------------------------------------------------
*
*----------------------------------------------------------*/
__disable_irq();
/* 条件:不正在发送 且 缓冲区有数据 */
if (tx_ring.is_sending || tx_ring.count == 0) {
__enable_irq();
return; /* 条件不满足,不执行发送 */
}
/*----------------------------------------------------------
*
*----------------------------------------------------------*/
current_tail = tx_ring.tail;
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE; /* 环形回绕 */
tx_ring.count--;
tx_ring.is_sending = true; /* 标记为发送中,防止重复启动 */
byte = tx_ring.buffer[current_tail];
__enable_irq(); /* 退出临界区 */
/*----------------------------------------------------------
* UART中断发送
*----------------------------------------------------------*/
HAL_UART_Transmit_IT(&huart2, &byte, 1);
}
/**
* @brief UART发送完成回调函数
* @note UART TX完成中断中调用
*
*
* @param
* @return
*
* -
* 1. is_sending标志UART硬件已空闲
* 2.
* 3.
* 4.
*
*
* - 使__disable_irq/__enable_irq保护共享数据访问
* -
*
*
* HAL库中断处理正确关联
* HAL_UART_TxCpltCallback中断回调中调用
*/
void UART2_Print_TxCpltCallback(void)
{
uint8_t byte;
uint16_t current_tail;
bool has_more = false;
/*----------------------------------------------------------
* UART为空闲状态
*----------------------------------------------------------*/
__disable_irq();
tx_ring.is_sending = false;
/*----------------------------------------------------------
*
*----------------------------------------------------------*/
if (tx_ring.count > 0) {
/* 取出下一字节,更新读指针(环形回绕) */
current_tail = tx_ring.tail;
tx_ring.tail = (tx_ring.tail + 1) % UART2_TX_BUFFER_SIZE;
tx_ring.count--;
tx_ring.is_sending = true; /* 立即标记为发送中 */
byte = tx_ring.buffer[current_tail];
has_more = true; /* 标记有待发送数据 */
}
__enable_irq();
/*----------------------------------------------------------
*
*----------------------------------------------------------*/
if (has_more) {
HAL_UART_Transmit_IT(&huart2, &byte, 1);
}
}
/**
* @brief
* @note
*
* @param
* @return bool: true=()false=
*
* 使
* -
* -
*
*
* - 使
*/
bool UART2_Print_IsBusy(void)
{
bool busy;
__disable_irq();
busy = tx_ring.is_sending || (tx_ring.count > 0);
__enable_irq();
return busy;
}
/**
* @brief
* @note
*
* @param
* @return uint16_t:
*
* available = buffer_size - count
*
* 使
* -
* -
*
*
* - 使
*/
uint16_t UART2_Print_Available(void)
{
uint16_t available;
__disable_irq();
available = UART2_TX_BUFFER_SIZE - tx_ring.count;
__enable_irq();
return available;
}
/**
* @brief
* @note
*
* @param
* @return uint16_t:
*
* 使
* -
* - UART发送速度跟不上数据产生速度
* - UART2_TX_BUFFER_SIZE或降低打印频率解决
*/
uint16_t UART2_Print_GetOverflowCount(void)
{
return tx_ring.overflow_count;
}
/*==============================================================================
* printf重定向实现
*============================================================================*/
/**
* @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实现串口打印
*/
#if defined(__CC_ARM) || defined(__ARMCC_VERSION)
int fputc(int ch, FILE *f)
{
(void)f; /* 未使用参数,避免编译器警告 */
UART2_Print_Send((uint8_t *)&ch, 1);
return ch;
}
#endif
/**
* @brief printf重定向函数 (GCC编译器 - )
* @note printf输出重定向到UART2
*
* @param ch: ()
* @return int:
*
*
* __GNUC__定义时编译GCC/Clang编译器环境下生效
*
*
* ARM GCC使用__io_putchar作为printf输出目标
* UART2_Print_Send
*/
#if defined(__GNUC__)
int __io_putchar(int ch)
{
UART2_Print_Send((uint8_t *)&ch, 1);
return ch;
}
/**
* @brief write系统调用重定向 (GCC编译器)
* @note write调用重定向到UART2
*
* @param file: (使)
* @param ptr: ()
* @param len: ()
* @return int:
*
*
* GCC配置下printf会调用_write而非__io_putchar
* write接口兼容
*/
int _write(int file, char *ptr, int len)
{
(void)file; /* 忽略文件描述符,只处理标准输出 */
UART2_Print_Send((uint8_t *)ptr, len);
return len;
}
#endif