zoukankan      html  css  js  c++  java
  • USART DMA双缓冲给PC发送数据和接收PC数据

    用DMA双缓冲给PC发送串口数据 和 接收PC串口数据。

    理解双缓冲概念:就是利用两个数组轮流导出或导进数据。

    比如定义两个缓冲区数组usart_buffer0[USART_NUM] 和 usart_buffer1[USART_NUM],数组大小USART_NUM要设置一样。

    给PC发送数据时:

    DMA先从usart_buffer0(先假设从usart_buffer0)缓冲区拿数据发给PC,usart_buffer0发完后,DMA再从usart_buffer1中拿数据发给PC,usart_buffer1发完后,DMA再次回到usart_buffer0拿数据发给PC,循环操作。

    注意:DMA在对usart_buffer1拿数据的同时,CPU可以更新usart_buffer0中的数据,同理,DMA在对usart_buffer0拿数据的同时,CPU也可以更新usart_buffer1中的数据

     接收数据也是同样的道理。

    先看看几个问题:

    (1)、DMA先从哪个地址拿数据发给PC?

        答:按照正常思路,觉得是先打印Memory0中的数据,而按照下面程序的配置,实测,PC先打印的数据是Memory1,这地方让我有点迷糊,有点像“后进先出”。所以后面传数组地址时,稍微注意一下。

                                 

    (2)、怎么知道DMA当前在对哪个缓冲区拿数据?

        答:可通过DMA_GetCurrentMemoryTarget(DMA_Stream_TypeDef* DMAy_Streamx);函数获取信息,

        这里使用寄存器if (DMA1_Stream5->CR&(1<<19) ) 来判断,也就是检测CR寄存器中的第bit19位。

    (3)、当DMA发送完usart_buffer0后,会不会进入中断,还是等usart_buffer1发送完后进中断?

        答:每当发送完一个缓冲就会进入一次中断。

    (4)、配置DMA缓冲区大小时,是配置USART_NUM还是 2*USART_NUM?  

         答: 配置大小是 USART_NUM

    (5)、DMA切换缓冲区需要每次配置吗?

        答:只需配置一次,软件会自动切换。

    先是头文件

    #ifndef __DEBUG_USART_H
    #define    __DEBUG_USART_H
    
    #include "stm32f4xx.h"
    #include <stdio.h>
    
    #define NEW_BOARD 1
    #define OLD_BOARD 0
    
    
    #if NEW_BOARD  //USART2 DMA1 µÚ6¸öÊý¾ÝÁ÷ µÚ4¸öͨµÀ
    #define USART_ADDRESS 0x40004404
    #define DEBUG_USART                             USART2
    #define DEBUG_USART_CLK                         RCC_APB1Periph_USART2
    #define DEBUG_USART_BAUDRATE                    115200
    
    #define DEBUG_USART_RX_GPIO_PORT                GPIOD
    #define DEBUG_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOD
    #define DEBUG_USART_RX_PIN                      GPIO_Pin_6
    #define DEBUG_USART_RX_AF                       GPIO_AF_USART2
    #define DEBUG_USART_RX_SOURCE                   GPIO_PinSource6
    
    #define DEBUG_USART_TX_GPIO_PORT                GPIOD
    #define DEBUG_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOD
    #define DEBUG_USART_TX_PIN                      GPIO_Pin_5
    #define DEBUG_USART_TX_AF                       GPIO_AF_USART2
    #define DEBUG_USART_TX_SOURCE                   GPIO_PinSource5
    
    #define RCC_AHB1Periph_DMAx                                          RCC_AHB1Periph_DMA1
    #define DMAx_Streamx                                                          DMA1_Stream6
    #define Rx_DMAx_Streamx                                                  DMA1_Stream5
    #define DMA_ALL_IT_FLAG                                                     DMA_IT_FEIF6|DMA_IT_DMEIF6|DMA_IT_TEIF6|DMA_IT_HTIF6|DMA_IT_TCIF6
    #define DMA_Channel_x                                                         DMA_Channel_4
    #define DMAx_Streamx_IRQn                                                 DMA1_Stream6_IRQn
    #define DMAx_Streamx_IRQHandler                                    DMA1_Stream6_IRQHandler
    #define DMA_IT_TCIFx                                                        DMA_IT_TCIF6
    #endif
    
    #if OLD_BOARD //USART1 DMA2  µÚ7¸öÊý¾ÝÁ÷ µÚ4¸öͨµÀ
    #define USART_ADDRESS 0x40011004
    #define DEBUG_USART                             USART1
    #define DEBUG_USART_CLK                         RCC_APB2Periph_USART1
    #define DEBUG_USART_BAUDRATE                    115200
    
    #define DEBUG_USART_RX_GPIO_PORT                GPIOA
    #define DEBUG_USART_RX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
    #define DEBUG_USART_RX_PIN                      GPIO_Pin_10
    #define DEBUG_USART_RX_AF                       GPIO_AF_USART1
    #define DEBUG_USART_RX_SOURCE                   GPIO_PinSource10
    
    #define DEBUG_USART_TX_GPIO_PORT                GPIOA
    #define DEBUG_USART_TX_GPIO_CLK                 RCC_AHB1Periph_GPIOA
    #define DEBUG_USART_TX_PIN                      GPIO_Pin_9
    #define DEBUG_USART_TX_AF                       GPIO_AF_USART1
    #define DEBUG_USART_TX_SOURCE                   GPIO_PinSource9
    
    
    #define RCC_AHB1Periph_DMAx                                            RCC_AHB1Periph_DMA2
    #define DMAx_Streamx                                                        DMA2_Stream7
    #define DMA_ALL_IT_FLAG                                                    DMA_IT_FEIF7|DMA_IT_DMEIF7|DMA_IT_TEIF7|DMA_IT_HTIF7|DMA_IT_TCIF7
    #define DMA_Channel_x                                                        DMA_Channel_4
    #define DMAx_Streamx_IRQn                                                DMA2_Stream7_IRQn
    #define DMAx_Streamx_IRQHandler                                    DMA2_Stream7_IRQHandler
    #define DMA_IT_TCIFx                                                        DMA_IT_TCIF7
    
    #endif
    
    
    #define __DEBUG     //¿ªÆô´®¿Úµ÷ÊÔ
    
    #ifdef  __DEBUG
    #define DEBUG(format,...)    printf("File:"__FILE__",Line:%03d:"format"
    ",__LINE__,##__VA_ARGS__) 
    #else
    
    #define DEBUG(format,...)
    
    #endif
    
    
    void Debug_USART_Config(void);
    void USART_Tx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num);
    void USART_Rx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num);
    
    
    
    #endif /* __USART1_H */
    View Code

    串口IO配置:

    void Debug_USART_Config(void)
    {
      GPIO_InitTypeDef GPIO_InitStructure;
      USART_InitTypeDef USART_InitStructure;
            
      RCC_AHB1PeriphClockCmd( DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK, ENABLE);
    
    #if NEW_BOARD
      /* 使能 UART 时钟 */
      RCC_APB1PeriphClockCmd(DEBUG_USART_CLK, ENABLE);  //使用的是USART2
    #else
        RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);//使用的USART1
    #endif
      
      /* 连接 PXx 到 USARTx_Tx*/
      GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE, DEBUG_USART_RX_AF);
    
      /*  连接 PXx 到 USARTx__Rx*/
      GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);
    
      /* 配置Tx引脚为复用功能  */
      GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    
      GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN  ;
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
      GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
    
      /* 配置Rx引脚为复用功能 */
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
      GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
      GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
                
      /* 配置串DEBUG_USART 模式 */
      USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
      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(DEBUG_USART, &USART_InitStructure); 
      USART_Cmd(DEBUG_USART, ENABLE);
    }
    View Code

     串口printf函数重定义

    ///重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
    int fputc(int ch, FILE *f)
    {
            /* 发送一个字节数据到串口DEBUG_USART */
            USART_SendData(DEBUG_USART, (uint8_t) ch);
            
            /* 等待发送完毕 */
            while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);        
        
            return (ch);
    }
    
    ///重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
    int fgetc(FILE *f)
    {
            /* 等待串口输入数据 */
            while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);
    
            return (int)USART_ReceiveData(DEBUG_USART);
    }
    View Code
    存储器到外设的DMA 发送配置:
    u8 tx_flag = 0;
    u8 rx_flag = 0;
    u8 usart_DMA_complete_tx = 0;
    u8 usart_DMA_complete_rx = 0;
    
    void USART_Tx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num)
    {
        NVIC_InitTypeDef   NVIC_InitStructure;
        DMA_InitTypeDef  DMA_InitStructure;
        
       RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMAx,ENABLE);//DMA时钟使能 
        
        DMA_DeInit(DMAx_Streamx);
        while (DMA_GetCmdStatus(DMAx_Streamx) != DISABLE){}//等待DMAx_Streamx可配置 
            
        DMA_ClearITPendingBit(DMAx_Streamx,DMA_ALL_IT_FLAG);//清空DMAx_Streamx上所有中断标志
    
      /* 配置 DMA Stream */
      DMA_InitStructure.DMA_Channel = DMA_Channel_x;  //通道设置
      DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_ADDRESS;//外设地址为
      DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存储器memory0地址
      DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式
      DMA_InitStructure.DMA_BufferSize = num;//数据传输量 ,注意这个大小
      DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式
      DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
      DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位 注意数据宽度是8位
      DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;//存储器数据长度:8位 注意数据宽度是8位
      DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 ,双缓冲区只能是循环模式
      DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
      DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
      DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
      DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
      DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
      DMA_Init(DMAx_Streamx, &DMA_InitStructure);//初始化DMA Stream
    
      DMA_DoubleBufferModeConfig(DMAx_Streamx,(uint32_t)buffer1,DMA_Memory_1);//配置Memory1地址,指向buffer1
      DMA_DoubleBufferModeCmd(DMAx_Streamx,ENABLE);//双缓冲模式开启
        
      DMA_ITConfig(DMAx_Streamx,DMA_IT_TC,ENABLE);//开启传输完成中断
        
      USART_DMACmd(DEBUG_USART,USART_DMAReq_Tx,ENABLE);//开启串口对DMA发送请求
        
      DMA_Cmd(DMAx_Streamx,DISABLE); //为了验证,DMA到底是先从哪个缓冲区拿数据,先不着急开启
        
      NVIC_InitStructure.NVIC_IRQChannel = DMAx_Streamx_IRQn; //对应的中断号
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级0
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//子优先级0
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
      NVIC_Init(&NVIC_InitStructure);//配置
    }
    
    void DMAx_Streamx_IRQHandler(void)
    {
        if(DMA_GetITStatus(DMAx_Streamx,DMA_IT_TCIFx)==SET)//DMA传输完成标志
        { 
            DMA_ClearITPendingBit(DMAx_Streamx,DMA_IT_TCIFx);//清DMA传输完成标准
    //        DMA_Cmd(DMAx_Streamx,DISABLE);
            if(DMAx_Streamx->CR&(1<<19)) 
            {
                tx_flag=2;     //说明DMA当前在Memory1中,通过判断该标志,CPU可以更新Memory0中的数据
            }
            else                            
            {
                tx_flag=1;    //说明DMA当前在Memory0中,通过判断该标志,CPU可以更新Memory1中的数据
            }
            usart_DMA_complete_tx++;
        }
    }

     外设到存储器DMA接收配置:

    //USART2_RX 使用的是DMA1 ,第5个数据流,第四个通道
    void USART_Rx_DMA_Init(uint8_t *buffer0, uint8_t*buffer1, uint32_t num)
    {
        NVIC_InitTypeDef   NVIC_InitStructure;
        DMA_InitTypeDef  DMA_InitStructure;
      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMAx,ENABLE);//DMA时钟使能 
        
        DMA_DeInit(DMA1_Stream5);
        while (DMA_GetCmdStatus(DMA1_Stream5) != DISABLE){}//等待DMA1_Stream5可配置 
            
        DMA_ClearITPendingBit(DMA1_Stream5,DMA_IT_FEIF5|DMA_IT_DMEIF5|DMA_IT_TEIF5|DMA_IT_HTIF5|DMA_IT_TCIF5);//清空DMA1_Stream5上所有中断标志
    
      /* 配置 DMA Stream */
      DMA_InitStructure.DMA_Channel = DMA_Channel_x;  //通道
      DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_ADDRESS;//外设地址为
      DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer0;//DMA 存储器0地址
      DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
      DMA_InitStructure.DMA_BufferSize = num;//数据传输量 
      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_PeripheralDataSize_Byte;//存储器数据长度:8位 
      DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式 
      DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级
      DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式        
      DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
      DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//外设突发单次传输
      DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//存储器突发单次传输
      DMA_Init(DMA1_Stream5, &DMA_InitStructure);//初始化DMA Stream
            
        DMA_DoubleBufferModeConfig(DMA1_Stream5,(uint32_t)buffer1,DMA_Memory_1);//配置DMA_Memory_1地址 
      DMA_DoubleBufferModeCmd(DMA1_Stream5,ENABLE);//双缓冲模式开启
        
      DMA_ITConfig(DMA1_Stream5,DMA_IT_TC,ENABLE);//开启传输完成中断
        
        USART_DMACmd(DEBUG_USART,USART_DMAReq_Rx,ENABLE);//开启DMA接收请求
        
        DMA_Cmd(DMA1_Stream5,DISABLE);
        
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream5_IRQn; 
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//子优先级
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
      NVIC_Init(&NVIC_InitStructure);//配置
        
    }
    
    void DMA1_Stream5_IRQHandler(void)
    {
        if(DMA_GetITStatus(DMA1_Stream5,DMA_IT_TCIF5)==SET)//DMA传输完成标志
        { 
            DMA_ClearITPendingBit(DMA1_Stream5,DMA_IT_TCIF5);//清DMA传输完成标准
    //        DMA_Cmd(DMAx_Streamx,DISABLE);
            if(DMA1_Stream5->CR&(1<<19))
            {
                rx_flag=2;   //说明DMA当前在Memory1中,主函数中通过判断该标志,将Memory0中的数据拷贝到temp数组中
            }
            else                           
            {
                rx_flag=1;    //说明DMA当前在Memory0中,主函数通过判断该标志,将Memory1中的数据拷贝到temp数组中
            }
            
            if(usart_DMA_complete_rx ==0)
            {
                printf("Please input 5 datas again>>
    ");//当前面第一次输入的5个数据收到之后,再次提示输入5个数据,这样就给两个缓冲buffer都填冲了数据
            }
            usart_DMA_complete_rx++;
        }
    }

    主函数:

    #define USART_NUM 5
    u8 usartx_buffer0[USART_NUM] = {0x00,0x11,0x22,0x33,0x44};
    u8 usartx_buffer1[USART_NUM] = {0x55,0x66,0x77,0x88,0x99};
    extern u8 tx_flag;
    extern u8 rx_flag;
    extern u8 usart_DMA_complete_tx;
    extern u8 usart_DMA_complete_rx;

    int
    main(void) {   Debug_USART_Config(); //串口IO配置 KEY_LMR_Init();//按键配置 u8 temp[10] = {0}; u8 i = 0; printf("USART DMA DoubleBuffer Tx/Rx Test "); printf("Press the Left key,STM send data to PC! "); printf("Press the Right key,STM receive data from PC ! "); while(1) { if(KEYL==0) { Delay_ms(1000); //消抖,保证按下一次按键只进入一次 USART_Tx_DMA_Init(usartx_buffer1,usartx_buffer0,USART_NUM);//注意这里先usartx_buffer1->Memory0,后是usartx_buffer0->Memory1,实测是先打印出来的是Memory1中的数据, DMA_Cmd(DMAx_Streamx,ENABLE); //这样打印顺序00 ,11,22,33,44,55,66,77,88,99。 设置缓冲大小是USART_NUM,而不是2*USART_NUM } if(usart_DMA_complete_tx ==2)//当发完一轮数据,即10个数据后,关掉DMA中断,要不然串口会一直不停的循环打印 { DMA_Cmd(DMAx_Streamx,DISABLE); usart_DMA_complete_tx = 0; printf("Tx Complete!!! "); } if(KEYR==0) //右键有按下时,PC可以给ST发送数据,数据量为USART_NUM { Delay_ms(1000); printf("Please input 5 datas>> ");//提示输入5个数据 USART_Rx_DMA_Init(usartx_buffer1,usartx_buffer0,USART_NUM);//注意这里先usartx_buffer1->Memory0,后是usartx_buffer0->Memory1,这样PC先发一组5个数据,是先保存在usartx_buffer0中 DMA_Cmd(DMA1_Stream5,ENABLE); } if(rx_flag==1) { memcpy(temp,usartx_buffer0,sizeof(u8)*USART_NUM); //将Memory1中的数据拷贝到temp中的前5位当中 } if(rx_flag==2) { memcpy((u8*)(temp+USART_NUM),usartx_buffer1,sizeof(u8)*USART_NUM);//将Memory0中的数据拷贝到temp中的后5位当中 } if(usart_DMA_complete_rx ==2) //接收到了10个数据,关掉DMA,按一次按键只收一轮数据 { DMA_Cmd(DMA1_Stream5,DISABLE); usart_DMA_complete_rx = 0; printf("Rx Complete!!! "); printf("Press the middle key to print Rx_Buff!! "); } if(KEYM == 0)//当接收完一轮数据后,按中键将接收到的数据进行打印出来对比 { Delay_ms(1000); for(i=0; i<10; i++) { printf("0x%-2x ",temp[i]); //左对齐打印 } printf("Print Rx_buff Complete!!! "); memset(temp,0,sizeof(u8) * USART_NUM * 2); } } }
  • 相关阅读:
    GUI基础学习
    常用类string的用法
    类。对象和包--补上周
    类.对象和包
    调用函数的注意事项
    函数的简单运用
    一维数组基础
    java中scanner类的用法
    数据库——DQL(语句查询)
    数据库——JDBC
  • 原文地址:https://www.cnblogs.com/wen2376/p/12336088.html
Copyright © 2011-2022 走看看