DMA传输的几个例子

前言:本文章部分代码参考自野火的例程

本人使用的是野火家的指南者开发板,芯片型号是STM32f103VET6

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

本篇源代码在这里

1 DMA简介

1.1 DMA是什么

DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用 CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。

数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是 SRAM 或者是 FLASH。

DMA 控制器包含了 DMA1 和 DMA2,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,这里的通道

可以理解为传输数据的一种管道。要注意的是 DMA2 只存在于大容量的单片机中。(这里的大容量是指FLASH的大小,规定FLASH在256-512KB的视为大容量)

1.2 DMA请求映像表

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

1.3 DMA传输方式

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节(如R1,R2寄存器等),主要涉及三种情况的数据传输,但本质上是一样的,都是从内存中的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。三种情况的数据传输如下:

外设到内存(如:外部器件通过ADC,把转换完的数据放到数据寄存器里,再通过DMA发送到内存)

内存到外设(如:通过串口,把数据从内存发到串口数据寄存器,再往外发送)

内存到内存(如:内部 FLASH 通过DMA向内部 SRAM 复制数据)

1.4 DMA仲裁器

仲裁器根据通道请求的优先级来启动外设/存储器的访问。

优先权管理分2个阶段:

● 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:

─ 最高优先级

─ 高优先级

─ 中等优先级

─ 低优先级

● 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先。

1.5 DMA特性

● 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。

● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。

● 支持循环的缓冲器管理

● 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。

● 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。

● 可编程的数据传输数目:最大为65535

1.6 DMA相关寄存器

  • DMA_CNDTR,这是一个 32 位的寄存器,但是只有16位有效,一次最多只能传输 65535 个数据

  • DMA_CCRx ,外设数据宽度由PSIZE[1:0] 配置,可以是 8/16/32 位,存储器的数据宽度由 MSIZE[1:0] 配置,可以是 8/16/32 位;外设的地址指针由 PINC 配置,存储器的地址指针由MINC 配置

看地址指针要不要递增,只需看源地址和目标地址会不会增加。

如:外部器件通过ADC,把转换完的数据放到数据寄存器里,再通过DMA发送到内存。这里ADC数据寄存器地址不会增加,数据会随着传输的过程不断更新。而内存的地址要增加,不然数据会被覆盖。

如:通过串口,把数据从内存发到串口数据寄存器,再往外发送。这里内存的地址要增加,不然传输的永远是同样的数据。而串口数据寄存器地址不会增加,数据会随着传输的过程不断更新。

如:内部 FLASH 通过DMA向内部 SRAM 复制数据。此时地址指针都要递增。

TEIE、HTIE、TCIE三个位使能相应的中断。中断标志在DMA_ISR寄存器里。

1.7 DMA初始化结构体

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; //外设地址 
  uint32_t DMA_MemoryBaseAddr;     //存储器地址 
  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_M2M;                //存储器到存储器模式
}DMA_InitTypeDef;

2 DMA 存储器到存储器模式

2.1 定义在SRAM和FLASH的数据

#define BUFFER_SIZE 32

/* 定义aSRC_Const_Buffer数组作为DMA传输数据源
 * const关键字将aSRC_Const_Buffer数组变量定义为常量类型
 * 表示数据存储在内部的FLASH中
 */
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
                                    0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
                                    0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
                                    0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
                                    0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
                                    0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
                                    0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
                                    0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
                                    0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
/* 定义DMA传输目标存储器
 * 存储在内部的SRAM中                                                                        
 */
uint32_t aDST_Buffer[BUFFER_SIZE];    

2.2 DMA配置

void DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    // 开启DMA时钟
    RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
    // 源数据地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
    // 目标地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
    // 方向:外设到存储器(这里的外设是内部的FLASH)    
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    // 传输大小    
    DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
    // 外设(内部的FLASH)地址递增        
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
    // 内存地址递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    // 外设数据单位    
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    // 内存数据单位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;     
    // DMA模式,一次或者循环模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
    //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  
    // 优先级:高    
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    // 使能内存到内存的传输
    DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
    // 配置DMA通道           
    DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
    //清除DMA数据流传输完成标志位
    DMA_ClearFlag(DMA_FLAG_TC);
    // 使能DMA
    DMA_Cmd(DMA_CHANNEL,ENABLE);
}

2.3 判断数据源是否完全相等

/**
  * 判断指定长度的两个数据源是否完全相等,
  * 如果完全相等返回1,只要其中一对数据不相等返回0
  */
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength){
  while(BufferLength--){
    if(*pBuffer != *pBuffer1){
      return 0;
    }
    pBuffer++;
    pBuffer1++;
  }
  return 1;  
}

2.5 main函数

