使用STM32F103和STM32F401CCU6对双轴摇杆(两个电压通道)进行ADC采样并通过DMA读取数值
STM32 ADC(模数转换)工作模式
单次转换模式
In Single Conversion mode, the ADC does one conversion. This mode is started either by setting the ADON bit in the ADC_CR2 register (for a regular channel only) or by an external trigger (for a regular or injected channel), while the CONT bit is 0. Once the conversion of the selected channel is complete:
If a regular channel was converted:
– The converted data is stored in the 16-bit ADC_DR register
– The EOC (End Of Conversion) flag is set
– and an interrupt is generated if the EOCIE is set.
If an injected channel was converted:
– The converted data is stored in the 16-bit ADC_DRJ1 register
– The JEOC (End Of Conversion Injected) flag is set
– and an interrupt is generated if the JEOCIE bit is set.
The ADC is then stopped.
连续转换模式
In continuous conversion mode, ADC starts another conversion as soon as it finishes one. This mode is started either by an external trigger or by setting the ADON bit in the ADC_CR2 register, while the CONT bit is 1. After each conversion:
If a regular channel was converted:
– The converted data is stored in the 16-bit ADC_DR register
– The EOC (End Of Conversion) flag is set
– An interrupt is generated if the EOCIE is set.
If an injected channel was converted:
– The converted data is stored in the 16-bit ADC_DRJ1 register
– The JEOC (End Of Conversion Injected) flag is set
– An interrupt is generated if the JEOCIE bit is set.
扫描模式
This mode is used to scan a group of analog channels. A single conversion is performed for each channel of the group. After each end of conversion, the next channel of the group is converted automatically. If the CONT bit is set, conversion does not stop at the last selected group channel but continues again from the first selected group channel.
When using scan mode, DMA bit must be set and the direct memory access controller is used to transfer the converted data of regular group channels to SRAM after each update of the ADC_DR register. The injected channel converted data is always stored in the ADC_JDRx registers.
非连续模式
This mode is enabled by setting the DISCEN bit in the ADC_CR1 register. It can be used to convert a short sequence of n conversions (n <=8) which is a part of the sequence of conversions selected in the ADC_SQRx registers. The value of n is specified by writing to the DISCNUM[2:0] bits in the ADC_CR1 register.
When an external trigger occurs, it starts the next n conversions selected in the ADC_SQRx registers until all the conversions in the sequence are done. The total sequence length is defined by the L[3:0] bits in the ADC_SQR1 register.
读取ADC结果的几种方式
The 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.
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
代码
#include
#include "timer.h"
#include "usart.h"
#define ARRAYSIZE 2*4
__IO uint16_t ADCConvertedValue[ARRAYSIZE];
void RCC_Configuration(void)
{
/* ADCCLK = PCLK2/4 */
RCC_ADCCLKConfig(RCC_PCLK2_Div4);
/* Enable peripheral clocks ------------------------------------------------*/
/* Enable DMA1 clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* Configure PA.00 (ADC Channel0) as analog input -------------------------*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
SystemInit();
Systick_Init();
USART_Configuration();
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration();
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//We will convert multiple channels
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//right 12-bit data alignment in ADC data register
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
// Set it to the number of channels
ADC_InitStructure.ADC_NbrOfChannel = 2;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel0 configuration, rank decides the order in ADCConvertedValue, start from 1 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_41Cycles5);
/* ADC1 regular channel1 configuration */
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_41Cycles5);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
/* Enable ADC1 reset calibration register */
ADC_ResetCalibration(ADC1);
/* Check the end of ADC1 reset calibration register */
while(ADC_GetResetCalibrationStatus(ADC1));
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while(ADC_GetCalibrationStatus(ADC1));
/* Start ADC1 Software Conversion */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
/* DMA1 channel1 configuration ----------------------------------------------*/
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Channel1);
// ADC1_DR_Address ((uint32_t)0x4001244C)
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(ADC1->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCConvertedValue;
/* Direction:
DMA_DIR_PeripheralSRC:from peripheral,
DMA_DIR_PeripheralDST:to peripheral
*/
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
/* Specifies the buffer size, in data unit, of the specified Stream.
The data unit is equal to the configuration set in DMA_PeripheralDataSize
or DMA_MemoryDataSize members depending in the transfer direction.
Set it to the number of channels
*/
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// Specifies whether the memory address register should be incremented or not
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// Priority among DMA channels
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
// From Memory to Memory
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
/* Enable DMA1 channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
while(1) {
for (u8 i = 0; i < ARRAYSIZE; i++) {
printf("%d ", *(ADCConvertedValue + i));
}
printf("\r\n");
Systick_Delay_ms(500);
}
}
void ADC1_IRQHandler(void)
{
ADC_ClearITPendingBit(ADC1, ADC_IT_EOC);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
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的代码
#include
#include "config.h"
#include "led.h"
#include "timer.h"
#include "uart.h"
#define ARRAYSIZE 2*4
__IO uint16_t ADCConvertedValue[ARRAYSIZE];
void RCC_Configuration(void)
{
/* Enable ADCx, DMA and GPIO clocks ****************************************/
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC1, DISABLE);
}
void GPIO_Configuration(void)
{
/* Configure ADC1 Channel0,1 pin as analog input ******************************/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
int main(void)
{
Systick_Init();
USART1_Init();
LED_Init();
/* System clocks configuration ---------------------------------------------*/
RCC_Configuration();
/* GPIO configuration ------------------------------------------------------*/
GPIO_Configuration();
/* DMA2 Stream0 channel0 configuration **************************************/
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_Channel = DMA_Channel_0;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)ADCConvertedValue;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
DMA_InitStructure.DMA_BufferSize = ARRAYSIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA2_Stream0, &DMA_InitStructure);
DMA_Cmd(DMA2_Stream0, ENABLE);
/* ADC Common Init **********************************************************/
ADC_CommonInitTypeDef ADC_CommonInitStructure;
ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
// 预分频4分频, ADCCLK=PCLK2/4=84/4=21Mhz,ADC时钟最好不要超过36Mhz
ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
// DMA使能 (DMA传输下要设置使能)
ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
//两个采样阶段之间的延迟x个时钟
ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
ADC_CommonInit(&ADC_CommonInitStructure);
/* ADC1 Init ****************************************************************/
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
//ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfConversion = 2;
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular channel0,1 configuration **************************************/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_56Cycles);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_56Cycles);
/* Enable DMA request after last transfer (Single-ADC mode) */
ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
/* Enable ADC1 DMA */
ADC_DMACmd(ADC1, ENABLE);
/* Enable ADC1 */
ADC_Cmd(ADC1, ENABLE);
ADC_SoftwareStartConv(ADC1);
while(1) {
LED_On();
for (u8 i = 0; i < ARRAYSIZE; i++) {
float a = (*(ADCConvertedValue + i) - 2048) * 512 /2048;
printf("% 5d ", (int)a);
}
printf("\r\n");
LED_Off();
Systick_Delay_ms(200);
}
}
参考
- 这篇写得很详细 https://deepbluembedded.com/stm32-adc-tutorial-complete-guide-with-examples/
- 可能有用的代码 https://github.com/Itachihi/stm32-adc/blob/master/readvb.c
- 可能有用的代码 ContinuousConvMode https://github.com/mnectnky/STM32F103-One-ADC-MultiChannel-Analog-Read/blob/main/main.c
- 可能有用的代码 ContinuousConvMode https://github.com/klesogor/ADC/blob/master/ADC.c
- 有用的代码 DMA, adc部分在main.c https://github.com/daedaleanai/stm32f103_adc2serial/blob/main/main.c
- 有用的代码 DMA, adc部分在main.c https://github.com/MrLuuuu/STM32F103ZET6_UartPrint/blob/master/USER/main.c
- 用stm32f103做的电容电感测试仪, 注入型, adc部分在main.c https://github.com/MilanGb/STM32F103-LC-R-meter/blob/main/main.c
- https://community.st.com/s/question/0D50X00009XkZ4F/adc-with-multiple-channels-settings
- This one is helpful https://embedds.com/multichannel-adc-using-dma-on-stm32/
- 这篇 https://blog.csdn.net/weixin_45456099/article/details/110669752
- 直接中断输出到串口 https://www.it610.com/article/1296571998083817472.htm
- STM32F2/F4/F7 DMA参考 https://www.st.com/resource/en/application_note/dm00046011-using-the-stm32f2-stm32f4-and-stm32f7-series-dma-controller-stmicroelectronics.pdf