本篇文章主要介绍ADC模数转换器及DMA转运,这也是Stm32中比较重要的部分,值得大家学习。

ADC简介

  1. ADC(Analog-Digital Converter)模拟-数字转换器

  2. ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁

  3. 12位逐次逼近型ADC,1us转换时间

  4. 输入电压范围:0~3.3V,转换结果范围:0~4095

  5. 18个输入通道,可测量16个外部和2个内部信号源

  6. 规则组和注入组两个转换单元

  7. 模拟看门狗自动监测输入电压范围

  8. STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道

逐次逼近型ADC

image-20250204182838805

ADC框图

image-20250204183258498

ADC基本结构

image-20250204182934280

注意:规则组可以采集16路通道,但是只能保持一路通道采集信息,一般配合DMA转运进行使用;而注入组AD数据寄存器完全可以将它能采集到的4路通道信息同时保存。

转换模式

根据选择单次转换还是连续转换,扫描还是非扫描,有以下4种转换模式:

  1. 单次转换,非扫描模式
  2. 连续转换,非扫描模式
  3. 单次转换,扫描模式
  4. 连续转换,扫描模式

如果不扫描,则只检测第一个通道(指定的),如果扫描,则会按照指定的顺序对通道进行检测,在检测完成之后,会将EOC置1;如果选择的是单次转换,在一次转换完成后就停止,直至下一次发送检测指令,选择连续,则会一直检测下去。

当然,在扫描模式下,还有间断模式,详见STM32参考手册。

ADC触发源

下表是STM32C8T6中ADC1和ADC2用于规则通道的外部触发:

触发源 类型 EXTSEL[2:0]
TIM1_CC1事件 来自片上定时器的内部信号 000
TIM1_CC2事件 来自片上定时器的内部信号 001
TIM1_CC3事件 来自片上定时器的内部信号 010
TIM2_CC2事件 来自片上定时器的内部信号 011
TIM3_TRGO事件 来自片上定时器的内部信号 100
TIM4_CC4事件 来自片上定时器的内部信号 101
EXTI线11/TIM8_TRGO事件 外部引脚/来自片上定时器的内部信号 110
SWSTART 软件控制位 111

数据对齐

  1. 数据右对齐:

image-20250204203930347

  1. 数据左对齐:

image-20250204203811983

选择左对齐的好处:可以选择取出高八位,降低精度。

转换时间

  • AD转换的步骤:采样,保持,量化,编码

  • STM32 ADC的总转换时间为:$T_{CONV}$ = 采样时间 + 12.5个ADC周期

  • 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期;$T_{CONV}$​ = 1.5 + 12.5 = 14个ADC周期 = 1μs

校准

  • ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

  • 建议在每次上电后执行一次校准

  • 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期

ADC外围硬件电路设计

image-20250204210704123

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//AD多通道

void AD_Init(void)
{
//时钟配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

//ADC时钟分频设置,注意:最大14MHz,故选择6分频,也就是72/6=12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);

//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);

//ADC配置
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;//连续or单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描or非扫描
ADC_InitStructure.ADC_NbrOfChannel = 1;//扫描模式下用到?个通道
ADC_Init(ADC1, &ADC_InitStructure);

ADC_Cmd(ADC1, ENABLE);//开启ADC

//ADC校准
ADC_ResetCalibration(ADC1);//复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);//返回 复位校准 的状态
ADC_StartCalibration(ADC1);//开始校准
while (ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5);//指定通道
ADC_SoftwareStartConvCmd(ADC1, ENABLE);//软件触发转换
while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);//等待转换完成
return ADC_GetConversionValue(ADC1);//返回测得值并且会 自动 清除标志位
}

DMA简介

  1. DMA(Direct Memory Access)直接存储器存取

  2. DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

  3. 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)

  4. 每个通道都支持软件触发和特定的硬件触发

  5. STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

image-20250205012732409

DMA框图

image-20250205012752788

DMA基本结构

image-20250205012812533

DMA请求

image-20250205012834903

数据转运+DMA

image-20250205012903786

DMA转运一轮后必须 关闭 DMA后再开启。

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//DMA转运

void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//开启时钟,DMA是AHB的外设

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);//配置到通道1

DMA_Cmd(DMA1_Channel1, DISABLE);
}

void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);
DMA_Cmd(DMA1_Channel1, ENABLE);

//de
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}

ADC扫描模式+DMA

image-20250205012933008

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//ADC扫描模式+DMA

#include "stm32f10x.h" // Device header

uint16_t AD_Value[4];//SRAM 数组

void AD_Init(void)
{
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

//ADC时钟分频:12MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);

//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);

//ADC通道配置
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初始化
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;//使用通道数目
ADC_Init(ADC1, &ADC_InitStructure);

//DMA初始化
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设地址:ADC1
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度:半字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址自增:关闭
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//储存器地址
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);//选择通道1

DMA_Cmd(DMA1_Channel1, ENABLE);//开启DMA
ADC_DMACmd(ADC1, ENABLE);//开启ADC的DMA触发信号
ADC_Cmd(ADC1, ENABLE);//开启ADC

//ADC复位
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//触发ADC
}

END