int main(void)
{
  /* 定义存放比较结果变量 */
  uint8_t TransferStatus;
  
  /* LED 端口初始化 */
  LED_GPIO_Config();
    
  /* 设置RGB彩色灯为紫色 */
  LED_PURPLE;  
  
  /* 简单延时函数 */
  Delay(0xFFFFFF);  
  
  /* DMA传输配置 */
  DMA_Config(); 
  
  /* 等待DMA传输完成 */
  while(DMA_GetFlagStatus(DMA_FLAG_TC)==RESET){}
  
  /* 比较源数据与传输后数据 */
  TransferStatus=Buffercmp(aSRC_Const_Buffer, aDST_Buffer, BUFFER_SIZE);
  
  /* 判断源数据与传输后数据比较结果*/
  if(TransferStatus==0)  {
    /* 源数据与传输后数据不相等时RGB彩色灯显示红色 */
    LED_RED;
  }else{ 
    /* 源数据与传输后数据相等时RGB彩色灯显示蓝色 */
    LED_BLUE;
  }

  while (1){}
}

2.6 实验现象

紫灯先亮,如果传输正确,则亮蓝灯,否则亮红灯

3 DMA存储器到外设模式

3.1 DMA配置

// 串口工作参数宏定义
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

// 串口对应的DMA请求通道
#define  USART_TX_DMA_CHANNEL     DMA1_Channel4
// 外设寄存器地址
#define  USART_DR_ADDRESS        (USART1_BASE+0x04)
// 一次发送的数据量
#define  SENDBUFF_SIZE            5000

uint8_t SendBuff[SENDBUFF_SIZE];
void USARTx_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    
    // 开启DMA时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    // 设置DMA目标地址:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
    // 内存地址(要传输的变量的指针)
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
    // 方向:从内存到外设    
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    // 传输大小    
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
    // 外设地址不增        
    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模式,一次或者循环模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
    //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;    
    // 优先级:中    
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
    // 禁止内存到内存的传输
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    // 配置DMA通道           
    DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);        
    // 使能DMA
    DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}

3.2 main函数

int main(void)
{
  uint16_t i;
  /* 初始化USART */
  USART_Config(); 

  /* 配置使用DMA模式 */
  USARTx_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();

  /*填充将要发送的数据*/
  for(i=0;i<SENDBUFF_SIZE;i++)
  {
    SendBuff[i]     = 'P';
    
  }

  /*为演示DMA持续运行而CPU还能处理其它事情,持续使用DMA发送数据,量非常大,
  *长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,
  *或把DMA配置中的循环模式改为单次模式*/        
  
  /* USART1 向 DMA发出TX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);

  /* 此时CPU是空闲的,可以干其他的事情 */  
  //例如同时控制LED
  while(1)
  {
    LED1_TOGGLE;
    Delay(0xFFFFF);
  }
}

3.3 实验现象

CPU一直在运行着延时函数(即CPU处于忙状态),同时通过DMA,把内存的数据传输到串口数据寄存器发送出去

4 DMA 外设到存储器

4.1 编程要点

  • ADC的GPIO初始化

  • 配置ADC工作模式

单通道连续转换

转换结果右对齐

软件触发ADC转换

  • 配置DMA模式

外设到存储器模式;

外设地址是ADC的数据寄存器,存储器地址是存储在SRAM的变量的地址;

外设地址不增(寄存器地址不变),存储器地址固定(传输的永远是最新转换完的ADC数据)

外设数据大小为2bytes(规则通道的转换结果放在ADC->DR的0~15bit)

内存数据大小也为2bytes

配置为循环传输模式

4.2 实验现象

ADC转换完的数据保存在ADC->DR,通过DMA传输到SRAM上(可看做赋值给某个变量),然后程序读取变量的值

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用Keil uVision4来完成STM32 DMA传输ADC的示例代码: 首先,确保你已经正确配置了STM32的ADC和DMA模块,并且已经创建了工程和相应的源文件。 在主函数中,添加以下代码: ```c #include <stm32f10x.h> // 定义缓冲区大小 #define BUFFER_SIZE 100 // 定义用于存储ADC采样数据的缓冲区 uint16_t adc_buffer[BUFFER_SIZE]; int main(void) { // 初始化ADC ADC_InitTypeDef ADC_InitStruct; ADC_StructInit(&ADC_InitStruct); ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_Init(ADC1, &ADC_InitStruct); ADC_Cmd(ADC1, ENABLE); // 配置DMA DMA_InitTypeDef DMA_InitStruct; DMA_StructInit(&DMA_InitStruct); DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR); DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adc_buffer; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStruct); // 使能DMA通道 DMA_Cmd(DMA1_Channel1, ENABLE); // 配置ADC和DMA的关系 ADC_DMACmd(ADC1, ENABLE); // 启动ADC转换 ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (1) { // 在这里可以对采样数据进行处理 // ... } } ``` 上述代码中,首先初始化了ADC模块和DMA模块,并配置了相应的参数。然后启动了ADC转换和DMA传输。在主循环中,可以对采样数据进行处理。 请注意,上述代码仅为示例,具体的配置和参数设置需要根据实际情况进行调整。在实际使用时,请参考STM32的官方文档和相关例程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值