DMA

BD网盘链接:

https://pan.baidu.com/s/10WHNgB_cicTP1SbdcI4AkQ?pwd=l1et 
提取码:l1et

概述

CW32L052支持DMA(Direct Memory Access),即直接内存访问,无需CPU干预,实现高速数据传输。数据的传输可以发生在:

• 外设和内存之间:例如ADC采集数据到内存,这种传输方式常见于需要将外设采集的数据快速传输到内存进行处理的应用。

• 内存和内存之间:例如在两个不同的数组之间传输数据,或者在不同的内存块之间进行数据拷贝。

• 外设和外设之间:例如从一个SPI主/从机传输数据到另一个SPI从/主机。

使用DMA能够有效减轻CPU的负担,特别是在大量数据需要高效传输的情况下,可以提高系统的整体性能。

框图
1.png

DMA功能框图

特性

使用DMA,最核心的就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是连续传输等等。

2.png

4个DMA通道的优先级和通道号绑定,通道号越小优先级越高,通道号越大优先级越低。

  • 4种传输模式: 

硬件触发BULK传输模式、硬件触发BLOCK传输模式、软件触发BULK传输模式和软件触发BLOCK传输模式。

硬件

 BULK

 BLOCK

软件

 BULK

 BLOCK

BULK传输模式:

适用于小数据块的传输,通常涉及大量的数据点,但每个数据点的大小较小。

与BLOCK模式不同,BULK模式下DMA会更频繁地启动新的传输,因为每个数据点通常被视为单独的传输单元,所以DMA控制器需要在每个数据点传输完成后需要重新配置或者启动DMA。在BULK传输模式下,传输过程不可被打断。

BLOCK传输模式:

适用于大数据块的高速传输,通常用于需要连续传输大量数据的情况。BLOCK模式下,DMA会将数据分成较大的块,并在传输时以这些块为单位进行操作。DMA控制器在一次配置后,连续传输多个数据块,而无需额外的干预或重新配置。每传输完成1个数据块后就要进行一次传输优先级的仲裁,允许CPU或者更高优先级的DMA通道访问当前DMA通道所占用的外设。

• 硬件触发和软件触发:

要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求。部分外设支持硬件触发启动DMA传输,如FLASH存储器、UART串口、TIM定时器、ADC数模转换器等被配置为DMA通道的触发源时,

 3.png

可以产生DMA请求(DMA request),硬件触发启动DMA传输,

 4.png

 而不支持硬件DMA的外设,只能配置为软件触发启动DMA传输。

 5.png

虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

• DMA中断

DMA通道在传输工程中可产生2个中断标志:传输错误中断标志和传输完成中断标志

不同 DMA 通道的中断各自独立,通过中断标志寄存器 DMA_ISR 可以获取各通道的中断标志。标志对应多个可能的产生原因,具体产生原因需查询 DMA_CSRy.STATUS 状态位,如下表所示

6.png

• 数据宽度:

数据位宽可以设置为8bit、16bit和32bit,DMA通道的源地址和目的地址的位宽必须完全一致

• 数据块数量:

传输的数据块数量可以设置为1 ~ 65535

• 数据传输优先级

当CPU和DMA访问不同的外设时,数据的传输可以同时进行;

当CPU和DMA同时访问同一个外设时,CPU的优先级高于DMA。

从外设到内存BD网盘链接:

https://pan.baidu.com/s/1RlVVPt72fJrnBBUEHf3iJQ?pwd=oklw 
提取码:oklw

通过ADC转换完成标志触发(硬件触发)DMA方式实现外设到内存的DMA传输

核心代码:
#include "main.h" 
#include "delay.h" 
#include "gpio.h" 
#include "cw32l052_lcd.h" 
#include "cw32l052_adc.h" 
#include "cw32l052_dma.h" 
 
#define NUM0 0x070d  //段式LCD数字段码 
#define NUM1 0x0600 
#define NUM2 0x030e 
#define NUM3 0x070a 
#define NUM4 0x0603 
#define NUM5 0x050b 
#define NUM6 0x050f 
#define NUM7 0x0700 
#define NUM8 0x070f 
#define NUM9 0x070b  

void ADC_Configuration(void);      //ADC配置函数 
void DMA_Configuration(void);      //DMA配置函数 
void LCD_Configuration(void);      //LCD配置函数 
void LCD_Proc(uint16_t dispdata);  //LCD子程序函数  

/* 
    **功能说明:
    **ADC采集数据触发DMA,将采集到的数据(1.2V内核电压基准源)存储在内存value中,并显示在LCD屏上 
*/ 

int main(void) 
{   
    LED_Init();   
    LCD_Configuration();   
    ADC_Configuration();   
    DMA_Configuration();   
    while (1)   
    {     
        LCD_Proc(value);  //显示采集到的ADC     
        PA15_TOG();     
        Delay_ms(500);   
    } 
}  

void ADC_Configuration(void) 
{   
    ADC_InitTypeDef   ADC_InitStruct = {0};    
    
    __RCC_ADC_CLK_ENABLE();   
    __RCC_GPIOA_CLK_ENABLE();    
    
    PA00_ANALOG_ENABLE(); //PA00 (AIN0)    
    ADC_InitStruct.ADC_OpMode = ADC_SingleChOneMode;   //单通道单次转换模式   
    ADC_InitStruct.ADC_ClkDiv = ADC_Clk_Div128;       //PCLK   A
    DC_InitStruct.ADC_SampleTime = ADC_SampTime5Clk; //5个ADC时钟周期   
    ADC_InitStruct.ADC_VrefSel = ADC_Vref_VDDA;       //VDDA参考电压(3.3V)   
    ADC_InitStruct.ADC_InBufEn = ADC_BufEnable;       //开启跟随器   
    ADC_InitStruct.ADC_TsEn = ADC_TsDisable;           //内置温度传感器失能   
    ADC_InitStruct.ADC_DMASOCEn = ADC_DMASOCEnable;   //ADC转换完成触发DMA传输   
    ADC_InitStruct.ADC_Align = ADC_AlignRight;         //ADC转换结果右对齐   
    ADC_InitStruct.ADC_AccEn = ADC_AccDisable;         //转换结果累加不使能   
    ADC_Init(&ADC_InitStruct);                        //初始化ADC配置      CW_ADC->CR1_f.DISCARD = FALSE;                    //ADC转换结果保存策略配置:新数据覆盖未被读取的旧数据          CW_ADC->CR1_f.CHMUX = ADC_Vref1P2Input;           //待转换通道配置:1.2V内核电压基准源    
    
    ADC_ClearITPendingBit(ADC_IT_EOC);   
    ADC_ITConfig(ADC_IT_EOC, ENABLE);   
    ADC_EnableNvic(ADC_INT_PRIORITY);    
    ADC_Enable();   
    ADC_SoftwareStartConvCmd(ENABLE);                  //开始转换 
}  

void ADC_IRQHandler(void) 
{     
    /* USER CODE BEGIN */   
    if(ADC_GetITStatus(ADC_IT_EOC) != RESET)   
    {       
        ADC_ClearITPendingBit(ADC_IT_EOC);       
        ADC_SoftwareStartConvCmd(ENABLE);              //开始转换   
    }     
    /* USER CODE END */ 
}  

void NVIC_Configuration(void) 
{     
    __disable_irq();      
    NVIC_ClearPendingIRQ(DMACH1_IRQn);      
    NVIC_EnableIRQ(DMACH1_IRQn);      
    __enable_irq(); 
}  

void DMA_Configuration(void) 
{   
    DMA_InitTypeDef DMA_InitStruct = {0};    
    
    __RCC_DMA_CLK_ENABLE();    
    
    DMA_StructInit(&DMA_InitStruct);      
    DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;     //BLOCK模式       
    DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT; //数据宽度32bit   
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Fix; //源地址固定   
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Fix;  //目标地址固定   
    DMA_InitStruct.DMA_TransferCnt = 1;  //数据块数量1   
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)&(CW_ADC->RESULT0);  //数据源地址 (外设)   
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&value;   //传输目标地址   (内存)   
    DMA_InitStruct.TrigMode = DMA_HardTrig;  //硬件触发DMA传输   
    DMA_InitStruct.HardTrigSource = DMA_HardTrig_ADC_SINGLETRANSCOM;  //硬件触发源:ADC单次转换完成标志   
    DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1   
    DMA_ClearITPendingBit(DMA_IT_ALL);  //清除DMA中断标志位   
    DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC, ENABLE);   //使能DMA通道1中断   
    NVIC_Configuration();   
    DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输 
}  

void DMACH1_IRQHandler(void) 
{    
     /* USER CODE BEGIN */   
     if( DMA_GetITStatus(DMA_IT_TC1) )  //DMA通道1传输完成标志     
     {         
         DMA_ClearITPendingBit(DMA_IT_TC1);          
         CW_DMACHANNEL1->CNT = 0x10001;     //REPEAT写1,传输数量为1                                
         DMA_Cmd(CW_DMACHANNEL1, ENABLE);      
     }     
     /* USER CODE END */ 
}

