• STM32基础分析——USART的DMA模式


    有关USART的DMA传输模式,其基本的概念和配置,网上有很多博客和教程都有,这里不再赘述,只是记录一下比较容易忽视而造成调试不通的问题。

    1. 串口发送和接收分属两个DMA通道

    一般方式操作串口时,读写数据都是只操作DR(数据寄存器),虽然它是由两个寄存器组成的,一个给发送用(TDR),一个给接收用(RDR),但是用户只能操作DR寄存。而DMA模式下,串口发送和接收分属两个DMA通道,需要单独配置。

    分别配置的代码如下:

    static void USART1_Tx_DMA_Config(void)
    {
        DMA_InitTypeDef DMA_InitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
        
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;  			// 配置DMA1_Channel4中断
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
        NVIC_Init(&NVIC_InitStructure); 
        
        DMA_DeInit(USART_TX_DMA_CHANNEL);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                  // 开启DMA时钟
        DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;        // 设置DMA源地址:串口数据寄存器地址
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)sendbuff;          // 内存地址(要传输的变量的指针)
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;                  // 方向:从内存到外设
        DMA_InitStructure.DMA_BufferSize = CMD_NUM;                         // 传输大小	
        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_Normal;                       // DMA一次模式	
        DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;               // 优先级:中 
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                        // 禁止内存到内存的传输
        DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);		            // 配置DMA通道DMA1_Channel4
       
        DMA_ITConfig(USART_TX_DMA_CHANNEL,DMA_IT_TC,ENABLE); 
        DMA_Cmd (USART_TX_DMA_CHANNEL,DISABLE);                              // 关闭DMA
    }
    
    
    static void USART1_Rx_DMA_Config(void)
    {
        DMA_InitTypeDef DMA_InitStructure;
      //注意,接收没使用接收DMA中断
    //    NVIC_InitTypeDef NVIC_InitStructure;
    //    
    //    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn;  
    //    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
    //    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;  
    //    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    //    NVIC_Init(&NVIC_InitStructure); 
        
        DMA_DeInit(USART_RX_DMA_CHANNEL);
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);                  // 开启DMA时钟
        DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;        // 设置DMA源地址:串口数据寄存器地址*/
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_cmd;            // 内存地址(要传输的变量的指针)
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                  // 方向:外设到内存
        DMA_InitStructure.DMA_BufferSize = CMD_NUM;                         // 传输大小	
        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_Normal;                       // DMA一次模式	
        DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;               // 优先级:中 
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                        // 禁止内存到内存的传输
        DMA_Init(USART_RX_DMA_CHANNEL, &DMA_InitStructure);		            // 配置DMA通道DMA1_Channel5
       
    //    DMA_ITConfig(USART_RX_DMA_CHANNEL,DMA_IT_TC,ENABLE); 
        DMA_Cmd (USART_RX_DMA_CHANNEL,ENABLE);                              // 使能DMA
    }
    

    注意:在串口的基本配置当中要打开DMA传输模式,函数如下:

        USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);                    // 开启串口发送DMA
        USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);                    // 开启串口接收DMA  
    

    2. 间隔单次传输

    将DMA传输模式设置为Normal(一次传输),传输完成需要再次传输时,需要再次向DMA通道的传输数量寄存器(CNDTR)写入要传输的字节数。但是,在写入前,需要关闭DMA,写完CNDTR后再打开。

    2.1 串口DMA发送

    我的设计方法是在初始化的时候,默认先关闭发送DMA,在需要串口发送数据时,先配置CNDTR,再打开DMA,发送完成后进入中断函数,再关闭DMA。

    void DMA1_Channel4_IRQHandler(void)  
    {  
        DMA_ClearFlag(DMA1_FLAG_TC4);  
        DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);  
    } 
      
    *********
    //代码片段
        DMA_SetCurrDataCounter(DMA1_Channel4,(uint16_t)CMD_NUM);      // 关于DMA单次传输,这条非常重要
        DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
    

    2.2 串口DMA接收

    设计方法是:不启用DMA接收通道中断,而使用串口传输中断,在串口中断函数中对DMA处理。注意,一般串口中断我们采用的是接收中断USART_IT_RXNE,接收一次即中断一次。在DMA模式下要使用空闲中断USART_IT_IDLE,空闲中断是在检测到接收数据后,在数据总线上的一个字节时间内,如果没有接收到新的数据,则触发空闲中断,它是在串口的RXNE位被置位之后才开始检测。简单理解是,连续的一串数据发送完成之后,才触发空闲中断。

    串口的CR1寄存器的IDLE位被硬件置1,必须采用软件将IDLE位清零才能避免反复进入空闲中断。具体的做法是先读取状态寄存器USART_SR,再读取数据寄存器USART_DR,完成后自动清除。需要注意的是,不能采用库函数USART_ClearFlag()或者USART_ClearItPending()来清除IDEL标志,因为这两个函数接收的中断标志位仅包括:

    • USART_FLAG_CTS: CTS Change flag (not available for UART4 and UART5).

    • USART_FLAG_LBD: LIN Break detection flag.

    • USART_FLAG_TC: Transmission Complete flag.

    • USART_FLAG_RXNE: Receive data register not empty flag.

    同理,关闭DMA后,重置传输字节数,再开启DMA(因为串口一直要监测接收数据)。串口中断函数基础代码如下:

    void USART1_IRQHandler(void)                             
    {
        uint32_t temp = 0;
        
        if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET) 
        {
    //      temp = USART_GetITStatus(USART1,USART_IT_IDLE);       // 在判断时已经读取过一次
            temp = USART_ReceiveData(USART1);                     // 必须添加这条语句
            DMA_Cmd(USART_RX_DMA_CHANNEL,DISABLE);
            DMA_SetCurrDataCounter(DMA1_Channel5,(uint16_t)CMD_NUM);
            DMA_Cmd(USART_RX_DMA_CHANNEL,ENABLE);
        }
    }
    

    3. 疑问

    实际上这里面还有一些隐含方式方法,感兴趣的可以尝试一下,欢迎分享。

    • 现在采用的是串口中断来处理接收问题,是否可以采用DMA接收中断来处理数据接收?就如同DMA发送中断来处理发送数据一样。

    4. 参考文献

    1. 《STM32F10X参考手册》
    2. 《32位基于ARM微控制器STM32F101xx与STM32F103xx 固件函数库》
    3. STM32的串口空闲中断
    4. STM32的串口采用DMA方式接收数据测试
    5. STM32使用串口IDLE中断的两种接收不定长数据的方式
  • 相关阅读:
    Cf序列化器-Serializer解析
    yield和return
    pymongo的使用
    Homebrew介绍和使用
    TypeError: expected string or bytes-like object
    JavaScript读取文本,并渲染在html
    反序相等
    打印邮票的组合
    打印对称平方数
    字符串按照原意输出
  • 原文地址:https://www.cnblogs.com/rouwawa/p/7911172.html
Copyright © 2020-2023  润新知