STM32标准库ADC和DMA知识点总结-2

简介: STM32标准库ADC和DMA知识点总结

STM32标准库ADC和DMA知识点总结-1

https://developer.aliyun.com/article/1508389


二、DMA原理和应用

DMA简介:

存储器映像:

RAM:随机存取存储器(英语:Random Access Memory,缩写:RAM),也叫主存,是与CPU直接交换数据的内部存储器。

ROM:(只读内存(Read-Only Memory)简称)英文简称ROM。ROM所存数据,一般是装入整机前事先写好的,整机工作过程中只能读出,而不像随机存储器那样能快速地、方便地加以改写。

DMA框图:


DMA基本结构:

DMA请求:

数据宽度与对齐:简单来说就是高位补零或者取高位舍低位


数据转运+DMA:

ADC扫描模式+DMA:

(1)DMA数据转运(内存到内存)

DMA.c

#include "stm32f10x.h"                  // Device header
 
uint16_t MyDMA_Size;          //定义全局变量,用于记住Init函数的Size,供Transfer函数使用
 
/**
  * 函    数:DMA初始化
  * 参    数:AddrA 原数组的首地址
  * 参    数:AddrB 目的数组的首地址
  * 参    数:Size 转运的数据大小(转运次数)
  * 返 回 值:无
  */
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
  MyDMA_Size = Size;          //将Size写入到全局变量,记住参数Size
  
  /*开启时钟*/
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);            //开启DMA的时钟
  
  /*DMA初始化*/
  DMA_InitTypeDef DMA_InitStructure;                    //定义结构体变量
  DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;           //外设基地址,给定形参AddrA
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据宽度,选择字节
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;     //外设地址自增,选择使能
  DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;             //存储器基地址,给定形参AddrB
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;     //存储器数据宽度,选择字节
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;         //存储器地址自增,选择使能
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;            //数据传输方向,选择由外设到存储器
  DMA_InitStructure.DMA_BufferSize = Size;                //转运的数据大小(转运次数)
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;             //模式,选择正常模式
  DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;               //存储器到存储器,选择使能
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;         //优先级,选择中等
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);              //将结构体变量交给DMA_Init,配置DMA1的通道1
  
  /*DMA使能*/
  DMA_Cmd(DMA1_Channel1, DISABLE);  //这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
}
 
/**
  * 函    数:启动DMA数据转运
  * 参    数:无
  * 返 回 值:无
  */
void MyDMA_Transfer(void)
{
  DMA_Cmd(DMA1_Channel1, DISABLE);          //DMA失能,在写入传输计数器之前,需要DMA暂停工作
  DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);  //写入传输计数器,指定将要转运的次数
  DMA_Cmd(DMA1_Channel1, ENABLE);           //DMA使能,开始工作
  
  while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);  //等待DMA工作完成
  DMA_ClearFlag(DMA1_FLAG_TC1);           //清除工作完成标志位
}


DMA.h

#ifndef __MYDMA_H
#define __MYDMA_H
 
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size);
void MyDMA_Transfer(void);
 
#endif


main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
 
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};       //定义测试数组DataA,为数据源
uint8_t DataB[] = {0, 0, 0, 0};             //定义测试数组DataB,为数据目的地
 
int main(void)
{
  /*模块初始化*/
  OLED_Init();        //OLED初始化
  
  MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);  //DMA初始化,把源数组和目的数组的地址传入
  
  /*显示静态字符串*/
  OLED_ShowString(1, 1, "DataA");
  OLED_ShowString(3, 1, "DataB");
  
  /*显示数组的首地址*/
  OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
  OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
    
  while (1)
  {
    DataA[0] ++;    //变换测试数据
    DataA[1] ++;
    DataA[2] ++;
    DataA[3] ++;
    
    OLED_ShowHexNum(2, 1, DataA[0], 2);   //显示数组DataA
    OLED_ShowHexNum(2, 4, DataA[1], 2);
    OLED_ShowHexNum(2, 7, DataA[2], 2);
    OLED_ShowHexNum(2, 10, DataA[3], 2);
    OLED_ShowHexNum(4, 1, DataB[0], 2);   //显示数组DataB
    OLED_ShowHexNum(4, 4, DataB[1], 2);
    OLED_ShowHexNum(4, 7, DataB[2], 2);
    OLED_ShowHexNum(4, 10, DataB[3], 2);
    
    Delay_ms(1000);   //延时1s,观察转运前的现象
    
    MyDMA_Transfer(); //使用DMA转运数组,从DataA转运到DataB
    
    OLED_ShowHexNum(2, 1, DataA[0], 2);   //显示数组DataA
    OLED_ShowHexNum(2, 4, DataA[1], 2);
    OLED_ShowHexNum(2, 7, DataA[2], 2);
    OLED_ShowHexNum(2, 10, DataA[3], 2);
    OLED_ShowHexNum(4, 1, DataB[0], 2);   //显示数组DataB
    OLED_ShowHexNum(4, 4, DataB[1], 2);
    OLED_ShowHexNum(4, 7, DataB[2], 2);
    OLED_ShowHexNum(4, 10, DataB[3], 2);
 
    Delay_ms(1000);   //延时1s,观察转运后的现象
  }
}