演示:ADC转换结果为1580左右,换算成电压:1580/4096*3.3=1.27V

7.png

从内存到内存

BD网盘链接:

https://pan.baidu.com/s/1ScCk-UqHBjJA5PxwSqMhkg?pwd=4701 
提取码:4701

通过软件触发DMA方式实现内存(FLASH)到内存(SRAM)的DMA传输

核心代码:
//单片机头文件
#include "main.h"
#include "cw32l052_lcd.h"
#include "cw32l052_dma.h"//硬件外设
#include "delay.h"#include "gpio.h"//C库
#include <string.h>

#define NUM0 0x070d  //段式LCD数字段码
#define NUM1 0x0600#define NUM2 0x030e
#define NUM3 0x070a#define NUM4 0x0603
#define NUM5 0x050b#define NUM6 0x050f
#define NUM7 0x0700#define NUM8 0x070f
#define NUM9 0x070b

#define DATASIZE 10

uint16_t const srcBuf[DATASIZE] =   //源内存(FLASH)数据
{  
    9999,8888,7777,6666,5555,  
    4444,3333,2222,1111,0
};

uint16_t dstBuf[DATASIZE]={0};      //目标内存(SRAM)数据

void DMA_Configuration(void);      //DMA配置函数
void LCD_Configuration(void);      //LCD配置函数
void LCD_Proc(uint16_t dispdata);  //LCD子程序函数

uint32_t value=0;  //ADC数值

/*
    **功能说明:
    **将srcBuf数组中的数据通过DMA传送到dstBuf数组中,
    **srcBuf数组中的数据通过const关键词存储到FLASH中,
    **dstBuf数组存储在SRAM程序运行过程中
    **传输完成后比较两数组内容,相同则打开LED1和LED1,LCD上循环显示dstBuf数据;
    **不同则进入死循环,两指示灯闪烁
*/

int main(void)
{  
    LED_Init();  
    LCD_Configuration();  
    DMA_Configuration();  
    DMA_SWTrigCmd(CW_DMACHANNEL1);   //使能通道1软件触发  
    while(DMA_GetFlagStatus(CW_DMACHANNEL1)!=DMA_CHANNEL_STATUS_TRANSCOMPLETE); //等待传输完成  
    if(memcmp(srcBuf,dstBuf,DATASIZE)==0)  //如果srcBuf和dstBuf相同  
    {      
        LED1_ON(); //指示灯      
        LED2_ON();      
        for(int i=0;i<10;i++)  //LCD屏显示dstBuf数据      
        {        
            LCD_Proc(dstBuf[i]);        
            Delay_ms(500);      
        }  
    }  
    else  //如果不相同  
    {    
        while(1)  //进入while死循环    
        {      
            PA15_TOG();  //指示灯      
            PC10_TOG();      
            Delay_ms(500);    
        }  
    }  
     
    while (1)  
    {   
    }
}

void DMA_Configuration(void)
{  
    DMA_InitTypeDef DMA_InitStruct = {0};   
    
    __RCC_DMA_CLK_ENABLE();   
    
    DMA_StructInit(&DMA_InitStruct);   
    DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK;     //BLOCK模式  
    DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_16BIT; //数据宽度16bit  
    DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase; //源地址固定  
    DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase;  //目标地址递增  
    DMA_InitStruct.DMA_TransferCnt = DATASIZE;  //数据块数量  
    DMA_InitStruct.DMA_SrcAddress = (uint32_t)&srcBuf[0];  //数据源地址 (内存)  
    DMA_InitStruct.DMA_DstAddress = (uint32_t)&dstBuf[0];   //传输目标地址 (内存)  
    DMA_InitStruct.TrigMode = DMA_SWTrig;  //软件触发DMA传输
  
    DMA_Init(CW_DMACHANNEL1,&DMA_InitStruct); //DMA通道1  
    DMA_Cmd(CW_DMACHANNEL1, ENABLE); //启动DMA通道1进行传输
}

演示:LCD屏上显示通过DMA传输的dstBuf的数据

8.png

从外设到外设

BD网盘链接:

https://pan.baidu.com/s/1fSyMPAapft2a_Vy_d3F9vw?pwd=dw6x   
提取码:dw6x

通过硬件触发DMA方式实现外设(SPI)到外设(SPI)的DMA传输

核心代码:
/*单片机头文件*/
#include "main.h"
/*硬件驱动*/
#include "delay.h"
#include "gpio.h"
#include "cw32l052_dma.h"
#include "cw32l052_spi.h"
/*C库*/
#include <string.h>

//硬件连接//SPIY_SCK  (PA10) -- SPIX_SCK  (PB13)
//SPIY_MISO (PA11) -- SPIX_MISO (PB14)
//SPIY_MOSI (PA12) -- SPIX_MOSI (PB15)

//SPI2相关定义(Master)
#define    SPIX                     CW_SPI2               
#define   SPIX_GPIO          CW_GPIOB
#define    SPIX_SCK_PIN             GPIO_PIN_13
#define   SPIX_MISO_PIN            GPIO_PIN_14
#define    SPIX_MOSI_PIN            GPIO_PIN_15
#define    SPIX_AF_SCK              PB13_AFx_SPI2SCK()
#define    SPIX_AF_MISO             PB14_AFx_SPI2MISO()
#define    SPIX_AF_MOSI             PB15_AFx_SPI2MOSI()
#define    SPIX_RX_DMACHANNEL       CW_DMACHANNEL1
#define    SPIX_TX_DMACHANNEL       CW_DMACHANNEL2  
#define     SPIX_DMA_RXTRIGSOURCE    DMA_HardTrig_SPI2_RXBufferNE
#define     SPIX_DMA_TXTRIGSOURCE    DMA_HardTrig_SPI2_TXBufferE

//SPI1相关定义(Slave)
#define    SPIY                     CW_SPI1
#define   SPIY_GPIO          CW_GPIOA
#define    SPIY_SCK_PIN             GPIO_PIN_10
#define    SPIY_MISO_PIN            GPIO_PIN_11
#define    SPIY_MOSI_PIN            GPIO_PIN_12
#define    SPIY_AF_SCK              PA10_AFx_SPI1SCK()
#define    SPIY_AF_MISO             PA11_AFx_SPI1MISO()
#define    SPIY_AF_MOSI             PA12_AFx_SPI1MOSI()

//数组长度
#define   BUFFERSIZE                 ARRAY_SZ(TxBuffer1)

//发送内容1
uint8_t TxBuffer1[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,                       
                       0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,                       
                       0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15,                       
                       0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,                       
                       0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23                      
                      };

//发送内容2
uint8_t TxBuffer2[] = {0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,                       
                       0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,                       
                       0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75,                       
                       0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C,                       
                       0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83                      
                      }; 
                      
uint8_t RxBuffer1[BUFFERSIZE];  //接收数组1
uint8_t RxBuffer2[BUFFERSIZE];  //接收数组2
uint8_t TxCounter = 0;  //发送计数
uint8_t RxCounter = 0;  //接收计数 

uint8_t TransferStatus1 = 1;  //DMA传输状态标志1
uint8_t TransferStatus2 = 1;  //DMA传输状态标志2 

void DMA_Configuration(void);      //DMA配置函数
void SPI_Configuration(void);      //SPI配置函数
void SPI_GPIO_Configuration(void); //SPI相关GPIO口配置 

/*
    **功能说明:
    **主机SPIY发送TxBuffer1中的数据,从机SPIX通过DMA接收数据并存储到RxBuffer1
  **主机SPIY发送无效数据,启动SPI通信,同时SPIX从机通过DMA发送TxBuffer2中的数据,SIPY接收数据并存储到RxBuffer2
  **单独比较TxBuffer1与RxBuffer1、TxBuffer2与RxBuffer2中的内容,比较结果通过LED灯指示
*/

