打印
[STM32F4]

stm32f4 串口空闲中断+DMA遇到的奇怪错位问题

[复制链接]
654|23
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
利用DMA接受串口任意长数据
1、简介
有时候,我们**利用串口在设备间进行高速而稳定的数据通信,于是定义了一些通讯协议,包括帧头、功能码、数据码、校验码等等,DMA非常适合此类需求。利用串口空闲中断+DMA的方法,我们可以快速地读取整帧数据进行分析。
在使用串口空闲中断+DMA的方法接受数据时,其流程如下
初始化DMA & USART
主机开始发送,在从机USART->DR寄存器收到数据后,DMA立刻将数据移至指定的存储buf中(此过程不需要cpu)
主机一帧数据发送完成后,串口暂时空闲,触发串口空闲中断。在这里可以计算收到数据的字节数,也可以对数据帧进行解码等操作。
清除标志位,开始下一帧接收

使用特权

评论回复
沙发
等你下课|  楼主 | 2023-10-29 02:02 | 只看该作者
循环模式和普通模式
在上面的文章中有一点没有讲清楚,就是DMA的循环模式(DMA_Mode_Circular)和普通模式(DMA_Mode_Normal)

使用特权

评论回复
板凳
等你下课|  楼主 | 2023-10-29 02:02 | 只看该作者
DMA_Mode_Normal:在普通模式下,传输结束后(即传输计数DMA1_Streamx->NDTR变为0)将不再产生DMA操作。要开始新的DMA传输,需要3个步骤:①关闭DMA通道,②在DMA_CNDTRx寄存器中重新写入传输数目,③然后重新开启DMA

                        //开启一次DMA传输
                        //DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
                        //ndtr:数据传输量  
                        void DMA_Transfer_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
                        {
                                DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输
                                while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}        //确保DMA可以被设置  
                                DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //数据传输量  
                                DMA_Cmd(DMA_Streamx, ENABLE);                      //开启DMA传输
                        }          

使用特权

评论回复
地板
等你下课|  楼主 | 2023-10-29 02:02 | 只看该作者
DMA_Mode_Circular:在循环模式下,最后一次传输结束时, DMA_SxNDTR寄存器的内容会自动地被重新加载为其初始数值,内部的当前外设/存储器地址寄存器也被重新加载为初始基地址。

使用特权

评论回复
5
等你下课|  楼主 | 2023-10-29 02:02 | 只看该作者
DMA接受数据错位问题
1、程序设置
帧长8字节
DMA普通模式
不使用FIFO进行节拍发送
从USART3->DR寄存器向一个8字节的buf转移数据

使用特权

评论回复
6
等你下课|  楼主 | 2023-10-29 02:02 | 只看该作者
//全局变量
#define USART3_RX_BUFFER_SIZE 8
#define USART3_TX_BUFFER_SIZE 5
uint8_t                USART3_Rx_Buffer[USART3_RX_BUFFER_SIZE] = {0};
uint8_t                USART3_Tx_Buffer[USART3_TX_BUFFER_SIZE] = {0};
uint8_t                USART3_Rx_DMA_Buffer[USART3_RX_BUFFER_SIZE] = {0};
uint8_t                USART3_Tx_DMA_Buffer[USART3_TX_BUFFER_SIZE] = {'1','2','3','4','\n'};


//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
//par:外设地址
//mar:存储器地址
//ndtr:数据传输量  
void DMA_Config(DMA_Stream_TypeDef *DMA_Streamx,uint32_t chx,uint32_t par,uint32_t mar,uint32_t dir,u16 ndtr)
{
        DMA_InitTypeDef  DMA_InitStructure;
        if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
        else
          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能
          DMA_DeInit(DMA_Streamx);
          while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置
       
          /* 配置 DMA Stream */
        DMA_InitStructure.DMA_Channel                                         = chx;                                                          //通道选择
          DMA_InitStructure.DMA_PeripheralBaseAddr                 = par;                                                                //DMA外设地址
          DMA_InitStructure.DMA_Memory0BaseAddr                     = mar;                                                                //DMA 存储器0地址
          DMA_InitStructure.DMA_DIR                                                 = dir;                                                                //direction of transmit.
          DMA_InitStructure.DMA_BufferSize                                 = ndtr;                                                                //数据传输量
          DMA_InitStructure.DMA_PeripheralInc                                = DMA_PeripheralInc_Disable;                //外设非增量模式
          DMA_InitStructure.DMA_MemoryInc                                 = DMA_MemoryInc_Enable;                                //存储器增量模式
          DMA_InitStructure.DMA_PeripheralDataSize                 = DMA_PeripheralDataSize_Byte;                //外设数据长度:8位
          DMA_InitStructure.DMA_MemoryDataSize                         = DMA_MemoryDataSize_Byte;                        //存储器数据长度:8位
          DMA_InitStructure.DMA_Mode                                                 = DMA_Mode_Normal;                                        //使用普通模式
          DMA_InitStructure.DMA_Priority                                         = DMA_Priority_High;                                //中等优先级
          DMA_InitStructure.DMA_FIFOMode                                         = DMA_FIFOMode_Disable;                 //不用FIFO
          DMA_InitStructure.DMA_FIFOThreshold                         = DMA_FIFOThreshold_Full;                       
          DMA_InitStructure.DMA_MemoryBurst                                 = DMA_MemoryBurst_Single;                        //存储器突发单次传输
          DMA_InitStructure.DMA_PeripheralBurst                     = DMA_PeripheralBurst_Single;                //外设突发单次传输
          DMA_Init(DMA_Streamx, &DMA_InitStructure);
          DMA_Cmd(DMA_Streamx,ENABLE);
}

