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博客

相关文章
|
17天前
使用STM32F103标准库实现定时器控制LED点亮和关闭
通过这篇博客,我们学习了如何使用STM32F103标准库,通过定时器来控制LED的点亮和关闭。我们配置了定时器中断,并在中断处理函数中实现了LED状态的切换。这是一个基础且实用的例子,适合初学者了解STM32定时器和中断的使用。 希望这篇博客对你有所帮助。如果有任何问题或建议,欢迎在评论区留言。
63 2
|
17天前
|
IDE 开发工具
使用STM32F103标准库实现自定义键盘
通过本文,我们学习了如何使用STM32F103标准库实现一个简单的自定义键盘。我们首先初始化了GPIO引脚,然后实现了一个扫描函数来检测按键状态。这个项目不仅能够帮助我们理解STM32的GPIO配置和按键扫描原理,还可以作为进一步学习中断处理和低功耗设计的基础。希望本文对你有所帮助,祝你在嵌入式开发的道路上不断进步!
64 4
|
17天前
|
存储 数据采集 数据安全/隐私保护
使用STM32F103读取TF卡并模拟U盘:使用标准库实现
通过以上步骤,你可以实现用STM32F103将TF卡内容变成U盘进行读取。这种功能在数据采集、便携式存储设备等应用中非常有用。如果你有更多的需求,可以进一步扩展此项目,例如添加文件管理功能、加密存储等。希望这篇博客能帮到你,如果有任何问题,欢迎在评论区留言讨论!
18 1
|
17天前
|
传感器
【经典案例】STM32F407使用HAL库配置I2C详解
STM32F407是一个强大的微控制器,广泛应用于嵌入式系统中。在许多应用中,我们需要使用I2C总线来与传感器、EEPROM、显示屏等外设进行通信。本文将详细介绍如何使用STM32 HAL库来配置和使用I2C接口。
30 2
|
17天前
|
开发者
【经典案例】使用HAL库配置STM32F407的SPI外设
在嵌入式系统开发中,STM32F407是一款广泛应用的微控制器,而SPI(Serial Peripheral Interface)是一种常用的通信接口。本文将详细介绍如何使用STM32的硬件抽象层(HAL)库配置STM32F407的SPI外设,并提供完整的代码示例。
42 1
|
1月前
【STM32】基于HAL库的360度编码器、摇杆代码编写
【STM32】基于HAL库的360度编码器、摇杆代码编写
|
1月前
|
传感器 算法
【STM32】I2C练习,HAL库读取MPU6050角度陀螺仪
【STM32】I2C练习,HAL库读取MPU6050角度陀螺仪
|
1月前
|
传感器
|
1月前
|
传感器 存储 缓存