int main(void)
{  
  LED_Init();               //初始化LED指示灯  
  SPI_GPIO_Configuration(); //配置PI相关GPIO口  
  DMA_Configuration();      //配置DMA传输  
  SPI_Configuration();      //配置SPI传输  
  SPI_DMACmd(SPIX, SPI_DMAReq_Rx, ENABLE); //使能SPIX DMA RX  
  SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset);//位选CS选中从机SPIX,起始信号  
  while(TxCounter < BUFFERSIZE)  
  {    
    while(SPI_GetFlagStatus(SPIY,SPI_FLAG_TXE) == RESET);//等待发送缓冲空(为空后硬件自动置1)    
    SPI_SendData(SPIY,TxBuffer1[TxCounter++]); //发送TxBuffer1中的数据,通过数据寄存器DR把数据填充到发送缓冲区中  
  }  
    
    while(DMA_GetFlagStatus(SPIX_RX_DMACHANNEL) != DMA_CHANNEL_STATUS_TRANSCOMPLETE);//等待DMA接收完成
        
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放从机SPIX,结束信号  
    TransferStatus1 = memcmp(TxBuffer1, RxBuffer1, BUFFERSIZE); //对比两数组数据  
    if(TransferStatus1==0)  //如果数据相同  
    {    
        LED1_ON();  //LED1指示  
    }  
    else  
    {    
       LED1_OFF();  
    }   
        
    TxCounter = 0;  
    SPI_ReceiveData(SPIY);//读DR以清除RXNE(接收非空)标志位  
    SPI_DMACmd(SPIX, SPI_DMAReq_Rx, DISABLE);//失能SPIX DMA RX  
    SPI_FlushSendBuff(SPIX);//清空发送缓冲区和移位寄存器  
    SPI_DMACmd(SPIX, SPI_DMAReq_Tx, ENABLE);//使能SPIX DMA TX  
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Reset);  
        
    while(TxCounter < BUFFERSIZE)  
    {          
        while(SPI_GetFlagStatus(SPIY, SPI_FLAG_TXE) == RESET){;} //主机发送数据以启动SPI通信    
        SPI_SendData(SPIY, TxBuffer1[TxCounter++]);     
           
        while(SPI_GetFlagStatus(SPIY, SPI_FLAG_RXNE) == RESET){;}      
        RxBuffer2[RxCounter++] = SPI_ReceiveData(SPIY);  //获取接收缓冲区中的内容    
    }  
        
    while(SPI_GetFlagStatus(SPIY,SPI_FLAG_BUSY) == SET); //检查数据是否已经全部通过SPI发送完毕    
    SPI_NSSInternalSoftwareConfig(SPIX, SPI_NSSInternalSoft_Set); //释放  
    TransferStatus2 = memcmp(TxBuffer2, RxBuffer2, BUFFERSIZE);  //检查  
    if(TransferStatus2 == 0)  
    {    
        LED2_ON();  
    }  
    else  
    {    
        LED2_OFF();  
    }  
    while (1)  
    {   
    }
}

void SPI_GPIO_Configuration(void)
{    
    GPIO_InitTypeDef GPIO_InitStructure = {0};     
    //打开GPIO时钟    
    __RCC_GPIOA_CLK_ENABLE();    
    __RCC_GPIOB_CLK_ENABLE();
    
  //SPI SCK MOSI MISO 复用    
  SPIY_AF_SCK;    
  SPIY_AF_MISO;    
  SPIY_AF_MOSI;    
  SPIX_AF_SCK;    
  SPIX_AF_MISO;    
  SPIX_AF_MOSI;
  
  //推挽输出    
  GPIO_InitStructure.Pins = SPIY_SCK_PIN | SPIY_MOSI_PIN;    
  GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;    
  GPIO_Init(SPIY_GPIO, &GPIO_InitStructure);
   
   GPIO_InitStructure.Pins = SPIX_MISO_PIN;    
  GPIO_Init(SPIX_GPIO, &GPIO_InitStructure);
  
  //浮空输入    
  GPIO_InitStructure.Pins = SPIX_SCK_PIN | SPIX_MOSI_PIN;    
  GPIO_InitStructure.Mode = GPIO_MODE_INPUT;    
  GPIO_Init(SPIX_GPIO, &GPIO_InitStructure);
  
  GPIO_InitStructure.Pins = SPIY_MISO_PIN;    
  GPIO_Init(SPIY_GPIO, &GPIO_InitStructure);}
void SPI_Configuration(void)
{  
    SPI_InitTypeDef SPI_InitStructure = {0};   
    
    __RCC_SPI1_CLK_ENABLE();  
    __RCC_SPI2_CLK_ENABLE();   
    
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工模式  
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式  
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //数据位宽8bit  
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  //时钟极性  
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;//时钟相位,奇数边缘采样  
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //配置NSS引脚(片选信号线)的使用模式,软件控制  
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; //波特率:PCLK8分频  
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //MSB先行模式  
    SPI_InitStructure.SPI_Speed = SPI_Speed_Low; //低速  
    SPI_Init(SPIY,&SPI_InitStructure);   
    
    SPI_InitStructure.SPI_Mode = SPI_Mode_Slave; //从机模式  
    SPI_Init(SPIX,&SPI_InitStructure); 
     
    SPI_Cmd(SPIX,ENABLE);  
    SPI_Cmd(SPIY,ENABLE);
}

void DMA_Configuration(void)
{  
    DMA_InitTypeDef DMA_InitStructure = {0};  
     
    __RCC_DMA_CLK_ENABLE();   //DMA TX 
     
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK; //BLOCK模式  
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;  
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Increase;  
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Fix;  
    DMA_InitStructure.TrigMode = DMA_HardTrig;  
    DMA_InitStructure.HardTrigSource = SPIX_DMA_TXTRIGSOURCE;  
    DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE;  
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)&TxBuffer2[0];  
    DMA_InitStructure.DMA_DstAddress = (uint32_t)&SPIX->DR; //数据寄存器  
    DMA_Init(SPIX_TX_DMACHANNEL,&DMA_InitStructure);  
    DMA_Cmd(SPIX_TX_DMACHANNEL,ENABLE);   
    
    //DMA RX      
    DMA_InitStructure.DMA_Mode = DMA_MODE_BLOCK;  
    DMA_InitStructure.DMA_TransferWidth = DMA_TRANSFER_WIDTH_8BIT;  
    DMA_InitStructure.DMA_SrcInc = DMA_SrcAddress_Fix;  
    DMA_InitStructure.DMA_DstInc = DMA_DstAddress_Increase;  
    DMA_InitStructure.TrigMode = DMA_HardTrig;  
    DMA_InitStructure.HardTrigSource = SPIX_DMA_RXTRIGSOURCE;  
    DMA_InitStructure.DMA_TransferCnt = BUFFERSIZE;  
    DMA_InitStructure.DMA_SrcAddress = (uint32_t)&SPIX->DR;  
    DMA_InitStructure.DMA_DstAddress = (uint32_t)&RxBuffer1[0];  
    DMA_Init(SPIX_RX_DMACHANNEL,&DMA_InitStructure);  
    DMA_Cmd(SPIX_RX_DMACHANNEL,ENABLE);
}

来源:CW32生态社区

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 7

01、问题背景

客户需要使用 MCU 输出正弦波,但受限于 MCU DAC 数量不足,建议尝试使用 PWM加滤波方式产生正弦波。同时要求正弦波与固定电平交替输出。因此可用一个 TIM 输出PWM,同时用另一个 TIM 来定时切换输出正弦波或固定电平。 

使用 TIM 输出 PWM 产生正弦波形时,需要结合 GPDMA 来实现。在 STM32U5 系列中,GPDMA 共有 16 个独立通道,其中 12-15 通道还具有 2D addressing/ repeat 功能。因此也可以使用一个 TIM 加 GPDMA 的一个 2D 通道实现 PWM 波形切换功能。

02、产生PWM

本文按以下配置产生 PWM,在 U575 NUCLEO 板测试: 

(1)MCU 主频:100MHz

(2)PWM 频率 2MHz(周期 500ns),脉宽可调范围 0~50 个计数时钟, 

(3)每个正弦波周期(10us)对应 20 个 PWM 脉冲,各 PWM 脉宽用计数时钟表示分别为:25, 33, 40, 45, 49, 50, 49, 45, 40, 33, 25, 17, 10, 5 , 1 , 0 , 1 , 5 , 10, 17

(4)将步骤 3 中的正弦波重复 1000 次,对应 10ms 的连续正弦波形

2.1. STM32CubeMX 生成测试工程

2.1.1. TIM1 CH1 PWM 配置

1.png

图1. TIM1 CH1 PWM

2.1.2. GPDMA CH12 配置

选用 GPDMA 通道 12,并配置为循环模式:

2.png

图2. GPDMA CH12

2.1.3. GPDMA Linked List 配置

创建 Linked List Queue,并配置为搭配 GPDMA 2D 功能通道使用。创建两个节点,TN1, TN2,并使用循环模式,指定首个循环节点为 TN1。

3.png

图3. Linked List

TN1 节点配置,由此节点结合 TIM 来产生 PWM,并滤波成正弦信号: 

(1)TIM1 更新事件作为 DMA 请求 

(2)使能 2D 功能,一个 block 传输完成后,回退到数组起点,重新传输 

(3)使能 Repeat 功能,重复 block 传输 1000 次

首先使能了 TrustZone 架构,然后将 LPGPIO 映射到了非安全区,并且配置了 DMA 链表功能,使用 LPTimer 作为触发,自动地修改 LPGPIO 的寄存器,从而达到在低功耗模式下,GPIO自动切换的功能。但遇到了 LPDMA 的配置问题,并且程序无法跳转到 Non-Secure 工程。

4.png

图4. Linked List Node1

TN2 节点配置,与 TN1 节点类似,用于切换到第二组数据产生第二种波形

5.png

图5. Linked List Node2

2.2. 测试代码

6.png

GPDMA Linked List 模式执行流程

7.png

图6. Linked List 执行过程及期望输

03、PWM滤波输出

在 U575 NUCLEO 板上测试,结果如下:

8.png

图7. 实际输出

从实测结果来看,滤波后的正弦波频率,波形持续时长都符合预期。另外,与通过额外 TIM 计时来切换 PWM 输出的方式相比,使用 Linked List repeat 这种方式,正弦波与固定电平输出之间切换更平滑。

9.png

图8. 额外 TIM 计时来切换 PWM 输出

04、小结

通过使用 GPDMA Linked List 模式,使用 2D addressing repeat 功能,能方便实现这种多种波形切换的应用场景。如 Node1 与 Node2 使用不同的数据长度和重复次数,则可得到不同时长的两种波形;通过增加更多 Node,则可得到多种不同波形。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 13

01、前言

有客户反馈,使用STM32F4的TIM2结合DMA,产生的PWM波形不符合预期,但是相同的配置使用在TIM3上,得到的PWM波形就是符合预期的。其代码和配置都是从F1移植过来的,在F1上使用TIM2是没有问题的,对于F4的TIM2发生的问题,客户一直没有找到根本原因。

02、实验

根据客户的反馈,我们进行了实验。

硬件:STM32F401RE-NUCLEO

在STM32CubeMX中,将TIM2和TIM3所有参数均做相同的配置,其中配置DMA两端均为halfword长度。

1.png

2.png

生成代码,并定义两个数组如下图所示:

3.png

在主函数中开启Timer。

4.png

我们可以发现,实验结果如客户反馈的,TIM2输出的PWM是不正确的,TIM3输出的PWM是正确的。

5.png

03、分析

我们的实验中,TIM2和TIM3的配置是完全一样的,即使传输相同的数据,得到的PWM波形也是不同的。为此我们比较了TIM2和TIM3的硬件属性,可以很容易查看出,TIM2的计数器是32bit的,而TIM3的计数器是16bit的。

我想我们已经知道答案了,TIM2的计数器是32bit的,但是我们配置的DMA是halfword长度,这在AHB总线上解析数据时产生了非预期的结果。在调试界面我们也能看到,当问题发生时,TIM2的CCR1竟然比ARR的值要大,或者出现异常值,所以出现异常波形。

6.png

根本原因在于,对于大部分STM32系列,主设备基于AHB外设进行寻址是不支持byte/half-word传输的,总线会强制将数据转化为32bit传送到总线上,这就是为什么我们看到CCR1的高半字和低半字的值是相同的原因。

当我们将TIM2的DMA外设端修改为word长度,并将内存数组定义为32bit,再次实验,可以发现PWM的波形就是正常的了:

7.png

8.png

9.png

04、小结

因为F103上没有32bit计数器的Timer,所以客户在F103上并没有出现类似的问题。在使用DMA访问经过AHB转APB的桥接外设时,我们要注意DMA对外设的访问宽度配置问题。

来源:STM32单片机

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 81

直接存储器访问(DMA)控制器,可以在内存和/或外设之间传输数据,而不需要CPU参与每次传输。合理利用DMA控制器,可以减轻CPU的负担。本文通过介绍DMA结构与工作原理,以及两种模式(兵乓模式与多数据包缓冲传输模式),来看看使用DMA如何提高MCU效率。

DMA结构与工作原理

先进的DMA控制器,如STMicroelectronicsSTM32F4系列中包含的控制器,可以通过灵活的数据流分配和传输管理功能进一步减轻CPU的负担。

如图左侧所示,来自8个不同的通道DMA请求,并到仲裁器上,从而建立优先级(编号较低的输入通道,具有较高的优先级)。然后激活最高优先级的传输,传输到图中右侧的两个AHB 主设备(存储器端口和外设接口),提高了外设到存储器传输的效率。这可能是DMA在基于CPU的设计中最常见的情况。

1.jpg

图 1.   STM32F4系列DMA控制器(图片来源于STMicroelectronics)

为每个路径分配单独的FIFO,如图1中间所示,允许针对每个外设接口的特性调整FIFO特性。例如,FIFO的阈值级别(请求传输的深度)可以单独设置为FIFO大小的¼,½或¾。这允许低速通道等待,直到FIFO几乎满了才进行传输,以最小化开销。更快的通道会更早地启动传输,可能只有一半大小,以避免FIFO溢出。

我们来通过一个实例,来看看DMA怎么工作的。

实例:“使用 STM32 来控制 NeoPixels LED”

硬件部分采用 STM32 开发板,与 NeoPixel LED灯带矩阵等相连接。

2.png

RGB NeoPixels实际上是WS2812智能控制LED。下面是WS2812 LED的3字节数据协议的结构,分别代表绿红蓝三个信息。

3.jpg

图 2.  WS2812 LED的3字节数据协议的结构

使用计时器来PWM控制波形,然后配置DMA使CPU高效并且易于实施。

4.jpg
图 3.   WS2812 LED的0和1位的计时图

在软件中,配置DMA, 选择了“TIM2_CH3/UP”, 将方向改为“内存到外设”, 同时,将优先级改为“非常高”,最后保存.ioc 文件,以生成项目代码。

5.jpg
图 4.   配置DMA流,以便有效更新PWM信号的占空比

更多内容请看下面文章:使用 STM32 来控制 NeoPixels

DMA的两种模式

合理使用两种DMA模式(兵乓模式与多数据包缓冲传输模式),可以帮助提高MCU效率。

USB外设是一个很好的外设示例,早期的USB实现的最大吞吐量只有1.5 Mb/秒。随着更高性能的标准版本的出现。比如要接近12 Mbit/s全速USB标准的理论最大值。我们来看看,数据传输方面DMA如何帮助提高MCU效率!

我们以MicrochipATXMEGA16D4-MH举例。

01、兵乓模式

之前通常使用单个存储器缓冲区进行外设数据传输。如果数据缓冲区已满,MCU将响应NAK(否定确认)消息。接收到NAK后,主机将等待并稍后重试传输。它将继续重试,直到MCU能够成功接收数据。

ATXMEGA16D4-MH使用乒乓模式来消除这个问题。乒乓模式使用两个存储单元(memory banks)进行数据传输。当一个存储单元满时,主机可以将数据传输到另一个存储单元。在两个存储单元之间交替传输可以避免复审,并提高整体数据带宽。

6.jpg
图 5.  乒乓模式提高了效率(图片来源于Microchip)

此外,如上图所示,以乒乓模式还使MCU有更多时间来处理数据。如图所示,没有乒乓,CPU只能处理传输之间的数据。使用乒乓模式,CPU可以在传输周期的一部分时间内处理数据,并降低NAK被要求“赶上”数据处理要求的可能性。

02、多数据包缓冲传输模式

另一个很有用的模式,可以让MCU的数据传输更高效。这个特性叫做“多数据包缓冲传输模式”。如果你要通过USB端口传送的数据包,超过了全速USB的BULK传输模式所允许的最大值(64字节),那么就可以用上这个模式。以前,你需要在主机上把数据包分成小块,然后在接收端把它们合并,这会增加中央处理器(CPU)的负担。不过现在,多数据包缓冲功能加入了USB设备,它会在数据包超过USB标准大小时自动帮你分割和合并数据。重要的是,这个模式还能减少中断的次数,因为只有在整个传输结束后才需要中断CPU。这意味着,CPU可以处理其他任务,或者进入休眠模式,直到整个传输完成并且准备好处理。

总结

合理利用DMA控制器,可以减轻减轻CPU的负担,事半功倍。结合“乒乓缓冲”和“多传输模式”,你可以把传输的带宽从基准BULK传输模式的5.6 Mb/s提升到8.7 Mbits/s,这是一个不小的提升。更重要的是,在使用这两个功能的情况下,CPU的负担从基准的46%降低到只有9%。这两个功能的结合,不仅在性能上有所改进,而且还能节省能源。

来源:得捷电子DigiKey(作者:Alan Yang)

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 76

在上一讲单通道ADC电压采集的基础上,本节主要介绍CKS32F4xx系列产品基于DMA传输的ADC多通道电压采集转换实现。

DMA传输在ADC中的应用

DMA是直接存储器存取,通常在使用ADC时,需要通过MCU内核不停的读取数据,如果使用DMA,那么读取的过程会绕过MCU,减轻MCU内核的处理压力,这样有利于资源的充分利用,提高ADC数据的处理效率。由于ADC规则通道组只有一个数据寄存器中,当转换多个通道时,使用DMA还可以避免丢失已经存储在ADC_DR寄存器中的数据。在使能DMA模式的情况下,每完成规则通道组中的一个通道转换后,都会生成一个DMA请求,便可将转换的数据从ADC_DR寄存器传输到用指定的目标内存位置。这样取代单通道实验使用中断服务的读取方法,可以实现多通道ADC应用中高速高效的采集。

软件设计要点

跟单通道例程一样,编写两个ADC驱动文件,bsp_adc.h和bsp_adc.c,用来存放ADC所用IO引脚的初始化函数以及ADC和DMA相关配置函数,主要流程为:

(1)初始化配置ADC目标引脚为模拟输入模式;