//开启一次DMA传输
//DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
//ndtr:数据传输量  
void DMA_Transfer_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
        DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输
        while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}        //确保DMA可以被设置  
        DMA_SetCurrDataCounter(DMA_Streamx,ndtr);           //数据传输量  
        DMA_Cmd(DMA_Streamx, ENABLE);                       //开启DMA传输
}          

//配置usart3
void USART3_Init(uint32_t bound)
{
        //GPIO端口设置
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
       
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);

        GPIO_PinAFConfig(GPIOD,GPIO_PinSource8,GPIO_AF_USART3); //GPIOD8复用为USART3_TX
        GPIO_PinAFConfig(GPIOD,GPIO_PinSource9,GPIO_AF_USART3); //GPIOD9复用为USART3_RX
       
          GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8| GPIO_Pin_9;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;       
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        GPIO_Init(GPIOD,&GPIO_InitStructure);
       
        USART_InitStructure.USART_BaudRate = bound;
        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(USART3, &USART_InitStructure);
       
        NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;               
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                       
        NVIC_Init(&NVIC_InitStructure);       
       
        USART_ClearFlag(USART3, USART_FLAG_TC);
//        USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
        USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);        //串口空闲中断       
        USART_ITConfig(USART3, USART_IT_TC, ENABLE);        //发送完成中断
        USART_DMACmd(USART3,USART_DMAReq_Rx | USART_DMAReq_Tx,ENABLE);               
        DMA_Cmd(DMA1_Stream1,ENABLE);
        USART_Cmd(USART3, ENABLE);
       
        //DMA for rx
        DMA_Config(DMA1_Stream1,DMA_Channel_4,
                                                 (uint32_t)&(USART3->DR),
                                                 (uint32_t)USART3_Rx_DMA_Buffer,
                                                 DMA_DIR_PeripheralToMemory,  
                                                 USART3_RX_BUFFER_SIZE);

        //DMA for tx                                         
        DMA_Config(DMA1_Stream3,DMA_Channel_4,
                                                 (uint32_t)&(USART3->DR),
                                                 (uint32_t)USART3_Tx_DMA_Buffer,
                                                 DMA_DIR_MemoryToPeripheral,
                                                 USART3_TX_BUFFER_SIZE);
        usart3.update=0;
        usart3.locked=0;
        usart3.tx_length=0;
        usart3.rx_length=0;
        usart3.rx_size=USART3_RX_BUFFER_SIZE;
        usart3.tx_size=USART3_TX_BUFFER_SIZE;
        usart3.tx_buf=USART3_Tx_DMA_Buffer;
        usart3.rx_buf=USART3_Rx_DMA_Buffer;

        DMA_Transfer_Enable(DMA1_Stream1,USART3_RX_BUFFER_SIZE);        //开启一次DMA接收
//        DMA_Transfer_Enable(DMA1_Stream3,USART3_TX_BUFFER_SIZE);        //开启一次DMA发送
}

//中断服务函数
void USART3_IRQHandler(void)
{
        uint8_t  rc_tmp;
        uint16_t rc_len;
        //空闲中断(接收)---------------------------------------------------------------------------------------
    if(USART_GetITStatus(USART3,USART_IT_IDLE)!=RESET)
    {
      //清除IDLE标志
      rc_tmp=USART3->SR;
      rc_tmp=USART3->DR;
      DMA_Cmd(DMA1_Stream1, DISABLE);
      DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1);        //清除发送完成标志
      DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TEIF1);        //清除发送错误标志
      rc_len = USART3_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Stream1);        //计算本次收到的数据帧长度
               
          if(!usart3.locked)
          {
                usart3.rx_length=rc_len;
                Data_Decode(USART3_Rx_DMA_Buffer);
                DMA_Transfer_Enable(DMA1_Stream1,USART3_RX_BUFFER_SIZE);
          }
        }  
       
        //发送完成中断(发送)---------------------------------------------------------------------------------------
        if(USART_GetITStatus(USART3,USART_IT_TC)!=RESET)
        {
                   DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3);          //清除DMA发送完成标志
                USART_ClearITPendingBit(USART3, USART_IT_TC);        //清除发送完成标志
                DMA_Cmd(DMA1_Stream3, DISABLE);
        }
}

