实现串口IDLE+DMA双缓冲区+环形缓冲区
1.CbueMX配置
模式配置
DMA配置
参数配置
2.环形缓冲区
这部分代码参考于netube99/RingBuffer: 基于C语言开发的环形缓冲区
RingBuffer.h
#ifndef __RING_BUFFER_H_ #define __RING_BUFFER_H_ #include "stdint.h" /** * @brief 返回值定义 */ #define RING_BUFFER_SUCCESS 0x01 #define RING_BUFFER_ERROR 0x00 /** * @brief 环形缓冲区结构体 */ typedef struct { uint32_t Head; // 操作头指针 uint32_t Tail; // 操作尾指针 uint32_t Length; // 已储存的数据量 uint8_t* ArrayAddr; // 缓冲区储存数组基地址 uint32_t MaxLength; // 缓冲区最大可储存数据量 } RingBuffer_TypeDef; /** * @brief 初始化新缓冲区 * @param rb_handle 待初始化的缓冲区结构体句柄 * @param buffer_addr 外部定义的缓冲区数组,类型必须为 uint8_t * @param buffer_size 外部定义的缓冲区数组空间 * @return 返回缓冲区初始化的结果 */ uint8_t RB_Init(RingBuffer_TypeDef* rb_handle, uint8_t* buffer_addr, uint32_t buffer_size); /** * @brief 从头指针开始删除指定长度的数据 * @param rb_handle 缓冲区结构体句柄 * @param Length 要删除的长度 * @return 返回删除指定长度数据结果 */ uint8_t RB_Delete(RingBuffer_TypeDef* rb_handle, uint32_t Length); /** * @brief 向缓冲区尾指针写一个字节 * @param rb_handle 缓冲区结构体句柄 * @param data 要写入的字节 * @return 返回写入结果 */ uint8_t RB_WriteByte(RingBuffer_TypeDef* rb_handle, uint8_t data); /** * @brief 向缓冲区尾指针写指定长度数据 * @param rb_handle 缓冲区结构体句柄 * @param input_addr 要写入的字符串地址 * @param write_Length 要写入的字符串长度 * @return 返回写入结果 */ uint8_t RB_WriteString(RingBuffer_TypeDef* rb_handle, uint8_t* input_addr, uint32_t write_Length); /** * @brief 从缓冲区头指针读一个字节 * @param rb_handle 缓冲区结构体句柄 * @param output_addr 读取到的字节地址 * @return 返回读取结果 */ uint8_t RB_ReadByte(RingBuffer_TypeDef* rb_handle, uint8_t* output_addr); /** * @brief 从缓冲区头指针读指定长度数据 * @param rb_handle 缓冲区结构体句柄 * @param output_addr 读取到的字符串地址 * @param read_Length 要读取的字符串长度 * @return 返回读取结果 */ uint8_t RB_ReadString(RingBuffer_TypeDef* rb_handle, uint8_t* output_addr, uint32_t read_Length); /** * @brief 获取缓冲区里已储存的数据长度 * @param rb_handle 缓冲区结构体句柄 * @return 返回缓冲区里已储存的数据长度 */ uint32_t RB_GetLength(RingBuffer_TypeDef* rb_handle); /** * @brief 获取缓冲区可用储存空间 * @param rb_handle 缓冲区结构体句柄 * @return 返回缓冲区可用储存空间 */ uint32_t RB_GetFreeSize(RingBuffer_TypeDef* rb_handle); #endifRingBuffer.c
#include <stdint.h> #include <string.h> #include "RingBuffer.h" /** * @brief 初始化新缓冲区 * @param rb_handle 待初始化的缓冲区结构体句柄 * @param buffer_addr 外部定义的缓冲区数组,类型必须为 uint8_t * @param buffer_size 外部定义的缓冲区数组空间 * @return 返回缓冲区初始化的结果 */ uint8_t RB_Init(RingBuffer_TypeDef* rb_handle, uint8_t* buffer_addr, uint32_t buffer_size) { // 缓冲区数组空间必须大于2且小于数据类型最大值 if (buffer_size < 2 || buffer_size == 0xFFFFFFFF) { return RING_BUFFER_ERROR; // 初始化失败 } rb_handle->Head = 0; // 复位头指针 rb_handle->Tail = 0; // 复位尾指针 rb_handle->Length = 0; // 复位已存储数据长度 rb_handle->ArrayAddr = buffer_addr; // 缓冲区储存数组基地址 rb_handle->MaxLength = buffer_size; // 缓冲区最大可储存数据量 return RING_BUFFER_SUCCESS; // 缓冲区初始化成功 } /** * @brief 从头指针开始删除指定长度的数据 * @param rb_handle 缓冲区结构体句柄 * @param Length 要删除的长度 * @return 返回删除指定长度数据结果 */ uint8_t RB_Delete(RingBuffer_TypeDef* rb_handle, uint32_t Length) { if (rb_handle->Length < Length) { return RING_BUFFER_ERROR; // 已储存的数据量小于需删除的数据量 } else { if ((rb_handle->Head + Length) >= rb_handle->MaxLength) { rb_handle->Head = Length - (rb_handle->MaxLength - rb_handle->Head); } else { rb_handle->Head += Length; // 头指针向前推进,抛弃数据 } rb_handle->Length -= Length; // 重新记录有效数据长度 return RING_BUFFER_SUCCESS; // 已储存的数据量小于需删除的数据量 } } /** * @brief 向缓冲区尾部写一个字节 * @param rb_handle 缓冲区结构体句柄 * @param data 要写入的字节 * @return 返回缓冲区写字节的结果 * @arg RING_BUFFER_SUCCESS: 写入成功 * @arg RING_BUFFER_ERROR: 写入失败 */ uint8_t RB_WriteByte(RingBuffer_TypeDef* rb_handle, uint8_t data) { // 缓冲区数组已满,产生覆盖错误 if (rb_handle->Length == (rb_handle->MaxLength)) { return RING_BUFFER_ERROR; // 缓冲区已满 } else { *(rb_handle->ArrayAddr + rb_handle->Tail) = data; // 基地址+偏移量,存放数据 rb_handle->Length++; // 数据量计数+1 rb_handle->Tail++; // 尾指针后移 } // 如果尾指针超越了数组末尾,尾指针指向缓冲区数组开头,形成闭环 if (rb_handle->Tail > (rb_handle->MaxLength - 1)) { rb_handle->Tail = 0; } return RING_BUFFER_SUCCESS; } /** * @brief 从缓冲区头指针读取一个字节 * @param rb_handle 缓冲区结构体句柄 * @param output_addr 读取的字节保存地址 * @return 返回读取状态 * @arg RING_BUFFER_SUCCESS: 读取成功 * @arg RING_BUFFER_ERROR: 读取失败 */ uint8_t RB_ReadByte(RingBuffer_TypeDef* rb_handle, uint8_t* output_addr) { if (rb_handle->Length != 0) { // 有数据未读出 *output_addr = *(rb_handle->ArrayAddr + rb_handle->Head); // 读取数据 rb_handle->Head++; rb_handle->Length--; // 数据量计数-1 // 如果头指针超越了数组末尾,头指针指向数组开头,形成闭环 if (rb_handle->Head > (rb_handle->MaxLength - 1)) { rb_handle->Head = 0; } return RING_BUFFER_SUCCESS; } return RING_BUFFER_ERROR; } /** * @brief 向缓冲区尾指针写指定长度的数据 * @param rb_handle 缓冲区结构体句柄 * @param input_addr 待写入数据的基地址 * @param write_Length 要写入的字节数 * @return 返回缓冲区尾部写指定长度字节的结果 * @arg RING_BUFFER_SUCCESS: 写入成功 * @arg RING_BUFFER_ERROR: 写入失败 */ uint8_t RB_WriteString(RingBuffer_TypeDef* rb_handle, uint8_t* input_addr, uint32_t write_Length) { // 可写入长度,剩余长度 uint32_t write_size_a = 0, write_size_b = 0; // 如果不够存储空间存放新数据,返回错误 // 注意:写成 (Length + write_Length) > MaxLength 会在 write_Length 接近 UINT32_MAX 时先溢出, // 从而绕过检查并产生超大 memcpy;这里改成先算剩余空间,避免加法溢出 if (write_Length > (rb_handle->MaxLength - rb_handle->Length)) { return RING_BUFFER_ERROR; } else { // 如果顺序可用长度小于需写入的长度,需要将数据拆成两次分别写入 if ((rb_handle->MaxLength - rb_handle->Tail) < write_Length) { write_size_a = rb_handle->MaxLength - rb_handle->Tail; // 缓冲区尾部可用空间 write_size_b = write_Length - write_size_a; // 剩余数据量 // 分别拷贝a、b段数据到储存数组中 memcpy(rb_handle->ArrayAddr + rb_handle->Tail, input_addr, write_size_a); memcpy(rb_handle->ArrayAddr, input_addr + write_size_a, write_size_b); rb_handle->Length += write_Length; // 记录新存储了多少数据量 rb_handle->Tail = write_size_b; // 重新定位尾指针位置 } else { write_size_a = write_Length; // 从尾指针开始写数据 memcpy(rb_handle->ArrayAddr + rb_handle->Tail, input_addr, write_size_a); rb_handle->Length += write_Length; // 记录新存储了多少数据量 rb_handle->Tail += write_size_a; // 重新定位尾指针位置 if (rb_handle->Tail == rb_handle->MaxLength) { rb_handle->Tail = 0; // 如果写入数据后尾指针刚好写到数组尾部,则回到开头,防止越位 } } return RING_BUFFER_SUCCESS; } } /** * @brief 从缓冲区头指针读指定长度的数据,保存到指定的地址 * @param rb_handle 缓冲区结构体句柄 * @param output_addr 读取的数据保存地址 * @param read_Length 要读取的字节数 * @return 返回缓冲区头指针读指定长度字节的结果 * @arg RING_BUFFER_SUCCESS: 读取成功 * @arg RING_BUFFER_ERROR: 读取失败 */ uint8_t RB_ReadString(RingBuffer_TypeDef* rb_handle, uint8_t* output_addr, uint32_t read_Length) { // 可读取长度,剩余长度 uint32_t Read_size_a = 0, Read_size_b = 0; // 如果读取长度大于已储存数据量,返回错误 if (read_Length > rb_handle->Length) { return RING_BUFFER_ERROR; } else { // 读取长度大于缓冲区头指针到数组末尾的距离,需要将数据拆成两次分别读取 if (read_Length > (rb_handle->MaxLength - rb_handle->Head)) { Read_size_a = rb_handle->MaxLength - rb_handle->Head; // 缓冲区头部可用空间 Read_size_b = read_Length - Read_size_a; // 剩余长度 memcpy(output_addr, rb_handle->ArrayAddr + rb_handle->Head, Read_size_a); memcpy(output_addr + Read_size_a, rb_handle->ArrayAddr, Read_size_b); rb_handle->Length -= read_Length; // 记录剩余数据量 rb_handle->Head = Read_size_b; // 重新定位头指针位置 } else { Read_size_a = read_Length; memcpy(output_addr, rb_handle->ArrayAddr + rb_handle->Head, Read_size_a); rb_handle->Length -= read_Length; // 记录剩余数据量 rb_handle->Head += Read_size_a; // 重新定位头指针位置 if (rb_handle->Head == rb_handle->MaxLength) rb_handle->Head = 0; // 如果读取数据后头指针刚好写到数组尾部,则回到开头,防止越位 } return RING_BUFFER_SUCCESS; } } /** * @brief 获取缓冲区里已储存的数据长度 * @param rb_handle 缓冲区结构体句柄 * @return 返回缓冲区里已储存的数据长度 */ uint32_t RB_GetLength(RingBuffer_TypeDef* rb_handle) { return rb_handle->Length; } /** * @brief 获取缓冲区可用储存空间 * @param rb_handle 缓冲区结构体句柄 * @return 返回缓冲区可用储存空间 */ uint32_t RB_GetFreeSize(RingBuffer_TypeDef* rb_handle) { return (rb_handle->MaxLength - rb_handle->Length); }3.串口驱动
bsp_uart.h
#ifndef __BSP_UART_H__ #define __BSP_UART_H__ #include "stm32g4xx_hal.h" #include "RingBuffer.h" extern DMA_HandleTypeDef hdma_usart3_rx; extern DMA_HandleTypeDef hdma_usart3_tx; // 接收 DMA 双缓冲区大小(必须为偶数:前半区 + 后半区),中断响应慢时加大本宏并同步加大 RX_RB_BUF_SIZE #define RX_DMA_BUF_SIZE 256 // 接收环形缓冲区大小(需大于 RX_DMA_BUF_SIZE) #define RX_RB_BUF_SIZE 512 // 发送环形缓冲区大小 #define TX_RB_BUF_SIZE 512 /** * @brief USART3 接收 DMA 双缓冲结构体 */ typedef struct { uint16_t DMA_RB_Pos; // DMA 缓冲区中已搬运到环形缓冲区的位置 uint16_t Rx_DMA_CurrPos; // DMA 缓冲区当前写入位置 uint16_t Rx_FrameLen; // 自上次空闲中断以来收到的字节数(当前帧长度) uint8_t Rx_DMA_Buf[RX_DMA_BUF_SIZE]; // DMA 双缓冲区:前半区 + 后半区 } UART_Rx_DMA_TypeDef; extern UART_Rx_DMA_TypeDef Uart3_Rx_DMA_t; /** * @brief USART3 发送 DMA 双缓冲结构体 */ typedef struct { uint16_t Tx_Len; // 本次 DMA 正在发送的数据长度(发送完成后才从环形缓冲区删除) uint8_t TxRB_Buf[TX_RB_BUF_SIZE]; // 发送环形缓冲区存储空间 } UART_Tx_DMA_TypeDef; extern UART_Tx_DMA_TypeDef Uart3_Tx_DMA_t; // 接收环形缓冲区 extern RingBuffer_TypeDef Uart3_RxRB_t; // 发送环形缓冲区 extern RingBuffer_TypeDef Uart3_TxRB_t; /** * @brief 初始化 USART3:DMA 双缓冲接收 + 空闲中断 + 收发环形缓冲区 * @note 必须在 MX_USART3_UART_Init() 之后调用 * @note HAL_UARTEx_ReceiveToIdle_DMA() 返回非 HAL_OK(例如 RxState 不是 READY)时接收不会启动, * 此时需要检查 MX_USART3_UART_Init() 是否已执行、或是否有一次接收还没结束 */ void Uart3_Init(void); /** * @brief 非阻塞发送:数据拷贝进发送环形缓冲区后立即返回,由 DMA 自动发完 * @param buf 数据地址 * @param len 数据长度(不能超过 TX_RB_BUF_SIZE) * @return RING_BUFFER_SUCCESS:已写入; RING_BUFFER_ERROR:参数错误或发送缓冲区空间不足 * @note 返回后 buf 可以立即复用/释放;空间不足时整条不写入,应用可稍后重试 */ uint8_t Uart3_Send(const uint8_t* buf, uint16_t len); /** * @brief 单字节非阻塞发送(串口打印的底层出口) * @param ch 待发送字节 * @return RING_BUFFER_SUCCESS:已写入发送缓冲区; RING_BUFFER_ERROR:发送缓冲区满,该字节被丢弃 * @note 不阻塞:打印量太大时中间的字符会被丢掉,避免在中断里长时间等待 */ uint8_t Uart3_SendByte(uint8_t ch); /** * @brief USART3 空闲中断回调:计算当前 DMA 已接收但尚未处理的数据,写入环形缓冲区 * @param huart UART 句柄 * @note HAL 本身没有这个回调,由 HAL_UARTEx_RxEventCallback() 在空闲事件时调用 */ void HAL_UART_RxIdleCallback(UART_HandleTypeDef* huart); /** * @brief 一帧接收完成(弱函数,应用层可重定义) * @param len 本帧长度(自上次空闲中断以来收到的字节数) * @note 在三个接收回调把数据写入环形缓冲区之后调用,可直接 RB_ReadString() 取这一帧 * @note 约定:重定义时必须把整帧从 Uart3_RxRB_t 里读干净(包括超长帧要丢弃的剩余部分), * 否则残留数据会顶在下一帧前面,之后每次解析都会错位 */ void Uart3_RxFrameCallback(uint16_t len); #endifbsp_uart.c
#include "bsp_uart.h" #include "usart.h" /** * @brief USART3 接收环形缓冲区 */ static uint8_t Uart3_RB_Buf[RX_RB_BUF_SIZE]; UART_Rx_DMA_TypeDef Uart3_Rx_DMA_t = { 0 }; UART_Tx_DMA_TypeDef Uart3_Tx_DMA_t = { 0 }; RingBuffer_TypeDef Uart3_RxRB_t = { 0 }; RingBuffer_TypeDef Uart3_TxRB_t = { 0 }; /* 内部函数声明 */ static void Uart3_TxStart(void); /** * @brief 初始化 USART3 */ void Uart3_Init(void) { // 初始化接收环形缓冲区,缓冲区需大于DMA缓冲区 RB_Init(&Uart3_RxRB_t, Uart3_RB_Buf, RX_RB_BUF_SIZE); // 初始化发送环形缓冲区(放在启动接收之前:否则刚启动就收到帧时,回显用的发送缓冲区还没准备好) RB_Init(&Uart3_TxRB_t, Uart3_Tx_DMA_t.TxRB_Buf, TX_RB_BUF_SIZE); Uart3_Tx_DMA_t.Tx_Len = 0; // 复位接收状态 Uart3_Rx_DMA_t.DMA_RB_Pos = 0; Uart3_Rx_DMA_t.Rx_DMA_CurrPos = 0; Uart3_Rx_DMA_t.Rx_FrameLen = 0; // 启动 DMA 双缓冲接收 + 空闲中断 HAL_UARTEx_ReceiveToIdle_DMA(&huart3, Uart3_Rx_DMA_t.Rx_DMA_Buf, RX_DMA_BUF_SIZE); } /** * @brief DMA 接受半缓冲区回调,防止在全满中断时数据还在接受导致数据丢失 * @note 此时前半缓冲区也被空闲中断写入环形缓冲区,后半缓冲区继续接收 */ void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef* huart) { uint16_t prev_pos; // DMA缓冲区已处理数据的位置 if (huart->Instance == USART3) { prev_pos = Uart3_Rx_DMA_t.DMA_RB_Pos; // 前prev_pos位置已经被空闲中断(半DMA缓冲区前面部分触发)或者全满中断处理过了,只需处理剩余DMA数据,即prev_pos到RX_DMA_BUF_SIZE/2的数据 if (prev_pos < (RX_DMA_BUF_SIZE / 2)) { uint16_t len = (RX_DMA_BUF_SIZE / 2) - prev_pos; // 只统计真正写进环形缓冲区的字节:缓冲区满时写不进去的不能算进帧长,否则下一帧会错位 if (RB_WriteString(&Uart3_RxRB_t, &Uart3_Rx_DMA_t.Rx_DMA_Buf[prev_pos], len) == RING_BUFFER_SUCCESS) { Uart3_Rx_DMA_t.Rx_FrameLen += len; } // 更新DMA缓冲区已处理数据的位置 Uart3_Rx_DMA_t.DMA_RB_Pos = RX_DMA_BUF_SIZE / 2; } // DMA 缓冲区当前写入位置(以 DMA 计数器为准,与下面两个回调保持一致) Uart3_Rx_DMA_t.Rx_DMA_CurrPos = RX_DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx); } } /** * @brief DMA 接收传输完成回调(TC) * @note 此时后半缓冲区写入环形缓冲区 * @note 以 DMA 计数器计算当前位置:回绕后位置回到起点,不能用固定的末尾位置 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart) { uint16_t prev_pos; // DMA缓冲区已处理数据的位置 uint16_t curr_pos; // DMA缓冲区当前写入位置 if (huart->Instance == USART3) { // 计算当前DMA缓冲区的偏移量,循环模式下 DMA 不会停:整个缓冲区收满只是"事件时刻",进中断时 NDTR 早已不是 0 curr_pos = RX_DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx); prev_pos = Uart3_Rx_DMA_t.DMA_RB_Pos; Uart3_Rx_DMA_t.Rx_DMA_CurrPos = curr_pos; if (curr_pos < prev_pos) { // DMA 已经回绕,先把DMA缓冲区的末尾剩余数据写入 uint16_t len = RX_DMA_BUF_SIZE - prev_pos; if (RB_WriteString(&Uart3_RxRB_t, &Uart3_Rx_DMA_t.Rx_DMA_Buf[prev_pos], len) == RING_BUFFER_SUCCESS) { Uart3_Rx_DMA_t.Rx_FrameLen += len; } prev_pos = 0; } // 后半缓冲区写入环形缓冲区 if (curr_pos > prev_pos) { uint16_t len = curr_pos - prev_pos; if (RB_WriteString(&Uart3_RxRB_t, &Uart3_Rx_DMA_t.Rx_DMA_Buf[prev_pos], len) == RING_BUFFER_SUCCESS) { Uart3_Rx_DMA_t.Rx_FrameLen += len; } } // 更新DMA缓冲区已处理数据的位置 Uart3_Rx_DMA_t.DMA_RB_Pos = curr_pos; } } /** * @brief USART3 空闲中断回调 * @note 当检测到线路空闲时,计算当前 DMA 已接收但尚未处理的数据,将其写入环形缓冲区 * @note HAL 本身没有这个回调,由 HAL_UARTEx_RxEventCallback() 在空闲事件时调用 */ void HAL_UART_RxIdleCallback(UART_HandleTypeDef* huart) { uint16_t prev_pos; // DMA缓冲区已处理数据的位置 uint16_t curr_pos; // DMA缓冲区当前写入位置 if (huart->Instance == USART3) { // 计算当前DMA缓冲区的偏移量 curr_pos = RX_DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart3_rx); prev_pos = Uart3_Rx_DMA_t.DMA_RB_Pos; Uart3_Rx_DMA_t.Rx_DMA_CurrPos = curr_pos; if (curr_pos > prev_pos) { uint16_t len = curr_pos - prev_pos; if (RB_WriteString(&Uart3_RxRB_t, &Uart3_Rx_DMA_t.Rx_DMA_Buf[prev_pos], len) == RING_BUFFER_SUCCESS) { Uart3_Rx_DMA_t.Rx_FrameLen += len; } } // 更新DMA缓冲区已处理数据的位置 Uart3_Rx_DMA_t.DMA_RB_Pos = curr_pos; } } /** * @brief UART 接收事件回调,把 HAL 的三种接收事件分发给上面三个回调 * @param huart UART 句柄 * @param Size HAL 传入的已接收数据位置 * @note TOIDLE 模式(ReceiveToIdle_DMA)下三种事件都进本函数,由 huart->RxEventType 区分 * @note 不使用 HAL 传入的 Size:空闲恰好发生在回绕点上时 HAL 会传入整个缓冲区大小 */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef* huart, uint16_t Size) { (void)Size; if (huart->Instance != USART3) { return; } if (huart->RxEventType == HAL_UART_RXEVENT_HT) { // 前半区收满 HAL_UART_RxHalfCpltCallback(huart); } else if (huart->RxEventType == HAL_UART_RXEVENT_TC) { // 后半区收满 HAL_UART_RxCpltCallback(huart); } else if (huart->RxEventType == HAL_UART_RXEVENT_IDLE) { // 总线空闲,一帧接收结束 HAL_UART_RxIdleCallback(huart); if (Uart3_Rx_DMA_t.Rx_FrameLen > 0) { Uart3_RxFrameCallback(Uart3_Rx_DMA_t.Rx_FrameLen); } Uart3_Rx_DMA_t.Rx_FrameLen = 0; } } /** * @brief 一帧接收完成,默认空实现,应用层可重定义 * @param len 本帧长度 */ __weak void Uart3_RxFrameCallback(uint16_t len) { (void)len; } /** * @brief 出错后重新启动接收/发送 */ void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) { if (huart->Instance != USART3) { return; } if (huart->RxState != HAL_UART_STATE_BUSY_RX) { Uart3_Rx_DMA_t.DMA_RB_Pos = 0; Uart3_Rx_DMA_t.Rx_DMA_CurrPos = 0; Uart3_Rx_DMA_t.Rx_FrameLen = 0; RB_Delete(&Uart3_RxRB_t, RB_GetLength(&Uart3_RxRB_t)); // 丢掉未接收完的残留 HAL_UARTEx_ReceiveToIdle_DMA(&huart3, Uart3_Rx_DMA_t.Rx_DMA_Buf, RX_DMA_BUF_SIZE); } if (huart->gState != HAL_UART_STATE_BUSY_TX) { Uart3_Tx_DMA_t.Tx_Len = 0; Uart3_TxStart(); } } /** * @brief 启动一段 DMA 发送,环形缓冲区回绕时只发送连续的一段 */ static void Uart3_TxStart(void) { uint16_t len = Uart3_TxRB_t.Length; uint16_t cont = Uart3_TxRB_t.MaxLength - Uart3_TxRB_t.Head; // 头指针到缓冲区末尾的连续长度 if ((len == 0) || (huart3.gState != HAL_UART_STATE_READY)) { return; // 无数据,或上一段还在发送(由发送完成中断续发) } if (len > cont) { len = cont; // 先发到头指针到缓冲区末尾这一段,剩下的一段由发送完成中断续发 } if (HAL_UART_Transmit_DMA(&huart3, &Uart3_TxRB_t.ArrayAddr[Uart3_TxRB_t.Head], len) == HAL_OK) { Uart3_Tx_DMA_t.Tx_Len = len; } } /** * @brief DMA 发送完成回调:删除已发送完的数据,并续发环形缓冲区里剩下的数据 * @note 已发送的数据必须在这里才删除:DMA 发送期间一直在读环形缓冲区,提前删除会被新数据覆盖 */ void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart) { if (huart->Instance != USART3) { return; } RB_Delete(&Uart3_TxRB_t, Uart3_Tx_DMA_t.Tx_Len); // 释放本次已经发送完的数据 Uart3_Tx_DMA_t.Tx_Len = 0; Uart3_TxStart(); // 环形缓冲区里还有数据则继续发送 } /** * @brief 非阻塞发送:数据拷贝进发送环形缓冲区后立即返回,由 DMA 自动发完 * @param buf 数据地址 * @param len 数据长度(不能超过 TX_RB_BUF_SIZE) * @return RING_BUFFER_SUCCESS:已写入; RING_BUFFER_ERROR:参数错误或发送缓冲区空间不足 */ uint8_t Uart3_Send(const uint8_t* buf, uint16_t len) { uint8_t ret; uint32_t irq; if ((buf == NULL) || (len == 0) || (len > TX_RB_BUF_SIZE)) { return RING_BUFFER_ERROR; // 参数错误:空指针 / 空数据 / 超过发送缓冲区容量 } irq = __get_PRIMASK(); __disable_irq(); // 与发送完成中断互斥:中断里会删除已发数据并更新长度计数 ret = RB_WriteString(&Uart3_TxRB_t, (uint8_t*)buf, len); __set_PRIMASK(irq); if (ret == RING_BUFFER_SUCCESS) { Uart3_TxStart(); // 上一段已发完则立即启动发送 } return ret; } /** * @brief 单字节非阻塞发送(Uart3_Send 的单字节封装,串口打印用) * @param ch 待发送字节 * @return RING_BUFFER_SUCCESS:已写入; RING_BUFFER_ERROR:发送缓冲区满,该字节被丢弃 */ uint8_t Uart3_SendByte(uint8_t ch) { return Uart3_Send(&ch, 1); }app_uart.c和app_uart.h的应用层实现,发送函数调用Uart3_SendByte或者Uart3_Send即可,接受函数Uart3_RxFrameCallback实现调用RB_ReadString函数读取缓冲区即可。
main.c函数初始化调用Uart3_Init()函数。