(2)使能ADC时钟和DMA时钟;

(3)配置DMA从ADC数据寄存器传输数据到指定的存储区;

(4)配置通用ADC为独立模式;

(5)设置ADC为12位分辨率,启动扫描,连续转换,不需要外部触发;

(6)设置ADC转换通道顺序及采样时间;

(7)使能DMA请求,DMA在AD转换完自动传输数据到指定的存储区;

(8)启动ADC模块;

(9)软件使能触发ADC转换。

这里需要注意的是,在使用ADC+DMA功能时,如果在启动ADC转换之后使能DMA,ADC采样数据可能会出现异常。因此建议先配置ADC及DMA相关参数,最后启动ADC转换。

代码实现

受篇幅限制,这里只介绍核心的部分代码,有些变量的设置,头文件的包含等并没有涉及到,完整的代码请参考本课程配套的例程。相关核心代码实现如下:

(1)ADC宏定义

#define TEMP_NOFCHANEL      3
/*=====================通道1 IO======================*/
// PB0 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT1    GPIOB
#define TEMP_ADC_GPIO_PIN1     GPIO_Pin_0
#define TEMP_ADC_GPIO_CLK1     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL1      ADC_Channel_8
/*=====================通道2 IO ======================*/
// PB1 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT2    GPIOB
#define TEMP_ADC_GPIO_PIN2     GPIO_Pin_1
#define TEMP_ADC_GPIO_CLK2     RCC_AHB1Periph_GPIOB
#define TEMP_ADC_CHANNEL2      ADC_Channel_9
/*=====================通道3 IO ======================*/
// PA6 ADC IO宏定义,可用杜邦线接 3V3 或者 GND 来实验
#define TEMP_ADC_GPIO_PORT3    GPIOA
#define TEMP_ADC_GPIO_PIN3     GPIO_Pin_6
#define TEMP_ADC_GPIO_CLK3     RCC_AHB1Periph_GPIOA
#define TEMP_ADC_CHANNEL3     ADC_Channel_6
// ADC 序号宏定义
#define TEMP_ADC              ADC1
#define TEMP_ADC_CLK          RCC_APB2Periph_ADC1
// ADC DR寄存器宏定义,ADC转换后的数字值则存放在这里
#define TEMP_ADC_DR_ADDR    ((u32)ADC1+0x4c)
// ADC DMA 通道宏定义,使用DMA传输
#define TEMP_ADC_DMA_CLK      RCC_AHB1Periph_DMA2
#define TEMP_ADC_DMA_CHANNEL  DMA_Channel_0
#define TEMP_ADC_DMA_STREAM   DMA2_Stream0

定义多个通道进行多通道ADC实验,并且定义DMA相关配置。

(2)ADC GPIO初始化

static void Temp_ADC_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    /*=====================通道1======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK1,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN1; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT1, &GPIO_InitStructure);
    /*=====================通道2======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK2,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN2; // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;     //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT2, &GPIO_InitStructure);
    /*=====================通道3=======================*/
    RCC_AHB1PeriphClockCmd(TEMP_ADC_GPIO_CLK3,ENABLE); // 使能 GPIO 时钟
    GPIO_InitStructure.GPIO_Pin = TEMP_ADC_GPIO_PIN3;  // 配置 IO
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;   //不上拉不下拉
    GPIO_Init(TEMP_ADC_GPIO_PORT3, &GPIO_InitStructure);
}

使用到GPIO时候都必须开启对应的GPIO时钟,GPIO用于AD转换功能必须配置为模拟输入模式。

(3)配置ADC工作模式

static void Temp_ADC_Mode_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;     
    // --------------DMA Init 结构体参数初始化-------------     
    // ADC1使用DMA2,数据流0,通道0,
    RCC_AHB1PeriphClockCmd(TEMP_ADC_DMA_CLK, ENABLE);   // 开启DMA时钟
    DMA_InitStructure.DMA_PeripheralBaseAddr = TEMP_ADC_DR_ADDR; // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)ADC_ConvertedValue;  // AD值存储地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;   // 数据传输方向为外设到存储器
    DMA_InitStructure.DMA_BufferSize = TEMP_NOFCHANEL; // 缓冲区大小,指一次传输的数据量
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外设寄存器只有一个,地址不递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   // 存储器地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 外设数据大小为半字
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;   // 存储器数据大小也为半字
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环传输模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;// DMA传输通道优先级为高
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;    // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO大小,FIFO禁止时不用配置
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
    DMA_InitStructure.DMA_Channel = TEMP_ADC_DMA_CHANNEL; // 选择 DMA 通道,通道存在于流中
    DMA_Init(TEMP_ADC_DMA_STREAM, &DMA_InitStructure);//初始化DMA流,
    DMA_Cmd(TEMP_ADC_DMA_STREAM, ENABLE); // 使能DMA流

    RCC_APB2PeriphClockCmd(TEMP_ADC_CLK , ENABLE);// 开启ADC时钟     
    // -------------ADC Common 结构体 参数初始化----------------
    ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;// 独立ADC模式
    ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;// 时钟为fpclk x分频
    ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // 禁止DMA直接访问模式     
    ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_20Cycles;  // 采样时间间隔
    ADC_CommonInit(&ADC_CommonInitStructure);     
    // -------------------ADC Init 结构体 参数初始化--------------------------
    ADC_StructInit(&ADC_InitStructure);
    ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;// ADC 分辨率
    ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 扫描模式,多通道采集需要
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  // 连续转换
    ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//禁止外部边沿触发
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;  //外部触发通道,使用软件触发时此值随便赋值即可
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐ADC_InitStructure.ADC_NbrOfConversion = TEMP_NOFCHANEL;      //转换通道3个                             
    ADC_Init(TEMP_ADC, &ADC_InitStructure);

    
  // 配置 ADC 通道转换顺序和采样时间周期
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL1, 1, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL2, 2, ADC_SampleTime_3Cycles);
  ADC_RegularChannelConfig(TEMP_ADC, TEMP_ADC_CHANNEL3, 3, ADC_SampleTime_3Cycles);

    ADC_DMARequestAfterLastTransferCmd(TEMP_ADC, ENABLE); // 使能DMA请求      
    ADC_DMACmd(TEMP_ADC, ENABLE);// 使能ADC DMA
    ADC_Cmd(TEMP_ADC, ENABLE);  // 使能ADC
    ADC_SoftwareStartConv(TEMP_ADC);//开始ADC转换,软件触发
}

首先,使用DMA_InitTypeDef定义了DMA初始化类型变量,另外使用ADC_InitTypeDef和ADC_CommonInitTypeDef结构体分别定义一个ADC初始化和ADC通用类型变量。

调用RCC_APB2PeriphClockCmd()开启ADC时钟以及RCC_AHB1PeriphClockCmd()开启DMA时钟。

对DMA进行必要的配置。首先设置外设基地址就是ADC的规则数据寄存器地址;存储器的地址就是指定的数据存储区空间,ADC_ConvertedValue是我们定义的一个全局数组名,它是一个无符号16位含有3个元素的整数数组;ADC规则转换对应只有一个数据寄存器,所以地址不能递增,而定义的存储区是专门用来存放不同通道数据的,所以需要自动地址递增。ADC的规则数据寄存器只有低16位有效,实际存放的数据只有12位而已,所以设置数据大小为半字大小。ADC配置为连续转换模式,DMA也设置为循环传输模式。设置好DMA相关参数后就使能DMA的ADC通道。

接下来使用ADC_CommonInitTypeDef结构体变量ADC_CommonInitStructure来配置ADC为独立模式、分频系数4、20个周期的采样延迟,并调用ADC_CommonInit函数完成ADC通用工作环境配置。

使用ADC_InitTypeDef结构体变量ADC_InitStructure来配置ADC1为12位分辨率、使能扫描模式、启动连续转换、使用内部软件触发无需外部触发事件、使用右对齐数据格式、转换通道为3,并调用ADC_Init函数完成ADC1工作环境配置。

ADC_RegularChannelConfifig函数用来绑定ADC通道转换顺序和采样时间。分别绑定3个ADC通道引脚并设置相应的转换顺序。

ADC_DMARequestAfterLastTransferCmd函数控制是否使能ADC的DMA请求,如果使能请求,并调用ADC_DMACmd函数使能DMA,则在ADC转换完成后就请求DMA实现数据传输。ADC_Cmd函数控制ADC转换启动和停止。

最后使用软件触发调用ADC_SoftwareStartConvCmd函数进行使能配置。

(4)Main程序

/**  主函数  */
int main(void)
{
    Debug_USART_Config();
    Temp_Init();
    while (1)
    {
        ADC_ConvertedValueLocal[0] =(float) ADC_ConvertedValue[0]/4096*(float)3.3;
        ADC_ConvertedValueLocal[1] =(float) ADC_ConvertedValue[1]/4096*(float)3.3;
        ADC_ConvertedValueLocal[2] =(float) ADC_ConvertedValue[2]/4096*(float)3.3;
        printf("\r\n PB0 value = %f V \r\n",ADC_ConvertedValueLocal[0]);
        printf("\r\n PB1 value = %f V \r\n",ADC_ConvertedValueLocal[1]);
        printf("\r\n PA6 value = %f V \r\n",ADC_ConvertedValueLocal[2]);
        Delay(0xffffff);  
    }
}

