【STM32】DMA+串口空闲中断接收定长数据(解决接收错位问题)
阅读原文时间:2021年04月20日阅读:1

## 阅读须知

阅读本文需要有一定的STM32基础。并对STM32 DMA和串口有一定的了解。

串口空闲中断是什么?看官方的解释(大致的意思就是RX总线在一定时间内没有活干了,就认为空闲了):

## 问题分析

我们都知道,在用STM32串口的时候,使用DMA传输和串口空闲中断很香。

原理大家都懂,就是发生串口空闲中断的时候,就表示数据传输完成。然后就执行数据处理或者使用就好了。

这样的想象说理想也可以,但是没有考虑完数据发送的时间差问题,也就是当接收方初始化完DMA后,发送方发送数据了没有。做个假设:

 发送方一帧数据为6字节(0x01,0x02,0x03,0x04,0x05,0x06),分析情况

 

发送方此刻状态

接收方此刻状态

串口空闲中断发送,DMA接收到的数据

发送方先初始化完成

正在发送第2个数据

开始接收0x02

0x02,0x03,0x04,0x05,0x06

接收方先初始化完成

等待初始化完成

等待发送方发送数据

0x01,0x02,0x03,0x04,0x05,0x06

可以看出,当发送方已经发送了几个数据,接收方才开始接收数据,就会产生接收错位。这时候使用数据必定是错误的操作。

在做SBUS接收(25个8位数据)的时候,我参考别人的DMA+串口空闲中断的代码,都逃不掉数据错位的现象。偶尔会正确,但是还是有错位的现象,一旦错位,就会一直错位下去!!!!

本文将教你如何设置DMA和串口,并解决错位的现象。

## DMA简介

预留。

## 串口初始化代码

注:我使用UART2,使用UART1的朋友请修改UART的RCC初始化函数。

这里包括GPIO、串口初始化,注意,这里打开了串口空闲中断,打开了串口DMA接收。这里2步是很重要的。

USART_DMACmd(RC_RX_UART_Port,USART_DMAReq_Rx,ENABLE);//ENABLE DMA receive

