基于stm32f103c8t6 的串口收发多字节代码使用DMA,用HAL库

张开发
2026/4/14 1:53:39 15 分钟阅读

分享文章

基于stm32f103c8t6 的串口收发多字节代码使用DMA,用HAL库
使用DMA方式进行串口多字节收发可以大幅降低CPU占用率特别适合大数据量传输和不定长数据帧的处理。本文将介绍**DMA固定长度收发**和**DMA空闲中断不定长接收**两种方式。一、DMA的优势二、CubeMX配置步骤2.1 基本串口配置1. 选择USART1PA9-TXPA10-RX2. 模式Asynchronous3. 参数115200波特率、8位数据、无校验、1停止位4. **使能USART1全局中断**2.2 DMA配置在DMA Settings中添加- **RX通道**USART1_RX优先级Medium模式Normal数据宽度Byte- **TX通道**USART1_TX优先级Medium模式Normal数据宽度Byte **注意**USART2_RX的DMA请求**唯一绑定至DMA1的Channel 6**这是硬件决定的不可更改。2.3 GPIO注意事项将RX引脚PA10设置为**上拉模式**Pull-up避免悬空时产生误接收。三、方式一DMA固定长度收发3.1 代码实现c/* main.c */#include main.h#include string.h/* 定义缓冲区 */#define RX_BUF_SIZE 200#define TX_BUF_SIZE 200uint8_t rx_buffer[RX_BUF_SIZE];uint8_t tx_buffer[TX_BUF_SIZE];volatile uint8_t rx_complete 0;volatile uint8_t tx_complete 1;/* 函数声明 */void UART_DMA_Init(void);void UART_DMA_Send(uint8_t *data, uint16_t len);/* 发送完成回调 */void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){if (huart-Instance USART1) {tx_complete 1; // 发送完成标志}}/* 接收完成回调 */void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){if (huart-Instance USART1) {rx_complete 1; // 接收完成标志}}/* DMA发送函数 */void UART_DMA_Send(uint8_t *data, uint16_t len){while (!tx_complete); // 等待上次发送完成tx_complete 0; // 清除标志HAL_UART_Transmit_DMA(huart1, data, len);}/* 启动DMA接收 */void UART_DMA_Start_Receive(void){HAL_UART_Receive_DMA(huart1, rx_buffer, RX_BUF_SIZE);}int main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();UART_DMA_Start_Receive();uint8_t send_data[] Hello DMA!\r\n;while (1) {/* 检查是否收到指定长度数据 */if (rx_complete) {rx_complete 0;/* 回显接收到的数据 */UART_DMA_Send(rx_buffer, RX_BUF_SIZE);/* 重新启动DMA接收 */UART_DMA_Start_Receive();}/* 定时发送测试数据 */static uint32_t last_send 0;if (HAL_GetTick() - last_send 1000) {last_send HAL_GetTick();UART_DMA_Send(send_data, sizeof(send_data) - 1);}}}四、方式二DMA空闲中断不定长接收- 推荐**空闲中断**的原理是当RX引脚收到数据后在指定时间内没有新数据到达时硬件自动产生空闲中断。结合DMA可以实现**任意长度数据帧的接收**且无需CPU参与逐字节搬运。#### 4.1 完整代码c/* main.c */#include main.h#include string.h#include stdio.h/* 缓冲区定义 */#define RX_BUF_SIZE 256uint8_t rx_dma_buffer[RX_BUF_SIZE]; // DMA接收缓冲区uint8_t rx_frame_buffer[RX_BUF_SIZE]; // 帧数据缓存volatile uint16_t rx_frame_len 0; // 接收到的帧长度volatile uint8_t rx_frame_ready 0; // 帧就绪标志/* 初始化函数 */void UART_DMA_Idle_Init(void){/* 启动DMA接收 */HAL_UART_Receive_DMA(huart1, rx_dma_buffer, RX_BUF_SIZE);/* 使能空闲中断 */__HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE);}/* 处理接收到的帧数据在主循环中调用 */void UART_Process_Received_Frame(void){if (rx_frame_ready) {rx_frame_ready 0;/* 处理接收到的数据 */printf(收到 %d 字节: %s\r\n, rx_frame_len, rx_frame_buffer);/* 可选择回显数据 */// HAL_UART_Transmit_DMA(huart1, rx_frame_buffer, rx_frame_len);/* 清空帧缓存准备下一帧 */memset(rx_frame_buffer, 0, RX_BUF_SIZE);}}/* 空闲中断处理函数在USART1_IRQHandler中调用 */void UART_IdleLine_Callback(void){/* 检查空闲中断标志 */if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) {/* 清除空闲中断标志 */__HAL_UART_CLEAR_IDLEFLAG(huart1);/* 停止DMA传输防止在处理过程中新数据覆盖 */HAL_UART_DMAStop(huart1);/* 计算接收到的数据长度 *//* CNDTR寄存器存储DMA剩余未传输的字节数 */uint16_t remaining __HAL_DMA_GET_COUNTER(huart1.hdmarx);rx_frame_len RX_BUF_SIZE - remaining;/* 将数据从DMA缓冲区复制到帧缓存 */if (rx_frame_len 0 rx_frame_len RX_BUF_SIZE) {memcpy(rx_frame_buffer, rx_dma_buffer, rx_frame_len);rx_frame_ready 1;}/* 重新初始化DMA接收缓冲区清空 */memset(rx_dma_buffer, 0, RX_BUF_SIZE);/* 重新启动DMA接收 */HAL_UART_Receive_DMA(huart1, rx_dma_buffer, RX_BUF_SIZE);}}#### 4.2 修改中断服务函数在 stm32f1xx_it.c 中修改 USART1_IRQHandlercvoid USART1_IRQHandler(void){/* 处理空闲中断 */UART_IdleLine_Callback();/* 调用HAL库标准处理函数 */HAL_UART_IRQHandler(huart1);}#### 4.3 主函数cint main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();/* 初始化DMA空闲中断接收 */UART_DMA_Idle_Init();while (1) {/* 处理接收到的数据帧 */UART_Process_Received_Frame();/* 其他任务... */}}五、关键API函数说明| 函数 | 功能 | 说明 ||------|------|------|| HAL_UART_Transmit_DMA() | DMA发送 | 启动DMA发送完成后触发TxCpltCallback || HAL_UART_Receive_DMA() | DMA接收 | 启动DMA接收完成后触发RxCpltCallback || HAL_UART_DMAStop() | 停止DMA传输 | 停止当前DMA传输用于空闲中断处理 || __HAL_DMA_GET_COUNTER() | 获取剩余字节数 | 获取DMA通道CNDTR寄存器值 || __HAL_UART_ENABLE_IT() | 使能中断 | 使能空闲中断等 || HAL_UARTEx_ReceiveToIdle_DMA() | 空闲中断接收 | HAL库扩展函数结合DMA和空闲中断 |六、注意事项1. **DMA通道映射是固定的**USART1_RX对应DMA1_Channel5USART1_TX对应DMA1_Channel4USART2_RX对应DMA1_Channel6。2. **初始化顺序**必须先初始化USART再初始化DMACubeMX生成的顺序通常是正确的。3. **空闲中断标志清除**必须通过 __HAL_UART_CLEAR_IDLEFLAG() 清除不能用 __HAL_UART_GET_FLAG() 后直接清零。4. **DMA缓冲区大小**应根据最大预期数据帧长度设置建议设为最大帧长的2倍以应对极端情况。5. **中断处理时效**空闲中断处理函数中不宜执行耗时操作应仅做数据复制和标志设置。6. **半满中断增强可靠性**对于极高数据率场景可结合DMA半满中断在数据接收过半时及时处理防止缓冲区溢出。七、三种模式对比总结对于大多数实际项目**DMA空闲中断**方式是最佳选择既保证了低CPU占用又能灵活处理不定长数据帧。有兴趣的友友可以入群一起学习

更多文章