(2)DMA+AD多同道(外设到内存)

面包板接线:

代码示例:

AD_DMA.c

#include "stm32f10x.h"                  // Device header
 
uint16_t AD_Value[4];         //定义用于存放AD转换结果的全局数组
 
/**
  * 函    数:AD初始化
  * 参    数:无
  * 返 回 值:无
  */
void AD_Init(void)
{
  /*开启时钟*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  //开启ADC1的时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //开启DMA1的时钟
  
  /*设置ADC时钟*/
  RCC_ADCCLKConfig(RCC_PCLK2_Div6);           //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
  
  /*GPIO初始化*/
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);          //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
  
  /*规则组通道配置*/
  ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
  ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1
  ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道2
  ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道3
  
  /*ADC初始化*/
  ADC_InitTypeDef ADC_InitStructure;                      //定义结构体变量
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;              //模式,选择独立模式,即单独使用ADC1
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;            //数据对齐,选择右对齐
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;     //外部触发,使用软件触发,不需要外部触发
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;              //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;                //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
  ADC_InitStructure.ADC_NbrOfChannel = 4;                   //通道数,为4,扫描规则组的前4个通道
  ADC_Init(ADC1, &ADC_InitStructure);                     //将结构体变量交给ADC_Init,配置ADC1
  
  /*DMA初始化*/
  DMA_InitTypeDef DMA_InitStructure;                      //定义结构体变量
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;       //外设基地址,给定形参AddrA
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;      //外设地址自增,选择失能,始终以ADC数据寄存器为源
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;          //存储器基地址,给定存放AD转换结果的全局数组AD_Value
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;     //存储器数据宽度,选择半字,与源数据宽度对应
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;           //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;              //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
  DMA_InitStructure.DMA_BufferSize = 4;                   //转运的数据大小(转运次数),与ADC通道数一致
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;               //模式,选择循环模式,与ADC的连续转换一致
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;           //优先级,选择中等
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);                //将结构体变量交给DMA_Init,配置DMA1的通道1
  
  /*DMA和ADC使能*/
  DMA_Cmd(DMA1_Channel1, ENABLE);             //DMA1的通道1使能
  ADC_DMACmd(ADC1, ENABLE);               //ADC1触发DMA1的信号使能
  ADC_Cmd(ADC1, ENABLE);                  //ADC1使能
  
  /*ADC校准*/
  ADC_ResetCalibration(ADC1);               //固定流程,内部有电路会自动执行校准
  while (ADC_GetResetCalibrationStatus(ADC1) == SET);
  ADC_StartCalibration(ADC1);
  while (ADC_GetCalibrationStatus(ADC1) == SET);
  
  /*ADC触发*/
  ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}

AD_DMA.h

#ifndef __AD_H
#define __AD_H
 
extern uint16_t AD_Value[4];
 
void AD_Init(void);
 
#endif


main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
 
int main(void)
{
  /*模块初始化*/
  OLED_Init();        //OLED初始化
  AD_Init();          //AD初始化
  
  /*显示静态字符串*/
  OLED_ShowString(1, 1, "AD0:");
  OLED_ShowString(2, 1, "AD1:");
  OLED_ShowString(3, 1, "AD2:");
  OLED_ShowString(4, 1, "AD3:");
  
  while (1)
  {
    OLED_ShowNum(1, 5, AD_Value[0], 4);   //显示转换结果第0个数据
    OLED_ShowNum(2, 5, AD_Value[1], 4);   //显示转换结果第1个数据
    OLED_ShowNum(3, 5, AD_Value[2], 4);   //显示转换结果第2个数据
    OLED_ShowNum(4, 5, AD_Value[3], 4);   //显示转换结果第3个数据
    
    Delay_ms(100);              //延时100ms,手动增加一些转换的间隔时间
  }
}

HAL库实验可看:STM32DMA原理和应用_stm32dma应用-CSDN博客

相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
32689 78
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17737 19
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36674 19
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24751 14
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36657 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29834 52

热门文章

最新文章

下一篇
开通oss服务