USART_ITConfig(RC_RX_UART_Port, USART_IT_IDLE, ENABLE);//ENABLE IDLE interrupt

   //GPIO init config
    RCC_APB2PeriphClockCmd(RC_RXIO_RCC,ENABLE); // GPIO Clock
    RCC_APB1PeriphClockCmd(RC_RX_UART_RCC, ENABLE); // UART Clock
    GPIO_InitStructure.GPIO_Pin = RC_RX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(RC_RXIO_Port, &GPIO_InitStructure);

   //USART init config
    USART_InitStructure.USART_BaudRate = RC_BAUD;
    USART_InitStructure.USART_WordLength = USART_WordLength_9b;//8b Not identified by betaflight 
    USART_InitStructure.USART_StopBits = USART_StopBits_2;
    USART_InitStructure.USART_Parity = USART_Parity_Even;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx ;
    USART_ITConfig(RC_RX_UART_Port, USART_IT_IDLE, ENABLE);//ENABLE IDLE interrupt
    USART_Init(RC_RX_UART_Port, &USART_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = RC_RX_UART_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =2;        
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        
    NVIC_Init(&NVIC_InitStructure);    

    USART_DMACmd(RC_RX_UART_Port,USART_DMAReq_Rx,ENABLE);//ENABLE DMA receive
    USART_Cmd(RC_RX_UART_Port, ENABLE);

## DMA初始化代码

这里包括DMA时钟初始化,DMA通道配置,DMA通道传输开启。

DMA_DIR(DMA传输方向)配置为DMA_DIR_PeripheralSRC,串口----》-----内存。

DMA_Mode(DMA传输模式)配置为DMA_Mode_Circular,循环模式,必须是这个。

DMA_BufferSize(目标接收长度)配置为SbusLength,这里是25。

    RCC_AHBPeriphClockCmd(RC_DMA_Rx_RCC,ENABLE);                       

    DMA_InitStructure.DMA_PeripheralBaseAddr =  (u32)(&RC_RX_UART_Port->DR);         
    DMA_InitStructure.DMA_MemoryBaseAddr     =  (u32) SbusDataRx;            
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                     
    DMA_InitStructure.DMA_BufferSize = SbusLength;                                
    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_InitStructure.DMA_Mode = DMA_Mode_Circular;                       
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                    
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                           
    DMA_Init(RC_DMA_Rx_Ch,&DMA_InitStructure);   
    DMA_ClearFlag(RC_Dma_RxFlagTC);
    DMA_Cmd(RC_DMA_Rx_Ch,ENABLE);  

## 串口空闲中断处理代码

这部分就是处理数据,解决接收错位的代码。写得很简单,我重点分析一下。

1. 关闭DMA传输通道。

2. 传输发生串口空闲中断后,首先需要清除SR和DR寄存器,因为他们还有数据的话,下一次启动DMA传输,他们会被DMA直接搬走。所以在发生DMA空闲中断后,要清除这2个寄存器,直接读取就可以清除。参考本文首图。

3. 清除DMA传输完成标志位。

4.  获取DMA接收剩余长度。这个是关键的第一步,这个剩余长度是和我们设定的DMA接收长度对比的。也就是用DMA_BufferSize值做自减,每接收到一个数据,就减1,当减到0时,接收完成。会自动设置为DMA_BufferSize值。

相关公式是:已接收长度=目标接收长度(DMA_BufferSize)- 剩余接收长度

5. 有效帧判断。通过获取的剩余长度和数据帧的特点进行判断接收到的数据的有效性。这里说的数据帧特点,也就是看数据帧是否含了帧头和CRC,如果有帧头和CRC,可以用做有效帧判断。我这里用的是帧头和帧尾。

6. 在检测到无效帧的时候需要把  剩余接收长度   设置为目标长度。使用这个函数

DMA_SetCurrDataCounter(RC_DMA_Rx_Ch,SbusLength);

这样一来,发生错位的时候(无效帧),就可以告诉DMA:你接收错误了,重新开始接收。

7. 清除接收缓冲区。我这里只要清除帧头和帧尾,避免他们影响下一次的判断。也不必要将所有缓冲区都清除(省CPU时间)。

8. 如果接收到的是有效帧,那就执行你的数据处理就好了。处理完毕,记得做清除接收缓冲区。也可以像我一样单纯的清除数据帧特点位。

9. 打开DMA传输通道。

经过这几部的处理,当检测到错位后,我们重新设置  剩余接收长度。这一来,DMA会重新开始接收数据,在发送方重新发送数据的时候接收方的DMA会从第0位开始转存。然后就可以达到解决接收错位的问题。

错误示例:

ErrorRX:错误的帧数量
OkRX:正确的帧数量
IDLEIRQ:帧数量

仿真的时候就可以看到效果了,ErrorRX的值正常应该是0-1。

u16 DMA_Residual_length=0; 
uint32_t ErrorRX=0; 
uint32_t OkRX=0;
uint32_t IDLEIRQ=0;
void USART2_IRQHandler(void) 
{
    u8 RxTemp;
    if(USART_GetITStatus(RC_RX_UART_Port,USART_IT_IDLE)!=RESET)
    {
        IDLEIRQ++;
        DMA_Cmd(RC_DMA_Rx_Ch,DISABLE);      // off DMA1_Channel6
        RxTemp=RC_RX_UART_Port->SR;         // USART2->SR
        RxTemp=RC_RX_UART_Port->DR;         // Clear USART2->DR

        DMA_ClearFlag(RC_Dma_RxFlagTC);     // Clear DMA1_FLAG_TC6
        DMA_Residual_length=DMA_GetCurrDataCounter(RC_DMA_Rx_Ch);//Get the residual length
        if((DMA_Residual_length==SbusLength) && (SbusDataRx[0] == 0x0f && SbusDataRx[24]==0x00))
        {
            OkRX++;
            UnSbusPack(ChannelRx);//    Valid frame,The received sbus data is parsed and stored in ChannelRx
        }
        else
        {
            ErrorRX++;
            DMA_SetCurrDataCounter(RC_DMA_Rx_Ch,SbusLength);
            SbusDataRx[0]=0;
            SbusDataRx[24]=0;
        }
        DMA_Cmd(RC_DMA_Rx_Ch,ENABLE);
    }
}

## 涉及的宏定义

#define RC_RX_UART_Port         USART2
#define RC_RX_UART_IRQn     USART2_IRQn
#define RC_RX_UART_RCC          RCC_APB1Periph_USART2
#define RC_RXIO_Port            GPIOA
#define RC_RXIO_RCC             RCC_APB2Periph_GPIOA
#define RC_RX_PIN               GPIO_Pin_3

#define RC_DMA_Rx
#define RC_DMA_Rx_RCC           RCC_AHBPeriph_DMA1
#define RC_DMA_Rx_Ch         DMA1_Channel6
#define RC_Dma_RxFlagTC        DMA1_FLAG_TC6

#define RC_BAUD                 100000
#define SbusLength              25         // Sbus Length TX and RX

u8 SbusDataRx[SbusLength];
u16 ChannelRx[16];
void UnSbusPack(u16 *Array);

## 后记

如有错漏请通知修改和补充。上面的代码是可以实现的。如果需要代码工程,请看鄙人上传的资源。

也可以通过这个传送门:查看资源

今天(2020.7.25)查看资源,发现涨得比股票基金都要快(涨到42个,涨了740个点!!!,)!!!!现在重新设置了下载积分(5个)。如超过了,如发现过多,请私信。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器