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"\n",__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>>\r\n");//当前面第一次输入的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\r\n"); printf("Press the Left key,STM send data to PC!\r\n"); printf("Press the Right key,STM receive data from PC !\r\n"); 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!!!\r\n"); } if(KEYR==0) //右键有按下时,PC可以给ST发送数据,数据量为USART_NUM { Delay_ms(1000); printf("Please input 5 datas>>\r\n");//提示输入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!!!\r\n"); printf("Press the middle key to print Rx_Buff!!\r\n"); } if(KEYM == 0)//当接收完一轮数据后,按中键将接收到的数据进行打印出来对比 { Delay_ms(1000); for(i=0; i<10; i++) { printf("0x%-2x\r\n",temp[i]); //左对齐打印 } printf("Print Rx_buff Complete!!!\r\n"); memset(temp,0,sizeof(u8) * USART_NUM * 2); } } }

 

posted @ 2020-02-20 15:59  MyBooks  阅读(2107)  评论(0编辑  收藏  举报