https://www.cnblogs.com/milton/p/15009112.html
使用STM32F103和STM32F401CCU6对双轴摇杆(两个电压通道)进行ADC采样并通过DMA读取数值
转换工作模式
工作模式由三个寄存器开关位控制, 因为名称有歧义, 容易理解模糊
连续模式开关
- 寄存器位置: ADC_CR2 的 CONT
- 这个开关控制了转换是单次转换(Single Conversion Mode)还是连续转换(Continuous Conversion Mode)
- CONT = 0, 单次转换(Single Conversion Mode), ADC只做一次采样转换. 这个状态时, 要么在 ADC_CR2 寄存器中设置 ADON 位(用于常规通道), 要么通过外部触发(用于常规或注入通道)
- CONT = 1, 连续转换(Continuous Conversion Mode), ADC在完成一次转换后就会开始下一次转换. 这个状态时, 要么ADC_CR2 寄存器中设置 ADON 位, 要么由外部触发
当对选中的通道的转换完成后:
- 如果转换的是常规通道:
- 转换完的数据存在 16-bit ADC_DR 寄存器
- EOC (End Of Conversion) 标志被置1
- 如果设置了 EOCIE , 会产生一个中断
- 如果转换的是注入通道:
- 转换完的数据存在 16-bit ADC_DRJ1 寄存器
- JEOC (End Of Conversion Injected) 标志被置1
- 如果设置了 JEOCIE , 会产生一个中断
如果是单次转换, ADC 就停止了; 如果是连续转换, 就会继续下一次转换.
扫描模式开关
- 寄存器位置: ADC_CR1 的 SCAN 位
- SCAN = 1 开启扫描模式, 用于扫描一组模拟通道, 通道定义于 ADC_SQRx(常规通道) 或 ADC_JSQR (注入通道), 对一组通道里面的每一个通道执行一次转换, 每次转换完一个通道后自动转换下一个通道当启用扫描模式时, DMA位必须置1 使用DMA控制器在每次 ADC_DR 更新后将转换数据传输到SRAM. 对于注入通道, 转换数据还是存储在 ADC_JDRx 寄存器.
- 如果 CONT = 0 (即单次转换), 在对这组的最后一个通道进行转换后自动停止
- 如果 CONT = 1 (即打开连续转换), 在对这组的最后一个通道进行转换后, 自动从这组的第一个通道开始下一轮转换
非连续模式开关
- 寄存器位置: ADC_CR1 的 DISCEN
- DISCEN = 1 开启非连续模式. 这时每次触发会转换 ADC_SQRx 中定义的转换序列其中的 n 个(n <= 8), 值 n 由 ADC_CR1 寄存器中的 DISCNUM[2:0] 这几位指定.
- 当外部触发转换时, 每次转换其中的 n 个通道, 最后一次转换的通道可能会小于n个
- 当全部通道转换完, 再次触发后会从头开始
读取ADC结果的几种方式
主动取值(Polling Method)
It’s the easiest way in code in order to perform an analog to digital conversion using the ADC on an analog input channel. However, it’s not an efficient way in all cases as it’s considered to be a blocking way of using the ADC. As in this way, we start the A/D conversion and wait for the ADC until it completes the conversion so the CPU can resume processing the main code.
中断模式
The interrupt method is an efficient way to do ADC conversion in a non-blocking manner, so the CPU can resume executing the main code routine until the ADC completes the conversion and fires an interrupt signal so the CPU can switch to the ISR context and save the conversion results for further processing.
However, when you’re dealing with multiple channels in a circular mode or so, you’ll have periodic interrupts from the ADC that are too much for the CPU to handle. This will introduce jitter injection and interrupt latency and all sorts of timing issues to the system. This can be avoided by using DMA.
DMA方式
Lastly, the DMA method is the most efficient way of converting multiple ADC channels at very high rates and still transfers the results to the memory without CPU intervention which is so cool and time-saving technique.
实际使用场景
单通道ADC
- 按需转换一次: 连续模式关, 扫描模式关, 非连续模式关, 每次通过拉取读结果, 或者通过ADC中断读结果
- 连续转换: 连续模式开, 扫描模式关, 非连续模式关, 通过ADC中断读结果
多通道ADC
- 连续模式关, 扫描模式开, 非连续模式关, 这样ADC的多个通道,按照配置的顺序依次转换一次后,就停止转换。 但是这种方式不能用中断的方式读取每个通道的结果数据, "Using interrupts with ADC Multi-Channel scan mode is just impossible as stated in the documentation of the STM32 microcontroller’s datasheet. Because the interrupt signal is fired at the end of the regular group conversion".
- 连续ADC转换:连续模式使能,扫描模式使能。这样ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。
采样周期
STM32F1
- ADC时钟最大为 14MHz
- ADC总转换时间 = 采样时间 + 12.5个ADC时钟周期(信号量转换时间)
- 采样时间由寄存器设定, 最低 1.5ADC 时钟周期, 最大 239.5ADC 时钟周期
一般情况, 如果是软件启动, 那么转换时间即是采样周期, 若通过定时器进行触发启动ADC, 则还需要加上定时器的相关时间.
STM32F4
- ADC时钟最大为 32MHz, 由PCLK2分频得到, 一般PCLK2与HCLK相同
- 如果HCLK走PLL倍频, 用最大的84MHz, 经过4分频, 则ADC时钟为21MHz
- 如果HCLK不走PLL, 用HSE的25MHz, 经过2分频, ADC时钟为12.5MHz
- 若要达到ADC的最大时钟32MHz, 需要令PCLK2或HCLK为64MHz, 经2分频得到
- ADC总转换时间 = 采样时间 + 信号量转换时间
- 信号量转换时间根据采样分辨率不同, 8bit=>11ADC周期, 10bit=>13ADC周期, 12bit=>15ADC周期
- 采样时间由寄存器设定,最低 3ADC 时钟周期, 最大 480ADC 时钟周期
STM32F103C8T6的代码实现
管脚与ADC的映射关系
- PA0:7 ADC1_IN0:7
- PB0 ADC1_IN8
- PB1 ADC1_IN9
实现两个通道电压采集到DMA
- 确定要采集的信号通道数量, 每个信号通道要保留的采样数, 比如下面的例子中是2个通道, 每个通道4个采样
- 根据上面的数量得到
ARRAYSIZE
, 声明用于DMA的内存变量__IO uint16_t ADCConvertedValue[ARRAYSIZE]
- 初始化时钟: ADC1, GPIOA, DMA1
- 初始化GPIOA用于采集的两个pin
- 初始化ADC1
- 初始化DMA1
代码
1 #include <stdio.h>
2 #include "timer.h"
3 #include "usart.h"
4
5 #define ARRAYSIZE 2*4
6 __IO uint16_t ADCConvertedValue[ARRAYSIZE];
7
8 void RCC_Configuration(void)
9 {
10 /* ADCCLK = PCLK2/4 */
11 RCC_ADCCLKConfig(RCC_PCLK2_Div4);
12 /* Enable peripheral clocks ------------------------------------------------*/
13 /* Enable DMA1 clock */
14 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
15 /* Enable ADC1 and GPIOC clock */
16 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
17 }
18
19 void GPIO_Configuration(void)
20 {
21 GPIO_InitTypeDef GPIO_InitStructure;
22 /* Configure PA.00 (ADC Channel0) as analog input -------------------------*/
23 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
24 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
25 GPIO_Init(GPIOA, &GPIO_InitStructure);
26 }
27
28 int main(void)
29 {
30 SystemInit();
31 Systick_Init();
32 USART_Configuration();
33
34 /* System clocks configuration ---------------------------------------------*/
35 RCC_Configuration();
36
37 /* GPIO configuration ------------------------------------------------------*/
38 GPIO_Configuration();
39
40 /* ADC1 configuration ------------------------------------------------------*/
41 ADC_InitTypeDef ADC_InitStructure;
42 ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
43 //We will convert multiple channels
44 ADC_InitStructure.ADC_ScanConvMode = ENABLE;
45 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
46 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
47 //right 12-bit data alignment in ADC data register
48 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
49 // Set it to the number of channels
50 ADC_InitStructure.ADC_NbrOfChannel = 2;
51 ADC_Init(ADC1, &ADC_InitStructure);
52
53 /* ADC1 regular channel0 configuration, rank decides the order in ADCConvertedValue, start from 1 */
54 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_41Cycles5);
55 /* ADC1 regular channel1 configuration */
56 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_41Cycles5);
57
58 /* Enable ADC1 DMA */
59 ADC_DMACmd(ADC1, ENABLE);
60
61 /* Enable ADC1 */
62 ADC_Cmd(ADC1, ENABLE);
63
64 /* Enable ADC1 reset calibration register */
65 ADC_ResetCalibration(ADC1);
66 /* Check the end of ADC1 reset calibration register */
67 while(ADC_GetResetCalibrationStatus(ADC1));
68
69 /* Start ADC1 calibration */
70 ADC_StartCalibration(ADC1);
71 /* Check the end of ADC1 calibration */
72 while(ADC_GetCalibrationStatus(ADC1));
73
74 /* Start ADC1 Software Conversion */
75 ADC_SoftwareStartConvCmd(ADC1, ENABLE);
76
77 /* DMA1 channel1 configuration ----------------------------------------------*/
78 DMA_InitTypeDef DMA_InitStructure;
79 DMA_DeInit(DMA1_Channel1);
80 // ADC1_DR_Address ((uint32_t)0x4001244C)
81 DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(ADC1->DR);
82 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCConvertedValue;
83 /* Direction:
84 DMA_DIR_PeripheralSRC:from peripheral,
85 DMA_DIR_PeripheralDST:to peripheral
86 */
87 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
88 /* Specifies the buffer size, in data unit, of the specified Stream.
89 The data unit is equal to the configuration set in DMA_PeripheralDataSize
90 or DMA_MemoryDataSize members depending in the transfer direction.
91 Set it to the number of channels
92 */
93 DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
94 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
95 // Specifies whether the memory address register should be incremented or not
96 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
97 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
98 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
99 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
100 // Priority among DMA channels
101 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
102 // From Memory to Memory
103 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
104 DMA_Init(DMA1_Channel1, &DMA_InitStructure);
105 /* Enable DMA1 channel1 */
106 DMA_Cmd(DMA1_Channel1, ENABLE);
107
108 while(1) {
109 for (u8 i = 0; i < ARRAYSIZE; i++) {
110 printf("%d ", *(ADCConvertedValue + i));
111 }
112 printf("\r\n");
113
114 Systick_Delay_ms(500);
115 }
116 }
117
118 void ADC1_IRQHandler(void)
119 {
120 ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
121 ADC_SoftwareStartConvCmd(ADC1,ENABLE);
122 }
STM32F401CCU6的代码实现
只有1个16通道ADC. One 12-bit analog-to-digital converter is embedded and shares up to 16 external channels, performing conversions in the single-shot or scan mode. In scan mode, automatic conversion is performed on a selected group of analog inputs. The ADC can be served by the DMA controller. An analog watchdog feature allows very precise monitoring of the converted voltage of one, some or all selected channels. An interrupt is generated when the converted voltage is outside the programmed thresholds.
To synchronize A/D conversion and timers, the ADCs could be triggered by any of TIM1, TIM2, TIM3, TIM4 or TIM5 timer.
管脚与ADC的映射关系
- PA0:7 ADC1_IN0:7
- PB0 ADC1_IN8
- PB1 ADC1_IN9
- PC0:5 ADC1_IN10:15
因为F401CCU6的PC口只有PC13,PC14,PC15, 所以可以用的ADC只有ADC1_IN0 - IN9
STM32F4的ADC1与DMA的映射
根据STM32F2/F4/F7的DMA参考手册, 这个系列的芯片中DMA1与DMA2各有8个Stream(Stream0 - Stream7), 分别对应着不同的外设, 其中ADC1对应的是DMA2的Stream0和Stream4, 在代码中必须使用这两个, 否则DMA不起作用
实现两个通道电压采集到DMA的代码
下面的代码, 使用PA0, PA1两个通道采集电压值, 并输出至URART1.
注意STM32F4当中没有Calibration, 官方的回答There is no hardware ADC calibration for our STM32F4, it is self-calibrated upon reset and once ADC is activated, However, I do not recommend to varies VDDA/ VREF+ voltage once ADC is activated, you should always keep them stable when it is active, else re-enable it again. For other STM32F1 as example, the calibration is able to compensate the Offset and you can do it from time to time when temperature change or VREF+ voltage Change.
1 #include <stdio.h>
2 #include "config.h"
3
4 #include "led.h"
5 #include "timer.h"
6 #include "uart.h"
7
8 #define ARRAYSIZE 2*4
9 __IO uint16_t ADCConvertedValue[ARRAYSIZE];
10
11 void RCC_Configuration(void)
12 {
13 /* Enable ADCx, DMA and GPIO clocks ****************************************/
14 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
15 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
16
17 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
18 RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
19 RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
20 }
21
22 void GPIO_Configuration(void)
23 {
24 /* Configure ADC1 Channel0,1 pin as analog input ******************************/
25 GPIO_InitTypeDef GPIO_InitStructure;
26 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
27 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
28 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
29 GPIO_Init(GPIOA, &GPIO_InitStructure);
30 }
31
32 int main(void)
33 {
34 Systick_Init();
35 USART1_Init();
36 LED_Init();
37
38 /* System clocks configuration ---------------------------------------------*/
39 RCC_Configuration();
40
41 /* GPIO configuration ------------------------------------------------------*/
42 GPIO_Configuration();
43
44 /* DMA2 Stream0 channel0 configuration **************************************/
45 DMA_InitTypeDef DMA_InitStructure;
46 DMA_InitStructure.DMA_Channel = DMA_Channel_0;
47 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
48 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCConvertedValue;
49 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
50 DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
51 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
52 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
53 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
54 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
55 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
56 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
57 DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
58 DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
59 DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
60 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
61 DMA_Init(DMA2_Stream0, &DMA_InitStructure);
62 DMA_Cmd(DMA2_Stream0, ENABLE);
63
64 /* ADC Common Init **********************************************************/
65 ADC_CommonInitTypeDef ADC_CommonInitStructure;
66 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
67 // 预分频4分频, ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
68 ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
69 // DMA使能 (DMA传输下要设置使能)
70 ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
71 //两个采样阶段之间的延迟x个时钟
72 ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
73 ADC_CommonInit(&ADC_CommonInitStructure);
74
75 /* ADC1 Init ****************************************************************/
76 ADC_InitTypeDef ADC_InitStructure;
77 ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
78 ADC_InitStructure.ADC_ScanConvMode = ENABLE;
79 ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
80 ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
81 //ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
82 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
83 ADC_InitStructure.ADC_NbrOfConversion = 2;
84 ADC_Init(ADC1, &ADC_InitStructure);
85
86 /* ADC1 regular channel0,1 configuration **************************************/
87 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_56Cycles);
88 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_56Cycles);
89
90 /* Enable DMA request after last transfer (Single-ADC mode) */
91 ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
92 /* Enable ADC1 DMA */
93 ADC_DMACmd(ADC1, ENABLE);
94 /* Enable ADC1 */
95 ADC_Cmd(ADC1, ENABLE);
96
97 ADC_SoftwareStartConv(ADC1);
98
99 while(1) {
100 LED_On();
101 for (u8 i = 0; i < ARRAYSIZE; i++) {
102 float a = (*(ADCConvertedValue + i) - 2048) * 512 /2048;
103 printf("% 5d ", (int)a);
104 }
105 printf("\r\n");
106 LED_Off();
107 Systick_Delay_ms(200);
108 }
109 }