STM32 ADC DMA操作详解
本内容基于江协科技STM32视频学习之后整理而得。
文章目录
1. ADC模拟-数字转换器
1.1 ADC模拟-数字转换器
1.2 逐次逼近型ADC
1.3 ADC框图

1.4 ADC基本结构
来自于RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的;
1.5 输入通道
1.6 规则组的转换模式
在ADC初始化的结构体里,会有两个参数:参1是选择单次转换还是连续转换,参2是选择扫描模式还是非扫描模式。
1.6.1 单次转换,非扫描模式
1.6.2 连续转换,非扫描模式
它在一次转换结束后,不会停止,而是立刻开始下一轮的转换,然后一直持续下去。因此只需最开始触发一次,之后就可以一直转换。优点是开始转换之后不需要等待一段时间,想要读AD值的时候,直接从数据寄存器取就是了。
1.6.3 单次转换,扫描模式
每触发一次,转换结束后,就会停下来,下次转换就得再触发才能开始。初始化结构体中还会有个参数:通道数目,若为7,就是在每次触发之后,依次对前7个位置进行AD转换,转换结果都放在数据寄存器里。为了防止数据被覆盖,就需要用DMA及时将数据挪走。7个通道转换完成之后,产生EOC信号,转换结束。然后再触发下一次,就又开始新一轮的转换。
1.6.4 连续转换,扫描模式
一次转换完成后,立刻开始下一次的转换。
在扫描模式的情况下,还有一种模式:间断模式,在扫描的过程中,每隔几个转换,就暂停一次,需要再次触发,才能继续。
1.7 触发控制
类型:外部引脚/来自片上定时器的内部信号,具体是引脚还是定时器,需要用AFIO重映射来确定。
软件控制位:软件触发
触发信号的选择可以通过设置右边的寄存器来完成。也可以使用库函数实现。
1.8 数据对齐
ADC是12位的,其转换结果就是一个12位的数据。但数据寄存器是16位的,所以存在数据对齐的问题
1.9 转换时间
1.10 校准
1.11 硬件电路
2. AD库函数及代码
2.1 AD库函数
// 配置ADCCLK分频器
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
// DeInit恢复缺省配置、Init初始化、StructInit结构体初始化
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
// 给ADC上电的,即开关控制
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 开启DMA输出信号
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// 中断输出控制,用于控制某个中断,能不能通往NVIC
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
// 复位校准、获取复位校准状态
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
// 开始校准、获取开始校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
// ADC软件开始转换控制,用于软件触发的函数
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC获取软件开始转换状态
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
// 判断转换是否结束。获取标志位状态,参数给EOC的标志位,判断EOC标志位是不是置1了
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 配置间断模式,函数1:每隔几个通道间断一次;函数2:是不是启用间断模式
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC规则组通道配置,给序列的每个位置填写指定的通道
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
// ADC 外部触发转换控制,就是是否允许外部触发转换
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
// ADC换取转换值。就是获取AD转换的数据寄存器,读取转换结果使用该函数
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// ADC获取双模式转换值,是ADC模式读取转换结果的函数
uint32_t ADC_GetDualModeConversionValue(void);
// 对ADC注入组进行配置
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
// 对模拟看门狗进行配置,函数1:是否启动看门狗;函数2:配置高低阈值;函数3:配置看门的通道
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
// ADC温度传感器、内部参考电压控制,用于开启内部的两个通道
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
// 获取中断状态、清除中断挂起位
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
2.2 7-1AD单通道代码
2.2.1 硬件电路
实现功能:接一个电位器,即滑动变阻器,用该电位器产生一个0~3.3V连续变化的模拟电压信号,接到STM32的PA0口上,之后用STM32内部的ADC读取电压数据,显示在屏幕上。屏幕上第一行显示的是AD转换后的原始数据,第二行是经过处理后实际的电压值。往左拧,AD值减小,电压值也减小,AD值最小为0,对应的电压就是0V,往右拧,AD值变大,对应电压值也变大。STM32的ADC是12位的,所以AD结果最大值是4095(2^12 – 1),对应的电压是3.3V。
2.2.2 代码流程
- 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
- 配置GPIO,配置为模拟输入模式
- 配置多路开关,把左边的通道接入到右边的规则组列表里
- 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
- 开关控制,调用ADC_Cmd函数,开启ADC
- 想要软件触发转换,有函数可以触发
2.2.3 代码
AD.c代码:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
/*
1. 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
2. 配置GPIO,配置为模拟输入模式
3. 配置多路开关,把左边的通道接入到右边的规则组列表里
4. 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
5. 开关控制,调用ADC_Cmd函数,开启ADC
6. 想要软件触发转换,有函数可以触发
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 6分频=12MHz
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;// 模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 采样时间是55.5个ADCCLK的周期
// 在规则组序列1的位置写入通道0,
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐:右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 触发源:软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;// 单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
// 复位校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
// 开始校准、获取开始校准状态
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(void)
{
// 软件触发
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
// 判断转换结束
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET); // 55.5 + 12.5 = 68个周期,ADCCLK = 12MHz,1/12M*68 = 5.6us,等待5.6us
// ADC获取转换值
return ADC_GetConversionValue(ADC1);
}
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue;
float Voltage;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "ADValue:");
OLED_ShowString(2, 1, "Voltage:0.00V");
while(1)
{
ADValue = AD_GetValue();
Voltage = (float)ADValue / 4095 * 3.3;
OLED_ShowNum(1, 9, ADValue, 4);
OLED_ShowNum(2, 9, Voltage, 1);// 显示整数部分
OLED_ShowNum(2, 11, (uint16_t)(Voltage * 100) % 100, 2);
Delay_ms(100);
}
}
2.3 7-2AD多通道代码
2.3.1 硬件电路
接了三个传感器:光敏电阻、热敏电阻、反射式红外传感器,把它们的AO(模拟电压输出端)分别接在了A1、A2、A3引脚,加上原来的电位器,总共四个输出通道,然后测出来的4个AD数据分别显示在屏幕上,AD0:电位器,往左拧,减小,往右拧增大;AD1:光敏电阻,遮挡时电阻变大,下拉作用变弱,输出电压变大,AD值增大;AD2:热敏电阻,用手热一下,温度升高,阻值变小,输出电压变小,AD值减小;AD3:反射式红外传感器,手靠近,有反光,AD值减小。
2.3.2 硬件运行结果
2.3.3 代码流程
- 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
- 配置GPIO,配置为模拟输入模式
- 配置多路开关,把左边的通道接入到右边的规则组列表里
- 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
- 开关控制,调用ADC_Cmd函数,开启ADC
- 想要软件触发转换,有函数可以触发
2.3.4 代码
- AD.c代码:
#include "stm32f10x.h" // Device header
void AD_Init(void)
{
/*
1. 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
2. 配置GPIO,配置为模拟输入模式
3. 配置多路开关,把左边的通道接入到右边的规则组列表里
4. 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
5. 开关控制,调用ADC_Cmd函数,开启ADC
6. 想要软件触发转换,有函数可以触发
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 6分频
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);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐:右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 触发源:软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;// 单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
// 复位校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
// 开始校准、获取开始校准状态
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
/*
调用该函数时,只要先指定通道,
返回值就是我们指定通道的结果.
通道为 0、1、2、3
*/
// 采样时间是55.5个ADCCLK的周期
// 通道为参数指定的通道
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
// 软件触发
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
// 判断转换结束
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET); // 等待5.6us
// ADC换取转换值
return ADC_GetConversionValue(ADC1);
}
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0, AD1, AD2, AD3;
float Voltage;
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while(1)
{
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);
OLED_ShowNum(1, 5, AD0, 4);
OLED_ShowNum(2, 5, AD1, 4);
OLED_ShowNum(3, 5, AD2, 4);
OLED_ShowNum(4, 5, AD3, 4);
Delay_ms(100);
}
}
3. DMA直接存储器存取
3.1 DMA
3.2 存储器映像
类型 | 起始地址 | 存储器 | 用途 |
---|---|---|---|
ROM | 0x0800 0000 | 程序存储器Flash | 存储C语言编译后的程序代码和常量· |
0x1FFF F000 | 系统存储器 | 存储BootLoader,用于串口下载 | |
0x1FFF F800 | 选项字节 | 存储一些独立于程序代码的配置参数 | |
RAM | 0x2000 0000 | 运行内存SRAM | 存储运行过程中的临时变量 |
0x4000 0000 | 外设寄存器 | 存储各个外设的配置参数 | |
0xE000 0000 | 内核外设寄存器 | 存储内核各个外设的配置参数 |
因此,计算机的核心关键部分就是CPU和存储器
3.3 DMA框图
主要包括:CPU和存储器
因此,外设就是寄存器,寄存器就是存储器。因此,DMA转运就可以归结为一类问题,就是从某个地址取内容,再放到另一个地址去。
3.4 DMA基本结构
若要进行存储器到存储器的转运,那就需要把其中一个存储器的地址,放在外设的这个站点。
M2M为0,就是硬件触发,硬件触发源可以选择ADC、串口、定时器,使用硬件触发的转运,一般都是与外设有关的转运。转运需要一定的时机,如ADC转换完成、串口收到数据、定时时间到等,在硬件达到这些时机时,传一个信号过来,来触发DMA进行转运。
3.5 DMA请求(触发)
默认优先级是通道号越小,优先级越高。也可以在程序中配置优先级。
3.6 数据宽度与对齐
3.7 数据转运+DMA
3.8 ADC扫描模式+DMA
4. DMA库函数及代码
4.1 DMA库函数
// ADC1_BASE是ADC1的基地址,基地址就是起始地址,即4001 2400
#define ADC1 ((ADC_TypeDef *) ADC1_BASE)
#define ADC1_BASE (APB2PERIPH_BASE + 0x2400)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
typedef struct
{
__IO uint32_t SR;
__IO uint32_t CR1;
__IO uint32_t CR2;
__IO uint32_t SMPR1;
__IO uint32_t SMPR2;
__IO uint32_t JOFR1;
__IO uint32_t JOFR2;
__IO uint32_t JOFR3;
__IO uint32_t JOFR4;
__IO uint32_t HTR;
__IO uint32_t LTR;
__IO uint32_t SQR1;
__IO uint32_t SQR2;
__IO uint32_t SQR3;
__IO uint32_t JSQR;
__IO uint32_t JDR1;
__IO uint32_t JDR2;
__IO uint32_t JDR3;
__IO uint32_t JDR4;
__IO uint32_t DR;
} ADC_TypeDef;
ADC1->DR
// ADC1是结构体指针,指向的是ADC1外设的起始地址,
// 访问结构体成员,相当于是加一个地址偏移。
// 起始地址 + 偏移,就是指定的寄存器
// 恢复缺省配置
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
// 初始化
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
// 结构体初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
// 使能
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
// 中断输出使能
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
// DMA设置当前数据寄存器,给传输计数器写数据的
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
// DMA获取当前数据寄存器,返回传输计数器的值
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
// 获取标志位状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
// 清除标志位
void DMA_ClearFlag(uint32_t DMAy_FLAG);
// 获取中断状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
// 清除中断挂起位
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
4.2 8-1DMA数据转运
4.2.1 硬件电路
使用DMA进行存储器到存储器的数据转运,把一个数组(源数组)的数据转运到另一个数组(目的数组)中。采用软件触发。
在OLED中显示源数组DataA 、地址及数据,目的数组DataB、地址及数据。
4.2.2 代码流程
- RCC开启DMA的时钟(AHB时钟)
- 调用DMA_Init,初始化参数:外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源
- 开关控制,DMA_Cmd
- 如果是硬件触发,在对应的外设调用一下xxx_DMACmd,开启一下触发信号的输出
- 如果需要DMA的中断,调用DMA_ITConfig,开启中断输出,再在NVIC里,配置相应的中断通道,写中断函数
- 如果转运完成,传输计数器清0了,若再想给传输计数器赋值的话,就DMA失能、写传输计数器、DMA使能。
4.2.3 代码
- MyDMA.c代码:
#include "stm32f10x.h" // Device header
uint16_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint32_t Size)
{
/*
1. RCC开启DMA的时钟
2. 调用DMA_Init,初始化参数:外设和存储器站点的
起始地址、数据宽度、地址是否自增、方向、
传输计数器、是否需要自动重装、选择触发源
3. 开关控制,DMA_Cmd
4. 如果是硬件触发,在对应的外设调用一下xxx_DMACmd,
开启一下触发信号的输出
5. 如果需要DMA的中断,调用DMA_ITConfig,开启中断输出,
再在NVIC里,配置相应的中断通道,写中断函数
6. 如果转运完成,传输计数器清0了,若再想给传输计数器赋值的话,
就DMA失能、写传输计数器、DMA使能。
*/
// DMA是AHB总线的设备,需要开启AHB时钟
MyDMA_Size = Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; // 外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设站点的数据宽度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable ; // 外设站点的是否自增
DMA_InitStructure.DMA_MemoryBaseAddr = 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转运有三个条件:
1. 传输计数器大于0
2. 触发源有触发信号
3. DMA使能
*/
DMA_Cmd(DMA1_Channel1, DISABLE);
}
// DMA传输函数,调用依次这个函数,就再次启动一次DMA转运
void MyDMA_Transfer(void)
{
// 重新给传输计数器赋值,先使DMA失能
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 等待转运完成
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
- main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
/*
uint8_t bb = 0x66; // 存储在SRAM中
const uint8_t aa = 0x66;
存储在Flash中,const定义的变量是常量,值不能更改
Flash是只读不能写入,因此const与Flash对应
*/
uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};// 源端数组
uint8_t DataB[] = {0, 0, 0, 0};// 目标数组
int main(void)
{
OLED_Init();
// 把DataA数组的数据转运到DataB里
MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
OLED_ShowString(1, 1, "DataA");
OLED_ShowString(3, 1, "DataB");
OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);// 显示DataA的地址
OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
OLED_ShowHexNum(2, 1, DataA[0], 2);
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);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
while(1)
{
DataA[0] ++;
DataA[1] ++;
DataA[2] ++;
DataA[3] ++;
OLED_ShowHexNum(2, 1, DataA[0], 2);
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);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowHexNum(2, 1, DataA[0], 2);
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);
OLED_ShowHexNum(4, 4, DataB[1], 2);
OLED_ShowHexNum(4, 7, DataB[2], 2);
OLED_ShowHexNum(4, 10, DataB[3], 2);
Delay_ms(1000);
}
}
4.3 8-2DMA+AD多通道
4.3.1 硬件电路
使用ADC的扫描模式来实现多通道采集,然后使用DMA来进行数据转运,AD转换的数据就会自动到定义的数组里,然后用OLED显示一下。
4.3.2 代码流程
- 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
- 配置GPIO,配置为模拟输入模式
- 配置多路开关,把左边的通道接入到右边的规则组列表里
- 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
- 开关控制,调用ADC_Cmd函数,开启ADC
- 想要软件触发转换,有函数可以触发
4.3.3 代码
- AD.c代码:
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
/*
1. 开启RCC时钟,包括ADC和GPIO时钟,ADCCLK的分频器
2. 配置GPIO,配置为模拟输入模式
3. 配置多路开关,把左边的通道接入到右边的规则组列表里
4. 配置ADC转换器,采用库函数结构体:包括AD转换器和AD数据寄存器
5. 开关控制,调用ADC_Cmd函数,开启ADC
6. 想要软件触发转换,有函数可以触发
*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 6分频
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);
// 4个通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
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_InitStructure.ADC_NbrOfChannel = 4; // 4个通道
ADC_Init(ADC1,&ADC_InitStructure);
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // 外设站点的起始地址 ADC
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设站点的数据宽度
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable ; // 外设站点的是否自增,否
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; // 存储器站点的起始地址 SRAM
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 存储器站点的数据宽度
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储器站点的是否自增,是
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输方向
DMA_InitStructure.DMA_BufferSize = 4; // 缓存区大小,即传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 传输模式,就是是否使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 选择是否是存储器到存储器,即选择硬件触发还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);// 开启DMA触发信号
ADC_Cmd(ADC1, ENABLE);
// 复位校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
// 开始校准、获取开始校准状态
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
/*
void AD_GetValue(void)
{
调用该函数,ADC开始转换,DMA也同步进行转运,
AD转换结果,依次放在这上面的AD_Value数组里,
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1,4);
DMA_Cmd(DMA1_Channel1, ENABLE);
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
*/
- main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
int main(void)
{
OLED_Init();
AD_Init();
OLED_ShowString(1, 1, "AD0:");
OLED_ShowString(2, 1, "AD1:");
OLED_ShowString(3, 1, "AD2:");
OLED_ShowString(4, 1, "AD3:");
while(1)
{
// AD_GetValue();
OLED_ShowNum(1, 5, AD_Value[0], 4);
OLED_ShowNum(2, 5, AD_Value[1], 4);
OLED_ShowNum(3, 5, AD_Value[2], 4);
OLED_ShowNum(4, 5, AD_Value[3], 4);
Delay_ms(100);
}
}
作者:luckyme_