主函数先调用Debug_USART_Config函数配置调试串口相关参数,接下来调用Temp_Init函数进行ADC初始化配置并启动ADC。配置了DMA数据传输,它会自动把ADC转换完成后数据保存到数组ADC_ConvertedValue内,我们只要使用数组就可以了。经过简单地计算就可以得到每个通道对应的实际电压。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 11

DMA简介

IDMA控制器的作用不仅在增强系统性能并减少处理器的中断生成,而且还针对32位MCU应用程序专门优化设计。DMA控制器为存储器到存储器,存储器到外设和外设到存储器的传输提供了7个通道。每个通道都支持外设的DMA请求映射到任意通道上。
1.png
图1. DMA控制器架构
DMA请求弹性映射简介
在使用AT43F425xx系列DMA时,必须配置DMA请求弹性映射功能,否则DMA不会运作。DMA请求弹性映射可将任意一个外设产生的DMA请求映射到通道1到通道7中的任意一个通道。
当设定弹性模式时(DMA_FLEX_EN=1),每个通道的请求来源由CHx_SRC来设定[x=1~7]。使用例子:假如DMA通道1指定成I2C1_TX,通道3要指定成I2C1_RX,其他不使用,则设定上必须是DMA_FLEX_EN=1,CH1_SRC=11,CH3_SRC=10,CH[2/4/5/6/7]_SRC=0。
CHx_SRC设定值对应请求来源见下表:
各IP对应ID号如下表:
2.png
表1. 各IP对应ID号列表
注:表格中“CHx_SRC”为ID号;“请求来源”为各IP的DMA请求。
DMA功能解析

可编程数据宽度

DMA控制器的通道可支持传输不同数据宽度,byte/halfword/word。通过DMA_CxCTRL中的PWIDTH和MWIDTH位可以对源数据和目标数据的数据宽度进行编程,通常情况下需要设置PWIDTH和MWIDTH位相等,当PWIDTH不等于MWIDTH时,会依据PWIDTH/MWIDTH设定将资料对齐。
3.png
图2. PWIDTH:byte, MWIDTH:half-word
4.png
图3. PWIDTH:half-word, MWIDTH:word
配置DMA弹性映射
在M2P与P2M模式下,必须配置DMA弹性映射,否则DMA不会响应外设DMA请求。DMA弹性映射的作用是为外设的DMA请求复用通道,即任何一个外设的DMA请求可以映射到DMA1的任意通道,这大大增加了DMA通道分配的灵活性。
配置DMA弹性映射比较简单,只需调用专门提供的接口函数即可:
DMA配置解析
以下对DMA的配置接口及流程进行说明。
函数接口
5.png
表2. 通道配置函数列表
数据流配置
  • 设置外设地址(CxPADDR寄存器)

    数据传输的初始外设地址,在传输过程中不可被改变。

  • 设置存储器地址(CxMADDR寄存器)

    数据传输的初始内存地址,在传输过程中不可被改变。

  • 配置数据传输量(CxDTCNT寄存器)

    可编程的传输数据长度最大为65535。在传输过程中,该传输数据量的值会逐渐递减。

  • 数据流配置(CxCTRL寄存器)

    包含通道优先级,数据传输的方向、宽度、地址增量模式、循环模式和中断方式。

    优先级(CHPL)

    分为4个等级,最高优先级、高优先级、中等优先级和低优先级。

    若有2个流优先级设定相同,则较低编号的流有较高的优先权。举例,通道1优先于通道2。

    数据传输方向(DTD)

    分为存储器到外设(M2P),外设到存储器(P2M)或存储器到存储器(M2M)传输。在存储器到存储器传输模式下不允许使用循环模式、双缓冲模式和直接模式。

    数据传输宽度(PWIDTH/MWIDTH)

    根据实际使用情景,可配置宽度为byte、halfword、word。

    地址增量模式(PINCM/MINCM)

    当通道配置设定为增量模式时,下一笔传输的地址将是前一笔传输地址加上传输宽度(PWIDTH/MWIDTH)。

    循环模式(LM)

    当流配置设定为循环模式时,在最后一次传输后CxDTCNT寄存器的内容会恢复成初始值。

  • 配置DMA弹性映射(DMA_SRC_SELx寄存器的CHx_SRC)

    在非存储器到存储器(M2M)模式下时,需要将外设的DMA请求DMA请求号写入,才能启动通道响应外设的DMA请求。

  • 打开通道(CxCTRL寄存器的CHEN位)

配置流程
  • 打开DMA时钟;

  • 调用通道复位函数复位数据流;

  • 调用结构体初始化函数初始化通道配置结构体;

  • 调用初始化函数初始化通道;

  • 调用DMA请求映射使能函数配置弹性映射功能;

  • 调用通道使能函数开启通道。

来源:AT32 MCU 雅特力科技

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 69

在上一讲,我们讲过CKS32F4xx系列的6个串口都支持DMA传输。因此本节我们对CKS32F4xx系列的DMA进行介绍,同时利用DMA对串口数据进行传输。

DMA介绍

DMA,全称为:Direct Memory Access,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。

CKS32F4xx系列最多有2个DMA控制器(DMA1和DMA2),共16个数据流(每个控制器8个),每个数据流总共可以有多达8个通道(或称请求)。CKS32F4xx系列的DMA支持外设到存储器传输、存储器到外设传输和存储器到存储器传输三种传输模式。,存储器一般是指片内SRAM、外部存储器、片内Flash等等。

外设到存储器:把外设数据寄存器内容转移到指定的内存空间。比如进行ADC采集时我们可以利用DMA传输把AD转换的数据转移到我们定义的存储区中。 

存储器到外设:把特定存储区内容转移至外设的数据寄存器中,这种多用于外设的发送通信,比如SPI、I2C和串口等。

存储器到存储器:就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类似于C语言内存拷贝函数memcpy,不过利用DMA传输可以达到更高的传输效率。

注意:只有DMA2控制器支持存储器到存储器的传输,DMA1不支持。

DMA功能框图

1.png

标号1处是外设通道,每一个数据流(标号2)对应着8个外设通道,外设通道选择要解决的主要问题是决定哪一个外设作为该数据流的源地址或者目标地址。DMA1各个通道的请求映像如下表所示:

2.png

DMA2各个通道的请求映像如下表所示:

3.png

比如我们利用DMA将存储器数据传输到串口3,然后发送出去,那么根据上表可知,我们可以选择DMA1的数据流3通道4,或者DMA1的数据流4通道7。而将串口3的数据传输到存储器则可以选择DMA1的数据流1通道4。

标号2处是数据流和仲裁器,一个DMA控制器对应8个数据流,数据流包含要传输数据的源地址、目标地址、数据等等信息。如果我们需要同时使用同一个DMA控制器 (DMA1 或DMA2) 多个外设请求时,那必然需要同时使用多个数据流,这时就需要仲裁器对每一个数据流的优先级进行划分。数据流的优先级可以通过配置DMA_SxCR寄存器 PL[1:0]位,可以设置为非常高、高、中和 低四个级别。如果两个或以上数据流软件设置优先级一样,则他们优先级取决于数据流编号,编号越低越具有优先权,比如数据流2优先级高于数据流 3。

标号3处是FIFO,用于在源数据传输到目标地址之前临时存放数据用的。可以通过DMA数据流xFIFO控制寄存器DMA_SxFCR的FTH[1:0]位来控制FIFO的阈值,分别为1/4、1/2、3/4和满。如果数据存储量达到阈值级别时,FIFO内容将传输到目标中。

标号4和5处是存储器端口、外设端口。DMA控制器通过存储器端口和外设端口与存储器和外设进行数据传输。DMA2(DMA控制器2)的存储器端口和外设端口都是连接到AHB总线矩阵,可以使用AHB总线矩阵功能。而DMA1的存储区端口相比DMA2的要减少AHB2外设的访问权,同时DMA1外设端口是没有连接至总线矩阵的,只有连接到APB1外设,所以 DMA1不能实现存储器到存储器传输。

标号6处是编程端口,AHB从器件编程端口是连接至AHB2外设的,AHB2外设在使用 DMA传输时需要相关控制信号。

DMA软件配置

在标准库函数中对DMA进行初始化主要是对结构体DMA_InitTypeDef内的各类参数进行初始化。DMA_InitTypeDef结构体参数及介绍如下:

typedef struct
{  
    uint32_t DMA_Channel;            
    uint32_t DMA_PeripheralBaseAddr;   
    uint32_t DMA_Memory0BaseAddr;     
    uint32_t DMA_DIR;                 
    uint32_t DMA_BufferSize;          
    uint32_t DMA_PeripheralInc;       
    uint32_t DMA_MemoryInc;         
    uint32_t DMA_PeripheralDataSize;   
    uint32_t DMA_MemoryDataSize;      
    uint32_t DMA_Mode;               
    uint32_t DMA_Priority;            
    uint32_t DMA_FIFOMode;           
    uint32_t DMA_FIFOThreshold;       
    uint32_t DMA_MemoryBurst;       
    uint32_t DMA_PeripheralBurst;   
}DMA_InitTypeDef;

1)DMA_Channel:DMA请求通道的选择,每个外设对应固定的通道,具体设置值需要查25.2节中的DMA1和DMA2各个通道的请求映像表。该值在标准库中可供选择的参数值如下:

#define DMA_Channel_0                     ((uint32_t)0x00000000)
#define DMA_Channel_1                     ((uint32_t)0x02000000)
#define DMA_Channel_2                     ((uint32_t)0x04000000)
#define DMA_Channel_3                     ((uint32_t)0x06000000)
#define DMA_Channel_4                     ((uint32_t)0x08000000)
#define DMA_Channel_5                     ((uint32_t)0x0A000000)
#define DMA_Channel_6                     ((uint32_t)0x0C000000)
#define DMA_Channel_7                     ((uint32_t)0x0E000000)

2)DMA_PeripheralBaseAddr:外设地址,设定DMA_SxPAR寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储区地址。比如要进行串口DMA传输,那么外设基地址为串口接收发送数据存储器USART1->DR的地址,表示方法为&USART1->DR。

3)DMA_Memory0BaseAddr:存储器0地址,设定DMA_SxM0AR寄存器值;一般设置为我们自定义存储区的首地址。比如我们程序中自己定义的用来存放数据的数组,此时这个值设置为数组名即可。

4) DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设以及存储器到存储器。该值在标准库中可供选择的参数值如下:

DMA_DIR_PeripheralToMemory  //外设到存储器
DMA_DIR_MemoryToPeripheral  //存储器到外设
DMA_DIR_MemoryToMemory  //存储器到存储器

5)DMA_BufferSize:设定待传输数据数目,初始化设定DMA_SxNDTR寄存器的值。

6)DMA_PeripheralInc:是否使能外设地址自动递增功能,它设定DMA_SxCR寄存器的PINC位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。该值在标准库中可供选择的参数值如下:

DMA_PeripheralInc_Enable      //使能
DMA_PeripheralInc_Disable     //不使能

7)DMA_MemoryInc:是否使能存储器地址自动递增功能,它设定DMA_SxCR寄存器的MINC位的值;我们自定义的存储区一般都是存放多个数据的,所以一般是使能存储器地址自动递增功能。该值在标准库中可供选择的参数值如下:

DMA_MemoryInc_Enable      //使能
DMA_MemoryInc_Disable     //不使能

8)DMA_PeripheralDataSize:外设数据宽度,可选字节(8位)、半字(16位)和字(32位),根据外设数据长度进行选择。该值在标准库中可供选择的参数值如下:

DMA_PeripheralDataSize_Byte      //字节
DMA_PeripheralDataSize_HalfWord  //半字
DMA_PeripheralDataSize_Word     //字

9) DMA_MemoryDataSize:存储器数据宽度,可选字节(8位)、半字(16位)和字(32位),和外设数据宽度相对应。该值在标准库中可供选择的参数值如下:

DMA_MemoryDataSize_Byte      //字节
DMA_MemoryDataSize_HalfWord  //半字
DMA_MemoryDataSize_Word     //字

10) DMA_Mode:DMA传输模式选择,可选一次传输或者循环传输。该值在标准库中可供选择的参数值如下:

DMA_Mode_Normal      //一次传输
DMA_Mode_Circular     //循环传输

11) DMA_Priority:软件设置数据流的优先级,有4个可选优先级分别为非常高、高、中和低。DMA优先级只有在多个DMA数据流同时使用时才有意义,如果只有一个数据流的话,设置成非常高优先级就可以了。该值在标准库中可供选择的参数值如下:

DMA_Priority_Low         //低
DMA_Priority_Medium     //中
DMA_Priority_High       //高
DMA_Priority_VeryHigh   //非常高

12) DMA_FIFOMode:FIFO模式使能,如果设置为DMA_FIFOMode_Enable 表示使能FIFO模式功能;如果采用直接传输模式,则不需要使用FIFO模式。直接模式下,DMA直接进行数据从源地址到目的地址的传输。而FIFO模式下,可以将要传输的多个数据(或字节)累计存储在FIFO缓冲器中,然后在FIFO缓冲器中设置存储阈值,当到达阈值时,FIFO会自动把所有存储的数据一次性的发送到目标地址。该值在标准库中可供选择的参数值如下:

DMA_FIFOMode_Disable  //不使能
DMA_FIFOMode_Enable  //使能

13) DMA_FIFOThreshold:FIFO阈值选择,可选4种状态分别为FIFO容量的1/4、1/2、3/4和满;不使用FIFO模式时,该设置改值无效。该值在标准库中可供选择的参数值如下:

DMA_FIFOThreshold_1QuarterFull         //1/4
DMA_FIFOThreshold_HalfFull            //1/2
DMA_FIFOThreshold_3QuartersFull       //3/4
DMA_FIFOThreshold_Full              //满

14) DMA_MemoryBurst:存储器突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。单次传输模式下,一次操作(软件)只能传输一次,突发传输模式下,一次操作可以传输多次,如4次,8次,16次。该值在标准库中可供选择的参数值如下:

DMA_MemoryBurst_Single            //单次
DMA_MemoryBurst_INC4            //4节拍
DMA_MemoryBurst_INC8           //8节拍
DMA_MemoryBurst_INC16          //16节拍

15) DMA_PeripheralBurst:外设突发模式选择,可选单次模式、4节拍的增量突发模式、8节拍的增量突发模式或16节拍的增量突发模式。该值在标准库中可供选择的参数值如下:

DMA_PeripheralBurst_Single        //单次
DMA_PeripheralBurst_INC4         //4节拍
DMA_PeripheralBurst_INC8         //8节拍
DMA_PeripheralBurst_INC16        //16节拍

串口DMA接发通信实验

串口的DMA接发通信实验是存储器到外设和外设到存储器的数据传输。在第24课串口通信的基础上编写而成。

1.编程要点

1)配置USART通信功能;

2)配置DMA通信功能,存储器到外设和外设到存储器两种模式的配置。

3)使能指定的DMA数据流中断;

4)使能USART3的DMA发送和接收请求;

5)开始一次DMA传输。

2.代码分析

代码中有关串口的配置的程序在第24课已经详细讲过了,这里就不再讲述。主要是对DMA相关的代码进行分析,相关程序在dma.c文件里。

代码清单1:DMA初始化配置

void MYDMA_Config(void)
{   
    DMA_InitTypeDef  DMA_InitStructure;  
    NVIC_InitTypeDef NVIC_InitStructure;  
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);  
    while (DMA_GetCmdStatus(DMA1_Stream3) != DISABLE){}   
    //存储器到外设的DMA配置  
    DMA_DeInit(DMA1_Stream3);  
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)USART3_TX_BUF;//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式  
    DMA_InitStructure.DMA_BufferSize = USART3_MAX_TX_LEN;//数据传输量   
    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;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输  
    DMA_Init(DMA1_Stream3, &DMA_InitStructure);//初始化DMA Stream  
    DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3);                         //清除标志位      
    DMA_Cmd(DMA1_Stream3, DISABLE);                                          //关闭DMA  
    DMA_ITConfig(DMA1_Stream3,DMA_IT_TC,ENABLE);     
    NVIC_InitStructure.NVIC_IRQChannel                   = DMA1_Stream3_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;  
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;  
    NVIC_Init(&NVIC_InitStructure);  USART_DMACmd(USART3,USART_DMAReq_Tx,ENABLE);       
    //外设到存储器的DMA配置  
    DMA_DeInit(DMA1_Stream1);  
    while (DMA_GetCmdStatus(DMA1_Stream1) != DISABLE){}//等待DMA可配置   
    DMA_InitStructure.DMA_Channel = DMA_Channel_4;  //通道选择  
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);;//DMA外设地址  
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)USART3_RX_BUF;//DMA 存储器0地址  
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;//存储器到外设模式  
    DMA_InitStructure.DMA_BufferSize = USART3_MAX_RX_LEN;//数据传输量   
    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_VeryHigh;//最高优先级  
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;           
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;  
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存储器突发单次传输  
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外设突发单次传输  
    DMA_Init(DMA1_Stream1, &DMA_InitStructure);//初始化DMA Stream  
    DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1);                         //清除标志位      
    DMA_Cmd(DMA1_Stream1, ENABLE);                                          //关闭DMA  
    DMA_ITConfig(DMA1_Stream1,DMA_IT_TC,ENABLE);   //使能DMA1数据流1的传输完成中断  
    NVIC_InitStructure.NVIC_IRQChannel                   = DMA1_Stream1_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;  
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;  
    NVIC_Init(&NVIC_InitStructure);  
    USART_DMACmd(USART3,USART_DMAReq_Rx,ENABLE);     
}