使用特权

评论回复
7
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
数据缓冲错位问题
(1)问题描述:
发送数据部分没什么问题,之前我设置的接收缓冲buf比传输数据长度多一点,接收也没问题,但今天调DMA的时候我把二者长度设成一样了(都是8字节),于是遇到以下问题

发送数据“12345678”,第一次发送后,只在buf[0]位置受到一个 ‘1’
再次发送数据“12345678”,buf[0]~buf[7]收到 “81234567”
再发送其他8字节数据,如“87654321”,buf[0]~buf[7]收到 “18765432”
基本就是第一次只收到第一个数据,以后都是数据错位,最后的数据跑到第一位了

使用特权

评论回复
8
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
调试过程
注意,DMA是不受cpu控制的,一旦设置好后就会自动搬运数据,在debug过程中它导致的赋值操作不会在断点停下,所以对DMA进行debug要特别注意

使用特权

评论回复
9
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
打开debug查看,第一次发送数据“12345678”后,DMA1_Stream1的EN位为1,表明数据流处于使能状态;NDTR值为7,表明本帧数据还有7个字节待接收,看起来没有问题。

使用特权

评论回复
10
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
进一步查看USART3->DR寄存器,其值为‘1’,说明第一个数据正常DMA正常传送,奇怪的是为何只发了一个数就进入中断了,这说明串口在发了一个数后就处于空闲状态。

使用特权

评论回复
11
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
debug单步调试,发现进刚进空闲中断的时候缓冲buf其实没收到值,此时USART3->DR中已经是‘1’了,buf[0]处的‘1’是在重设接收数据长度,重新使能DMA后才收到的,这就说明第一次DMA实质没有启动

使用特权

评论回复
12
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
在USART3_Init()函数最后一句DMA_Transfer_Enable(DMA1_Stream1,USART3_RX_BUFFER_SIZE);处设置断点,发现进入DMA_Transfer_Enable函数前数据长度实质已经设好了,而且EN位也是1(流已使能);而出此函数后,EN位变成0了(流处于禁止状态)

使用特权

评论回复
13
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
进入DMA_Transfer_Enable函数单步调试,发现失能、重设两步都正常,但是最后使能流失败,看了看使能函数,里面就一个位操作,是在不知道为啥不能使能。于是查看数据手册,发现如下内容:

使用特权

评论回复
14
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者

使用特权

评论回复
15
等你下课|  楼主 | 2023-10-29 02:03 | 只看该作者
按手册查看DMA1->LISR和DMA->HISR,发现在DMA_Transfer_Enable函数中DMA_Cmd(DMA_Streamx, DISABLE)一句执行的瞬间,DMA1->LISR立刻变为0x800,这标志 数据流1传输完成,正因为这个标志没有清除导致使能流失败

使用特权

评论回复
16
等你下课|  楼主 | 2023-10-29 02:04 | 只看该作者

使用特权

评论回复
17
等你下课|  楼主 | 2023-10-29 02:04 | 只看该作者

使用特权

评论回复
18
等你下课|  楼主 | 2023-10-29 02:04 | 只看该作者
为什么会出现这个置位呢,进一步查看数据手册,有如下内容:

使用特权

评论回复
19
等你下课|  楼主 | 2023-10-29 02:04 | 只看该作者
这样一来就真相大白了!就是因为串口初始化后进行了多余的重新设置数据传输量长度的操作,导致数据流失能,这样一来串口DR寄存器的数据就不能转移,因此串口被阻塞,触发空闲中断。接下来在中断服务函数USART3_IRQHandler中清除了所有传输完成和传输错误标志,使得中断函数最后重新设置数据传输量长度时数据流可以使能成功(EN成功置位)。DMA使能后立即将DR寄存器中的 '1’转移至buf[0],同时NDTR值由8减一变为7,这就出现了第一次发送“12345678”后的情形

使用特权

评论回复
20
等你下课|  楼主 | 2023-10-29 02:04 | 只看该作者
第二次点击发送时,由于NDTR值为7,还可以发送“1234567”,它们被DMA转移到buf[1]~buf[7],然后DMA传输完成。此时发送数据的最后一个字节‘8’实际上已经处于USART3->DR中了,但是由于由一轮传输已经结束,故暂时无法转移到buf中。重新设置数据传输量NDTR=8后,DMA再次打开,立即把DR中的剩余’8‘搬运到buf[0],NDTR-1=7。这就产生了第二次发送“1234567”后的情况

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

319

帖子

0

粉丝