STM32串口环形缓冲区设计全解析

目录

一、背景

二、程序示例

1、串口配置

核心问题分析

问题1:缓冲区满/空状态歧义

问题2:长度计算失效

问题3:边界条件处理

2、数据结构

3、初始化

4、串口中断

5、获取数据


在嵌入式系统中,高效处理串口数据是开发者面临的常见挑战,本博客为学习记录贴,记录stm32f1x芯片的串口环形缓冲区管理设计模式。

一、背景

串口通信面临两个常见问题:

  1. 数据不定长:接收的数据包长度未知

  2. 实时性要求:需要快速响应避免数据丢失

目前常用的设计通常结合DMA传输IDLE中断,配合缓冲区管理机制来解决这两个问题。

二、程序示例

1、串口配置

首先,先配置串口、DMA和空闲中断。

void Uart1_Config(uint32_t baudrate)		//先配置但不开启USART_Cmd
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = UART1_TX_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(UART1_TX_POART, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;	//有外部上拉就选浮空,没有就选上拉
	GPIO_InitStructure.GPIO_Pin = UART1_RX_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(UART1_RX_POART, &GPIO_InitStructure);
	
	USART_DeInit(USART1);
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = baudrate;
	USART_InitStructure.USART_HardwareFlowControl = DISABLE; 
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1, &USART_InitStructure);
	
	/*开启串口中断*/
	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
	
	//配置串口总中断
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
	
}

void Uart1_DMA_Config(void)
{
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//***********需要开启dma时钟
	
	DMA_DeInit(DMA1_Channel5);
	DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)Usart1_RxBuffer;
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;			//配合idle一般使用循环模式
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)(USART1->DR);        //数据寄存器
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;			
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh;	//设置为高优先级
	DMA_InitStruct.DMA_BufferSize = UARTx_RX_SIZE + 1;	  //*************注意此处只使用IDLE控制不使用DMA的TC控制,因此+1
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;			//外设到内存
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;				//选用硬件触发
	DMA_Init(DMA1_Channel5, &DMA_InitStruct);
	
#if defined(DMA_INTERRUPT)
	
	/*开启DMA中断*/
	DMA_ITConfig(DMA1_Channel5, DMA_IT_TC | DMA_IT_HT | DMA_IT_TE, ENABLE);
	
	//配置DMA总中断
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;	//需要去参考手册查
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_Init(&NVIC_InitStructure);
	
#endif

	DMA_Cmd(DMA1_Channel5, ENABLE);
}

void Uart1_start(void)
{
	USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
	USART_Cmd(USART1, ENABLE);
}

uint16_t get_datalen(void)
{
    return (UARTx_RX_SIZE + 1) - DMA_GetCurrDataCounter(DMA1_Channel5);    //注意为配合DMA的 counter 设置此处为 UARTx_RX_SIZE + 1
}

void dma_setcounter_setmxar(void)
{
	DMA_SetCurrDataCounter(DMA1_Channel5, UARTx_RX_SIZE + 1);				//***********重新设置dma转运大小
	DMA1_Channel5->CMAR = (uint32_t)(U1CB.pURxDataIn->pStart);			//***********重新设置起始地址
	DMA_Cmd(DMA1_Channel5, ENABLE);
}

关键设计:此处DMA设计为 DMA_InitStruct.DMA_BufferSize = UARTx_RX_SIZE + 1,也就是只用idle中断控制,不会使用dma_tc中断。

核心问题分析

问题1:缓冲区满/空状态歧义

场景
使用标准缓冲区大小2048:

  • 初始状态:计数器=2048

  • 接收2048字节后:计数器=0 → 自动重置为2048

歧义
计数器=2048可能表示:

  1. 初始状态(缓冲区空)

  2. 缓冲区满后重置的状态

无法区分这两种状态!

问题2:长度计算失效

当接收2048字节时:

received = initial_counter - current_counter;
// 2048 - 2048 = 0 ❌ (应为2048)

问题3:边界条件处理

传统方案在以下场景失效:

  • 连续接收多个满缓冲区

  • 缓冲区回绕处理

  • 错误恢复机制

2、数据结构

#define NUM             10    // 支持10个缓存块
#define UARTx_RX_SIZE   2048

uint8_t Usart1_RxBuffer[UARTx_RX_SIZE];    //物理缓冲区

typedef structure
{
    uint8_t *pStart;
    uint8_t *pEnd;
}UCB;

typedef structure
{
    uint16_t URxtotalReceived;    //累计接收数据量

    UCB URxDataPtr[NUM];    //se指针对 结构体数组
    UCB *pURxDataIn;        //IN指针用于标记接收数据
    UCB *pURxDataOut;       //OUT指针用于提取接收的数据
    UCB *pURxDataEnd;       //IN和OUT指针的结尾标志
    
}Usart_Rx_Control_Block;

Usart_Rx_Control_Block U1CB;

将物理缓冲区分为Usart1_RxBuffer[UARTx_RX_SIZE]分为NUM个逻辑分块。

  • 物理缓冲区:2048字节大缓冲区
  • 逻辑分块:10个数据块(NUM=10)

优势:允许应用程序在处理前一个数据块时,DMA继续接收新数据

3、初始化

//初始化U1CB
void U1Rx_PtrInit(void)
{
    U1CB.pURxDataIn = URxDataPtr[0];
    U1CB.pURxDataOut = URxDataPtr[0];
    U1CB.pURxDataEnd = URxDataPtr[NUM-1];
    U1CB.URxtotalReceived = 0;
    U1CB.URxDataPtr->pStart = Usart_RxBuffer;
}

4、串口中断

#define UARTx_RX_MAX    1029    //单次接收最大量

void USART1_IRQHandler(void)
{
	if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
	{
         //清除标志位
		USART1->SR;                       //清中断
		USART1->DR;                       //清中断

        U1CB.URxtotalReceived += get_datalen();     //获取本次接收数据长度
        U1CB.pURxDataIn->pEnd = &Usart_RxBuffer[U1CB.URxtotalReceived - 1];    //修改pEnd指针
        if(++ U1CB.pURxDataIn == U1CB.pURxDataEnd)
        {
            U1CB.pURxDataIn = URxDataPtr[0];
        }

        if(UARTx_RX_SIZE - U1CB.URxtotalReceived >=  UARTx_RX_MAX)
        {
            U1CB.pURxDataIn->pStart = &Usart_RxBuffer[U1CB.URxtotalReceived];
        }
        else
        {
            U1CB.pURxDataIn->pStart = Usart_RxBuffer;
            U1CB.URxtotalReceived = 0;
        }

        dma_setcounter_setmxar();    //设置dma counter 和 dma数组start地址
	}
}

5、获取数据

void process_data(void)
{
    if(U1CB.pURxDataIn != U1CB.pURxDataOut)    //表明有数据
    {
        uint16_t Lenth = U1CB.pURxDataOut->pEnd - U1CB.pURxDataOut->pStart;   //获取数据长度
        
        /*
            处理数据
            ...
        */
        
        U1CB.pURxDataOut ++;
        if(U1CB.pURxDataOut == U1CB.pURxDataEnd)
        {
            U1CB.pURxDataOut = &URxDataPtr[0];
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值