一、功能概述
本教程基于STM32F103芯片,通过DMA(直接内存访问)实现高效串口通信,并结合**环形缓冲区(Circular Buffer)**解决数据接收时的溢出问题。代码核心功能如下:
USART1的DMA收发配置:自动传输数据,不占用CPU资源。
循环缓冲区管理:确保接收数据的连续性,避免丢失。
中断触发机制:通过空闲中断检测一帧数据结束,通过DMA中断管理缓冲区状态。
二、代码结构解析
1. 宏定义与全局变量
#define USART1_RX_DMA_CHANNEL DMA1_Channel5 // 接收DMA通道
#define USART1_TX_DMA_CHANNEL DMA1_Channel4 // 发送DMA通道
#define INTERCOM_RX_BUFF_SIZE 512 // 接收缓冲区大小
#define BUFFER_SIZE INTERCOM_RX_BUFF_SIZE // 环形缓冲区大小
// 环形缓冲区结构体
typedef struct {
volatile uint8_t buffer[BUFFER_SIZE];
volatile uint16_t head; // 数据起始位置
volatile uint16_t tail; // 数据结束位置
volatile uint16_t count; // 当前数据量
} CircularBuffer_T;
// 全局DMA通信管理结构体
TRANSFER_UART_DMA_T Transfer_UART_DMA;
关键点:
接收使用循环模式DMA,发送使用单次模式DMA。
环形缓冲区通过head和tail指针实现数据的循环覆盖,count记录有效数据量。
2. 环形缓冲区操作函数
初始化缓冲区
void CB_InitBuffer(CircularBuffer_T *cb) {
cb->head = 0;
cb->tail = 0;
cb->count = 0;
memset(cb->buffer, 0, BUFFER_SIZE);
}
数据出队(从缓冲区读取)
uint8_t CB_Dequeue(CircularBuffer_T *cb, uint8_t *data) {
if (cb->count == 0) return 1; // 缓冲区为空
*data = cb->buffer[cb->head];
cb->head = (cb->head + 1) % BUFFER_SIZE;
cb->count--;
return 0;
}
3. USART1与DMA初始化
USART1配置(Transfer_USART1_Init_Config)
GPIO配置:
PB6(TX):复用推挽输出。
PB7(RX):浮空输入。
波特率设置:根据SystemParam.Uart_Baud_Rate_Num选择(2400~115200)。
中断配置:使能空闲中断(USART_IT_IDLE),用于检测一帧数据接收完成。
关键代码:
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); // 空闲中断
USART_Cmd(USART1, ENABLE); // 启动USART1
4. 数据收发流程
发送数据(Transfer_USART1_DMA_Send)
检查发送缓冲区是否空闲。
将数据拷贝到发送缓冲区tx_buff。
设置DMA传输长度并启动发送。
代码逻辑:
memcpy(Transfer_UART_DMA.tx_buff, data_p, len); // 数据拷贝
DMA_SetCurrDataCounter(USART1_TX_DMA_CHANNEL, len); // 设置长度
DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE); // 启动发送
接收数据(中断处理)
空闲中断(USART1_IRQHandler):
触发条件:串口总线空闲(一帧数据接收完毕)。
计算接收到的数据长度,更新环形缓冲区指针。
DMA完成中断(DMA1_Channel5_IRQHandler):
标记缓冲区循环状态,处理数据覆盖逻辑。
// 空闲中断中更新缓冲区
temp = DMA_GetCurrDataCounter(USART1_RX_DMA_CHANNEL);
Transfer_UART_DMA.rx_buff.tail = BUFFER_SIZE - temp;
二、完整代码
#define USART1_RX_DMA_CHANNEL DMA1_Channel5
#define USART1_TX_DMA_CHANNEL DMA1_Channel4
#define INTERCOM_RX_BUFF_SIZE 512
#define INTERCOM_TX_BUFF_SIZE 512
// 定义环形缓冲区的大小
#define BUFFER_SIZE INTERCOM_RX_BUFF_SIZE
typedef struct
{
volatile uint8_t buffer[BUFFER_SIZE]; // 存储数据的数组
volatile uint16_t head; // 指向第一个有效数据的索引
volatile uint16_t tail; // 指向下一个空闲位置的索引
volatile uint16_t count; // 当前缓冲区中有效数据的数量
} CircularBuffer_T;
typedef struct
{
// volatile uint8_t rx_flag;
// uint8_t rx_buff[INTERCOM_RX_BUFF_SIZE];
// volatile uint16_t rx_len;
CircularBuffer_T rx_buff;
volatile uint8_t rx_buff_loopback;
uint8_t tx_buff[INTERCOM_TX_BUFF_SIZE];
} TRANSFER_UART_DMA_T;
TRANSFER_UART_DMA_T Transfer_UART_DMA;
CircularBuffer_T Rx_CircularBuffer;
// 初始化环形缓冲区
void CB_InitBuffer(CircularBuffer_T *cb)
{
cb->head = 0;
cb->tail = 0;
cb->count = 0;
// 初始化数组为0
for (int i = 0; i < BUFFER_SIZE; i++)
cb->buffer = 0;
}
// 检查环形缓冲区是否为空
uint8_t CB_IsEmpty(CircularBuffer_T *cb)
{
return cb->count == 0;
}
// 检查环形缓冲区是否已满
uint8_t CB_IsFull(CircularBuffer_T *cb)
{
return cb->count == BUFFER_SIZE;
}
/**
* @brief 从环形缓冲区取出数据
* @param 无
* @retval 0 有数据,1 无数据
*/
uint8_t CB_Dequeue(CircularBuffer_T *cb, uint8_t *data)
{
if (CB_IsEmpty(cb))
return 1; // 缓冲区为空
if (data == NULL)
return 1;
*data = cb->buffer[cb->head];
cb->head = (cb->head + 1) % BUFFER_SIZE;
cb->count--;
return 0;
}
/**
* @brief USART1配置
* @param 无
* @retval 无
*/
void Transfer_USART1_Init_Config(void)
{
uint8_t clear = clear;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
// 打开时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 将USART Tx的GPIO配置为推挽复用模式
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 将USART Rx的GPIO配置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置串口的工作参数
// 配置波特率
switch (SystemParam.Uart_Baud_Rate_Num)
{
case 0:
USART_InitStructure.USART_BaudRate = 2400;
break;
case 1:
USART_InitStructure.USART_BaudRate = 4800;
break;
case 2:
USART_InitStructure.USART_BaudRate = 9600;
break;
case 3:
USART_InitStructure.USART_BaudRate = 19200;
break;
case 4:
USART_InitStructure.USART_BaudRate = 38400;
break;
case 5:
USART_InitStructure.USART_BaudRate = 115200;
break;
default:
USART_InitStructure.USART_BaudRate = 115200;
break;
}
// USART_InitStructure.USART_BaudRate = UART_TRANSFER_BAUD_RATE;
// 配置帧数据字长
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校验位
USART_InitStructure.USART_Parity = USART_Parity_No;
// 配置硬件流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置工作模式,收发一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(USART1, &USART_InitStructure);
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = UART1_PRIORITY;
NVIC_Init(&NVIC_InitStruct);
// //使能发送完成中断
// USART_ITConfig(USART1,USART_IT_TC,ENABLE);
// 使能空闲中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
USART_ClearFlag(USART1, USART_IT_IDLE);
clear = USART1->SR;
clear = USART1->DR;
// 使能串口
USART_Cmd(USART1, ENABLE);
}
/**
* @brief USART1 DMA配置
* @param 无
* @retval 无
*/
void Transfer_USART1_DMA_Init_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStruct;
// 开启DMA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(USART1_TX_DMA_CHANNEL); // TX
DMA_DeInit(USART1_RX_DMA_CHANNEL); // RX
// Rx到接收缓冲区
DMA_StructInit(&DMA_InitStructure);
// 设置DMA源地址:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR));
// 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Transfer_UART_DMA.rx_buff.buffer;
// 方向:从外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
// 传输大小
DMA_InitStructure.DMA_BufferSize = INTERCOM_RX_BUFF_SIZE;
// 外设地址不增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 内存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// 内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA模式,循环
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // DMA_Mode_Circular DMA_Mode_Normal
// 优先级:中
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// 禁止内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 配置DMA通道
DMA_Init(USART1_RX_DMA_CHANNEL, &DMA_InitStructure);
// 开启传输完成中断
DMA_ITConfig(USART1_RX_DMA_CHANNEL, DMA_IT_TC, ENABLE);
DMA_ClearFlag(DMA1_FLAG_TC5);
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel5_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = DMA_PRIORITY;
NVIC_Init(&NVIC_InitStruct);
// 发送缓冲区到TX
DMA_StructInit(&DMA_InitStructure);
// 设置DMA源地址:串口数据寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART1->DR));
// 内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)Transfer_UART_DMA.tx_buff;
// 方向:从外设到内存
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
// 传输大小
DMA_InitStructure.DMA_BufferSize = 0;
// 外设地址不增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 内存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外设数据单位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// 内存数据单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA模式,循环
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_Mode_Circular DMA_Mode_Normal
// 优先级:中
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
// 禁止内存到内存的传输
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(USART1_TX_DMA_CHANNEL, &DMA_InitStructure);
// 使能DMA
DMA_Cmd(USART1_RX_DMA_CHANNEL, ENABLE);
DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // 使能DMA串口发送和接受请求
}
void Transfer_USART1_DMA_Start(void)
{
DMA_Cmd(USART1_RX_DMA_CHANNEL, DISABLE);
// 重新写入要传输的数据数量
DMA_SetCurrDataCounter(USART1_RX_DMA_CHANNEL, INTERCOM_RX_BUFF_SIZE);
// 启动DMA
DMA_Cmd(USART1_RX_DMA_CHANNEL, ENABLE);
}
/**
* @brief USART1 DMA 发送数据
* @param 无
* @retval 0 正常,1 异常
*/
uint8_t Transfer_USART1_DMA_Send(uint8_t *data_p, uint16_t len)
{
// /* 发送一个字节数据到USART */
// USART_SendData(USART1,*data_p);
// /* 等待发送数据寄存器为空 */
// while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
if (len == 0 || len > INTERCOM_TX_BUFF_SIZE)
return 1;
// if (DMA_GetCurrDataCounter(USART1_TX_DMA_CHANNEL))
// return 1;
while (DMA_GetCurrDataCounter(USART1_TX_DMA_CHANNEL))
;
if (data_p)
memcpy(Transfer_UART_DMA.tx_buff, data_p, len);
DMA_Cmd(USART1_TX_DMA_CHANNEL, DISABLE);
DMA_SetCurrDataCounter(USART1_TX_DMA_CHANNEL, len); // 重新写入要传输的数据数量
DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE); // 启动DMA发送
return 0;
}
void Transfer_USART1_DMA_Send_Test(void)
{
// if (Transfer_USART1_DMA_Send(Transfer_UART_DMA.rx_buff, Transfer_UART_DMA.rx_len) == 0)
// ;
// Transfer_UART_DMA.rx_len = 0;
uint8_t rx_data = 0;
if (CB_Dequeue(&(Transfer_UART_DMA.rx_buff), &rx_data) == 0)
Transfer_USART1_DMA_Send(&rx_data, 1);
}
/**
* @brief USART1 中断
* @param 无
* @retval 无
*/
void USART1_IRQHandler(void)
{
uint16_t temp;
// 空闲中断
if (USART_GetITStatus(USART1, USART_IT_IDLE))
{
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
temp = USART1->SR;
temp = USART1->DR;
// 接收完成
temp = DMA_GetCurrDataCounter(USART1_RX_DMA_CHANNEL);
// 计算新增数据
if (Transfer_UART_DMA.rx_buff_loopback == 0)
Transfer_UART_DMA.rx_buff.count += BUFFER_SIZE - temp - Transfer_UART_DMA.rx_buff.tail;
else
{
Transfer_UART_DMA.rx_buff.count += BUFFER_SIZE - Transfer_UART_DMA.rx_buff.tail + BUFFER_SIZE - temp;
Transfer_UART_DMA.rx_buff_loopback = 0;
}
Transfer_UART_DMA.rx_buff.tail = BUFFER_SIZE - temp;
// 数据溢出,覆盖
if (Transfer_UART_DMA.rx_buff.count > BUFFER_SIZE)
{
// 更新头部位置
Transfer_UART_DMA.rx_buff.count = BUFFER_SIZE;
Transfer_UART_DMA.rx_buff.head = Transfer_UART_DMA.rx_buff.tail + 1 % BUFFER_SIZE;
}
}
}
void DMA1_Channel5_IRQHandler(void)
{
if (DMA_GetITStatus(DMA1_IT_TC5))
{
DMA_ClearITPendingBit(DMA1_IT_TC5);
Transfer_UART_DMA.rx_buff_loopback = 1;
}
}
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/hallo8888/article/details/146337620
|
|