这段代码主要是根据第25.3节中讲解的内容对DMA进行配置的。串口发送数据我们利用的是DMA数据流3通道4,存储器是我们定义的一个数组USART3_TX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。串口接收数据我们利用的是DMA数据流1通道4,存储器是我们定义的一个数组USART3_RX_BUF,外设是串口的数据寄存器USART_DR,表示方法为&USART3->DR。

代码清单2:DMA1_Stream3_IRQHandler函数

void DMA1_Stream3_IRQHandler(void)  //DMA发送数据流中断服务函数
{    
    if(DMA_GetITStatus(DMA1_Stream3,DMA_IT_TCIF3) != RESET)       
    {          
        DMA_ClearITPendingBit(DMA1_Stream3,DMA_IT_TCIF3);         
        DMA_Cmd(DMA1_Stream3,DISABLE); //关闭DMA          
        USART3_DMA_Tx_flag = 0; //发送完成    
    }       
}

该函数是DMA1数据流3中断服务函数,在函数里我们通过对DMA_IT_TCIF3标志位的判断,可以知道数据流3的数据是否传输完成,然后清除相应的标志位和关闭DMA传输。

代码清单3:DMA1_Stream1_IRQHandler函数

void DMA1_Stream1_IRQHandler(void) //DMA接收数据流中断服务函数
{    
    if(DMA_GetITStatus(DMA1_Stream1,DMA_IT_TCIF1) != RESET)       
    {          
        DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1); //清除标志位            
        DMA_Cmd(DMA1_Stream1,DISABLE); //关闭DMA      
    }   
}

该函数的功能和DMA1数据流3中断服务函数的功能是一样的。

代码清单4:USART3_IRQHandler函数

void USART3_IRQHandler(void)                  
{   
    if(USART_GetITStatus(USART3 , USART_IT_IDLE) != RESET)     
    {    
        USART_ReceiveData(USART3);//清除空闲中断标志    
        DMA_Cmd(DMA1_Stream1,DISABLE);//关闭DMA ,防止干扰      
        USART3_RX_STA = USART3_MAX_RX_LEN - DMA_GetCurrDataCounter(DMA1_Stream1);//获得接收到的字节数      
        DMA_SetCurrDataCounter(DMA1_Stream1,USART3_MAX_RX_LEN);     
        DMA_ClearITPendingBit(DMA1_Stream1,DMA_IT_TCIF1);//比f103多的一句         
        DMA_Cmd(DMA1_Stream1,ENABLE);// DMA 开启,等待数据。    
        USART3_RX_BUF[USART3_RX_STA&0X7FFF]='\0';    
        USART3_RX_STA|=0x8000;   //最高位置1,标记接收完成了                                                                                                   
    }  
    if(USART_GetFlagStatus(USART3,USART_FLAG_ORE) == SET)  // 检查 ORE 标志,防止开关总中断死机,放在接收中断前面  
    {    
        USART_ClearFlag(USART3,USART_FLAG_ORE);    
        USART_ReceiveData(USART3);  
    }
}

该函数是串口3的中断服务函数。在函数里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。然后在中断里依次做了以下事情:处理接收buff的数据;关闭串口接收DMA通道,防止后面接收到数据产生串扰;计算接收到的字节数存储在USART3_RX_STA变量中;重新设置DMA下次要接收的数据字节数;清除DMA1数据流1接收完成标志位;开启DMA通道,等待下一次的数据接收;置位标志位USART3_RX_STA,表示接收完成了。同时为了防止串口溢出错误,程序卡死在串口中断里,在中断服务函数里增加了溢出错误的处理。

代码清单5:主函数

int main(void)
{  
    GPIO_Configuration();  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2  
    USART_Configuration();  
    printf("start\r\n");  
    while (1)  
    {    
        if(USART3_RX_STA&0x8000)    
        {        
            //printf("USART3_Read:%s",USART3_RX_BUF);      
            printf("USART3_Read:%s",USART3_RX_BUF);      
            USART3_RX_STA=0;      
            memset(USART3_RX_BUF,0,sizeof(USART3_RX_BUF));    
        }  
    }
}

主函数的编写逻辑比较简单,首先是各类外设的初始化,包括GPIO初始化、NVIC中断初始化、串口初始化。然后在while循环里等待接收完成标志置位,将接收到的数据再发送到串口调试助手,并利用memset函数将存储数据的数组USART3_RX_BUF清零。

来源:中科芯MCU

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 12

DMA简介

DMA控制器的作用不仅在增强系统性能并减少处理器的中断生成,而且还针对32位MCU应用程序专门优化设计。DMA控制器为存储器到存储器,存储器到外设和外设到存储器的传输提供了7个通道。每个通道都支持外设的DMA请求映射到任意通道上。

1.png

图1. DMA控制器架构

DMAMUX简介

对于如何将外设的DMA请求映射到任意的数据流通道上,就需要使用到DMAMUX。DMAMUX针对每个外设都设计了独有的ID号,使用者只需要将此ID号写入对应的寄存器中并打开DMAMUX功能即可。DMAMUX的引入,使得DMA相较于传统DMA控制器变得更加灵活,使用者可以随意的分配7个通道的使用情况,不必再纠结与某个IP的DMA请求只能固定使用在某个或某几个通道上。

各IP对应ID号如下表:

表1. 各IP对应ID号列表

2.png

注:表格中“DMAMUX请求”为ID号;“来源”为各IP的DMA请求。

DMA功能解析

可编程数据宽度

DMA控制器的通道可支持传输不同数据宽度,byte/halfword/word。通过DMA_CxCTRL中的PWIDTH和MWIDTH位可以对源数据和目标数据的数据宽度进行编程,通常情况下需要设置PWIDTH和MWIDTH位相等,当PWIDTH不等于MWIDTH时,会依据PWIDTH/MWIDTH设定将资料对齐。

3.png

图2. PWIDTH:byte, MWIDTH:half-word

4.png

图3. PWIDTH:half-word, MWIDTH:word

配置DMAMUX

在M2P与P2M模式下,必须配置DMAMUX,否则DMA不会响应外设DMA请求。DMAMUX的作用是为外设的DMA请求复用通道,即任何一个外设的DMA请求可以映射到DMA1/DMA2的任意通道,这大大增加了DMA通道分配的灵活性。

配置DMAMUX比较减到,只需调用专门提供的两个接口函数即可:

5.png


DMA配置解析

以下对DMA的配置接口及流程进行说明。

函数接口

6.png表2. 通道配置函数列表

数据流配置

  • 设置外设地址(CxPADDR寄存器)

数据传输的初始外设地址,在传输过程中不可被改变。

  • 设置存储器地址(CxMADDR寄存器)

数据传输的初始内存地址,在传输过程中不可被改变。

  • 配置数据传输量(CxDTCNT寄存器)

可编程的传输数据长度最大为65535。在传输过程中,该传输数据量的值会逐渐递减。

  • 数据流配置(CxCTRL寄存器)

包含通道优先级,数据传输的方向、宽度、地址增量模式、循环模式和中断方式。

优先级(CHPL)

分为4个等级,最高优先级、高优先级、中等优先级和低优先级。

若有2个流优先级设定相同,则较低编号的流有较高的优先权。举例,流1优先于流2。

数据传输方向(DTD)

分为存储器到外设(M2P),外设到存储器(P2M)或存储器到存储器(M2M)传输。

在存储器到存储器传输模式下不允许使用循环模式、双缓冲模式和直接模式。

数据传输宽度(PWIDTH/MWIDTH)

根据实际使用情景,可配置宽度为byte、halfword、word。

地址增量模式(PINCM/MINCM)

当通道配置设定为增量模式时,下一笔传输的地址将是前一笔传输地址加上传输宽度(PWIDTH/MWIDTH)。

  • 循环模式(LM)

当流配置设定为循环模式时,在最后一次传输后CxDTCNT寄存器的内容会恢复成初始值。

  • 使能DMAMUX(MUXSEL寄存器的TBL_SEL位)

在非存储器到存储器(M2M)模式下时,需要使能DMAMUX功能,才能启动数据流响应外设的DMA请求。

  • 写入外设ID号(MUXCxCTRL寄存器的REQSEL)

在非存储器到存储器(M2M)模式下时,需要将外设的DMA请求ID号写入,才能启动数据流响应外设的DMA请求。

  • 打开数据流(CxCTRL寄存器的CHEN位)

配置流程

  • 打开DMA时钟;

  • 调用通道复位函数复位数据流;

  • 调用结构体初始化函数初始化通道配置结构体;

  • 调用初始化函数初始化通道;

  • 调用DMAMUX使能函数以及ID号写入函数配置DMAMUX相关内容;

  • 调用通道使能函数开启通道。
    PS. 案例介绍可点击文末”阅读原文”查看

来源:AT32 MCU 雅特力科技

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 145

页面

订阅 RSS - DMA