ADC – Analog to digital conversion (single multiple channels and detailed examples of interrupt or DMA reading)

Article directory

  • ADC – Analog to Digital Conversion
    • ADC functional block diagram
      • Voltage input range
        • How to measure voltage exceeding 0~3.3V?
      • Input channel
      • Channel conversion sequence
      • trigger source
      • conversion time
      • data register
      • interrupt
      • voltage conversion
    • ADC initialization structure
    • Code example
      • 1-Independent mode-Single channel-Interrupt reading
      • 2-Independent mode-Single channel-DMA read
      • 3-Independent mode-Multi-channel-DMA reading
      • 4-Dual mode-Multi-channel-Rule synchronization

ADC – Analog to Digital Conversion

ADC: Analog to Digital, analog-to-digital converter

1-Three independent ADCs 1 / 2 / 3

2-The resolution is 12 bits

3-Each ADC has 18 channels, including 16 external channels

ADC functional block diagram

1-Voltage input range

2-Input channel

3-Conversion order

4-Trigger source

5-Conversion time

6-Data register

7-Interruption

Voltage input range

Input voltage: VREF- ≤ VIN ≤ VREF +

Pins that determine the input voltage: VREF-, VREF +, VDDA, VSSA

VSSA and VREF- are grounded, VREF + and VDDA are connected to 3V3, and the input voltage range of the ADC is: 0~3.3V.

How to measure the voltage exceeding 0~3.3V?

ADC can measure:-10V~10V

According to Kirchhoff’s law (KCL), the current flowing into a node is equal to the current flowing out

(Vint – Vout)/R2 + (3V3-Vout)/R1 = Vout / R3

Vout = (Vint + 10) /6

R1 = 100K, R2 = 300K, R3 = 150K, the effect will be better

Input channel

The 16 external channels are divided into regular channels and injection channels during conversion. There are up to 16 regular channels and 4 injection channels. So what is the difference between these two channels? When to use?

Regular Channel: As the name implies, the regular channel means very regular. This is the channel we usually use.

Injection channel: Injection can be understood as inserting or jumping in line. It is a restless channel. It is a method that is forcibly inserted into the conversion during regular channel conversion. This is very similar to interrupt programs, both of which are restless masters. Therefore, the injection channel will only appear when the regular channel exists.

Channel conversion sequence

Trigger source

1. Software trigger: ADC_CR2:ADON/SWST ART/JSWSTART

2. External event trigger: Internal timer/external IO

Selection: ADC_CR2:EXTSEL[2:0] and JEXTSEL[2:0]

Activation: ADC_CR2:EXTEN and JEXTEN

Conversion time

Conversion timeT: conv = sampling time + 12.5 cycles

ADC_CLK: ADC analog circuit clock, the maximum value is 14M, provided by PCLK2, it can also be divided by 2/4/6/8 (generally it can only be divided by 6), ADCPRE[1 of RCC_CFGR: 0] settings. PCLK2=72M.

Digital clock: RCC_APB2ENR, used to access registers

Sampling time: The ADC requires several ADC_CLK cycles to complete sampling of the input analog quantity. The number of sampling cycles can be set through the SMPx[2:0] bits in the ADC sampling time registers ADC_SMPR1 and ADC_SMPR2. ADC_SMPR2 It controls channels 0~9, and ADC_SMPR1 controls channels 10~17. Each channel can be sampled at a different time. The minimum sampling period is 1.5, that is, if we want to achieve the fastest sampling, we should set the sampling period to 31.5 periods. The period mentioned here is 1/ADC_CLK.

Minimum conversion time: Tconv = sampling time + 12.5 cycles

PCLK2 = 72M, ADC_CLK = 72/6 = 12M

Tconv = 1.5 + 12.5 = 14 cycles = 14/12us=1.17us

Data register

After everything is ready, the ADC converted data depends on the conversion group. The data of the rule group is placed in the ADC_DR register, and the data of the injection group is placed in JDRx.

1-16 bits are valid, used to store independent mode conversion completed data
2- ADC_CR2:ALIGN

3- Only one, multi-channel acquisition is best done using DMA

Bits 1-16 are valid, used to store injection channel conversion completion data
2- ADC_CR2:ALIGN

3- There are 4 such registers

Interruption

1-ADC_SR,ADC_CR1

2- ADC_HTR, ADC_LTR

Voltage conversion

How to calculate the analog quantity based on the amount of data

1-The voltage input range is: 0~3.3V

2-The resolution is 12 bits

3-The minimum accuracy is: 3.3/2^12

4-Suppose the digital quantity is X, then there is an analog quantity Y = (3.3 / 2^12)*X

ADC initialization structure

ADC_InitTypeDef

ADC_MODE: mode selection, ADC_CR1:DUALMOD

ADC_ScanConvMode: Scan mode, ADC_CR1:SCAN

ADC_ContinuousConvMode: continuous conversion mode, ADC_CR2:CON

ADC_ExternalTrigConv: External trigger conversion selection, ADC_CR2: EXTTRIG and EXTSEL[2:0]

ADC_MODE: mode selection, ADC_CR1:DUALMOD

ADC_ScanConvMode: Scan mode, ADC_CR1:SCAN

ADC_DataAlign: data alignment format, ADC_CR2:ALIGN

ADC_NbrOfChannel: Number of channels converted, configuration rule sequence register and injection sequence register

Several commonly used firmware library functions

ADC_Init(); 429

RCC_ADCCLKConfig(); 680

ADC_RegularChannelConfig(); 442

ADC_Cmd(); 431 //Single ADC mode use

ADC_SoftwareStartConvCmd(); 438 //Use in single ADC mode

ADC_ExternalTrigConvCmd(); 443 //Dual ADC mode use

ADC_DMACmd(); 432

Code example

Compass ADC interface

1-Independent mode-Single channel-Interrupt reading

1-Initialize the GPIO used by ADC

2-Initialize the ADC initialization structure

3-Configure the ADC clock, configure the conversion sequence and sampling time of the channel

4-Enable ADC conversion completion interrupt and configure interrupt priority

The interrupt source is searched in stm32f10x.h, and the interrupt service function is searched in the startup file

5-Enable ADC and prepare to start conversion

6-Calibrate ADC

7-The software triggers the ADC and actually starts the conversion

8-Write an interrupt service function to read ADC conversion data

The interrupt source is searched in stm32f10x.h, and the interrupt service function is searched in the startup file

9-Write the main function to print out the converted data

bsp_adc.h

#ifndef _BSP_ADC_H
#define _BSP_ADC_H

#include "stm32f10x.h"

#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
#define ADC_x ADC2

#define ADC_CHANNEL ADC_Channel_11
#define ADC_CLK RCC_APB2Periph_ADC2

#define ADC_IRQ ADC1_2_IRQn //Both ADC1 and ADC2 use this interrupt source and interrupt service function
#define ADC_IRQHandler ADC1_2_IRQHandler


void ADCx_Init(void);


#endif /*_BSP_ADC_H*/

bsp_adc.c

#include "bsp_adc.h"

static void ADCx_GPIO_Config(void)
{<!-- -->
GPIO_InitTypeDef GPIO_InitStructure;
\t
RCC_APB2PeriphClockCmd(ADC_GPIO_CLK,ENABLE);
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = ADC_PIN;


GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADCx_Mode_Config(void)
{<!-- -->
ADC_InitTypeDef ADC_InitStruct;
RCC_APB2PeriphClockCmd(ADC_CLK ,ENABLE);
\t
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // No external trigger conversion is required, just open the software
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // Only one ADC is used, which is independent mode
ADC_InitStruct.ADC_NbrOfChannel = 1;
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //Disable scan mode, only required for multi-channel, not required for single channel

ADC_Init(ADC_x, & amp;ADC_InitStruct);
\t
//Configure the ADC clock to be PCLK2 divided by 8, that is, 9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);

//Configure ADC channel conversion sequence and sampling time
ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL,1,ADC_SampleTime_55Cycles5);

// An interrupt is generated when the ADC conversion is completed, and the conversion value is read in the interrupt service routine.
ADC_ITConfig(ADC_x, ADC_IT_EOC, ENABLE);
\t

ADC_Cmd(ADC_x,ENABLE);
\t
//ADC starts calibration
ADC_StartCalibration(ADC_x);
\t
//Wait for ADC calibration to complete
while(ADC_GetCalibrationStatus(ADC_x));
\t
// Since no external trigger is used, software is used to trigger the ADC conversion.
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
\t
}

static void ADC_NVIC_Config(void)
{<!-- -->
NVIC_InitTypeDef NVIC_InitStruct;
\t
//Priority grouping
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
\t
//Configure interrupt priority
NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQ;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
\t
NVIC_Init( & amp;NVIC_InitStruct);

}

void ADCx_Init(void)
{<!-- -->
  ADCx_GPIO_Config();
  ADCx_Mode_Config();
ADC_NVIC_Config();
\t
}


mian.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "./adc/bsp_adc.h"

// ADC single channel acquisition, does not use DMA, generally only ADC2 is used in this way, because ADC2 cannot use DMA


extern __IO uint16_t ADC_ConversionValue;

//Local variables are used to save the converted voltage value
float ADC_ConvertedValueLocal;

//Software delay
void Delay(__IO uint32_t nCount)
{<!-- -->
  for(; nCount != 0; nCount--);
}

int main(void)
{<!-- -->

USART_Config();
  ADCx_Init();
  
  printf("\r\
 ----This is an ADC single-channel interrupt reading experiment----\r\
");
while(1)
{<!-- -->
    ADC_ConvertedValueLocal = (float) ADC_ConversionValue/4096*3.3;
    
    printf("\r\
 The current AD value = 0x x \r\
", ADC_ConversionValue);
    printf("\r\
 The current AD value = %f V \r\
", ADC_ConvertedValueLocal);
    Delay(0xffffee);
    
  }
\t  
}

stm32f10x_it.c

__IO uint16_t ADC_ConversionValue;


void ADC_IRQHandler(void)
{<!-- -->
  if(ADC_GetITStatus(ADC_x, ADC_IT_EOC) == SET)
  {<!-- -->
    ADC_ConversionValue = ADC_GetConversionValue(ADC_x);
  }
  
  ADC_ClearITPendingBit(ADC_x, ADC_IT_EOC);

}

2-Independent mode-Single channel-DMA reading

bsp_adc.c

#include "bsp_adc.h"

__IO uint16_t ADC_ConversionValue;

static void ADCx_GPIO_Config(void)
{<!-- -->
GPIO_InitTypeDef GPIO_InitStructure;
\t
RCC_APB2PeriphClockCmd(ADC_GPIO_CLK,ENABLE);
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = ADC_PIN;


GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADCx_Mode_Config(void)
{<!-- -->
  DMA_InitTypeDef DMA_InitStructure;
  ADC_InitTypeDef ADC_InitStruct;
  
  // Turn on DMA clock
  RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
  
  DMA_DeInit(ADC_DMA_CHANNEL);
  
  //Set DMA source address: ADC data register address*/
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)( & amp;(ADC_x->DR));
  // memory address
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) & amp;ADC_ConversionValue;
  // Direction: from peripheral to memory
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  //Transfer size
  DMA_InitStructure.DMA_BufferSize = 1;
  // Peripheral address does not increase
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  //The memory address does not increase
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
  // Peripheral data unit
  DMA_InitStructure.DMA_PeripheralDataSize =
  DMA_PeripheralDataSize_HalfWord;
  //Memory data unit
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  // DMA mode, loop mode
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //Priority: high
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  // Disable memory-to-memory transfers
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  //Configure DMA channel
  DMA_Init(ADC_DMA_CHANNEL, & amp;DMA_InitStructure);
  // Enable DMA
  DMA_Cmd (ADC_DMA_CHANNEL,ENABLE);
  
  
\t
RCC_APB2PeriphClockCmd(ADC_CLK ,ENABLE);
\t
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // No external trigger conversion is required, just open the software
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // Only one ADC is used, which is independent mode
ADC_InitStruct.ADC_NbrOfChannel = 1;
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //Disable scan mode, only required for multi-channel, not required for single channel

ADC_Init(ADC_x, & amp;ADC_InitStruct);
\t
//Configure the ADC clock to be PCLK2 divided by 8, that is, 9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);

//Configure ADC channel conversion sequence and sampling time
ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL,1,ADC_SampleTime_55Cycles5);
  
  //Generate ADC DMA request
  ADC_DMACmd(ADC_x, ENABLE);

ADC_Cmd(ADC_x,ENABLE);
\t
//ADC starts calibration
ADC_StartCalibration(ADC_x);
\t
//Wait for ADC calibration to complete
while(ADC_GetCalibrationStatus(ADC_x));
\t
// Since no external trigger is used, software is used to trigger the ADC conversion.
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
\t
}


void ADCx_Init(void)
{<!-- -->
  ADCx_GPIO_Config();
  ADCx_Mode_Config();
\t
\t
}

bsp_adc.h

#ifndef _BSP_ADC_H
#define _BSP_ADC_H

#include "stm32f10x.h"

#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN GPIO_Pin_1
#define ADC_x ADC1

#define ADC_CHANNEL ADC_Channel_11
#define ADC_CLK RCC_APB2Periph_ADC1

#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL DMA1_Channel1

void ADCx_Init(void);


#endif /*_BSP_ADC_H*/

mian.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "./adc/bsp_adc.h"

// ADC single channel acquisition, does not use DMA, generally only ADC2 is used in this way, because ADC2 cannot use DMA


extern __IO uint16_t ADC_ConversionValue;

//Local variables are used to save the converted voltage value
float ADC_ConvertedValueLocal;

//Software delay
void Delay(__IO uint32_t nCount)
{<!-- -->
  for(; nCount != 0; nCount--);
}

int main(void)
{<!-- -->

USART_Config();
  ADCx_Init();
  
  printf("\r\
 ----This is an ADC independent mode-single channel-DMA reading experiment----\r\
");
while(1)
{<!-- -->
    ADC_ConvertedValueLocal = (float) ADC_ConversionValue/4096*3.3;
    
    printf("\r\
 The current AD value = 0x x \r\
", ADC_ConversionValue);
    printf("\r\
 The current AD value = %f V \r\
", ADC_ConvertedValueLocal);
    Delay(0xffffee);
    
  }
 
}

3-Independent mode-Multi-channel-DMA reading

bsp_adc.c

#include "bsp_adc.h"

__IO uint16_t ADC_ConversionValue[NUMCHANNEL] = {<!-- -->0,0,0,0,0,0};

static void ADCx_GPIO_Config(void)
{<!-- -->
GPIO_InitTypeDef GPIO_InitStructure;
\t
RCC_APB2PeriphClockCmd(ADC_GPIO_CLK,ENABLE);
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = ADC_PIN1;
  GPIO_InitStructure.GPIO_Pin = ADC_PIN2;
  GPIO_InitStructure.GPIO_Pin = ADC_PIN3;
  GPIO_InitStructure.GPIO_Pin = ADC_PIN4;
  GPIO_InitStructure.GPIO_Pin = ADC_PIN5;
  GPIO_InitStructure.GPIO_Pin = ADC_PIN6;

GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADCx_Mode_Config(void)
{<!-- -->
  DMA_InitTypeDef DMA_InitStructure;
  ADC_InitTypeDef ADC_InitStruct;
  
  // Turn on DMA clock
  RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
  
  DMA_DeInit(ADC_DMA_CHANNEL);
  
  //Set DMA source address: ADC data register address*/
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)( & amp;(ADC_x->DR));
  // memory address
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConversionValue;
  // Direction: from peripheral to memory
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  //Transfer size
  DMA_InitStructure.DMA_BufferSize = NUMCHANNEL;
  // Peripheral address does not increase
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  //The memory address does not increase
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  // Peripheral data unit
  DMA_InitStructure.DMA_PeripheralDataSize =
  DMA_PeripheralDataSize_HalfWord;
  //Memory data unit
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  // DMA mode, loop mode
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //Priority: high
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  // Disable memory-to-memory transfers
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  //Configure DMA channel
  DMA_Init(ADC_DMA_CHANNEL, & amp;DMA_InitStructure);
  // Enable DMA
  DMA_Cmd (ADC_DMA_CHANNEL,ENABLE);
  
  
\t
RCC_APB2PeriphClockCmd(ADC_CLK ,ENABLE);
\t
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // No external trigger conversion is required, just open the software
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent; // Only one ADC is used, which is independent mode
ADC_InitStruct.ADC_NbrOfChannel = NUMCHANNEL;
ADC_InitStruct.ADC_ScanConvMode = ENABLE; //Enable scan mode, required for multi-channel, not required for single channel

ADC_Init(ADC_x, & amp;ADC_InitStruct);
\t
//Configure the ADC clock to be PCLK2 divided by 8, that is, 9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);

//Configure ADC channel conversion sequence and sampling time
ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL1,1,ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL2,2,ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL3,3,ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL4,4,ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL5,5,ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC_x,ADC_CHANNEL6,6,ADC_SampleTime_55Cycles5);
  
  //Generate ADC DMA request
  ADC_DMACmd(ADC_x, ENABLE);

ADC_Cmd(ADC_x,ENABLE);
\t
//ADC starts calibration
ADC_StartCalibration(ADC_x);
\t
//Wait for ADC calibration to complete
while(ADC_GetCalibrationStatus(ADC_x));
\t
// Since no external trigger is used, software is used to trigger the ADC conversion.
ADC_SoftwareStartConvCmd(ADC_x, ENABLE);
\t
}


void ADCx_Init(void)
{<!-- -->
  ADCx_GPIO_Config();
  ADCx_Mode_Config();
\t
\t
}


bsp_adc.h

#ifndef _BSP_ADC_H
#define _BSP_ADC_H

#include "stm32f10x.h"

#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC
#define ADC_PIN1 GPIO_Pin_0
#define ADC_PIN2 GPIO_Pin_1
#define ADC_PIN3 GPIO_Pin_2
#define ADC_PIN4 GPIO_Pin_3
#define ADC_PIN5 GPIO_Pin_4
#define ADC_PIN6 GPIO_Pin_5
#define ADC_x ADC1

#define NUMCHANNEL 6

#define ADC_CHANNEL1 ADC_Channel_10
#define ADC_CHANNEL2 ADC_Channel_11
#define ADC_CHANNEL3 ADC_Channel_12
#define ADC_CHANNEL4 ADC_Channel_13
#define ADC_CHANNEL5 ADC_Channel_14
#define ADC_CHANNEL6 ADC_Channel_15

#define ADC_CLK RCC_APB2Periph_ADC1

#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL DMA1_Channel1

void ADCx_Init(void);


#endif /*_BSP_ADC_H*/

mian.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "./adc/bsp_adc.h"

// ADC single channel acquisition, does not use DMA, generally only ADC2 is used in this way, because ADC2 cannot use DMA


extern __IO uint16_t ADC_ConversionValue[NUMCHANNEL];

//Local variables are used to save the converted voltage value
float ADC_ConvertedValueLocal[NUMCHANNEL];

//Software delay
void Delay(__IO uint32_t nCount)
{<!-- -->
  for(; nCount != 0; nCount--);
}

int main(void)
{<!-- -->

USART_Config();
  ADCx_Init();
  
  printf("\r\
 ----This is an ADC independent mode-multi-channel-DMA reading experiment----\r\
");
while(1)
{<!-- -->
    ADC_ConvertedValueLocal[0] = (float) ADC_ConversionValue[0]/4096*3.3;
    ADC_ConvertedValueLocal[1] = (float) ADC_ConversionValue[1]/4096*3.3;
    ADC_ConvertedValueLocal[2] = (float) ADC_ConversionValue[2]/4096*3.3;
    ADC_ConvertedValueLocal[3] = (float) ADC_ConversionValue[3]/4096*3.3;
    ADC_ConvertedValueLocal[4] = (float) ADC_ConversionValue[4]/4096*3.3;
    ADC_ConvertedValueLocal[5] = (float) ADC_ConversionValue[5]/4096*3.3;
    
    printf("\r\
 The CH0 AD value = %f V \r\
", ADC_ConvertedValueLocal[0]);
    printf("\r\
 The CH1 AD value = %f V \r\
", ADC_ConvertedValueLocal[1]);
    printf("\r\
 The CH2 AD value = %f V \r\
", ADC_ConvertedValueLocal[2]);
    printf("\r\
 The CH3 AD value = %f V \r\
", ADC_ConvertedValueLocal[3]);
    printf("\r\
 The CH4 AD value = %f V \r\
", ADC_ConvertedValueLocal[4]);
    printf("\r\
 The CH5 AD value = %f V \r\
", ADC_ConvertedValueLocal[5]);
    
    printf("\r\
\r\
");
    Delay(0xffffee);
    
  }
\t
  
}

4-Dual mode-Multi-channel-Rule synchronization

bsp_adc.c

#include "bsp_adc.h"

__IO uint32_t ADC_ConversionValue[NUMCHANNEL] = {<!-- -->0};

static void ADCx_GPIO_Config(void)
{<!-- -->
GPIO_InitTypeDef GPIO_InitStructure;
\t
RCC_APB2PeriphClockCmd(ADC_GPIO_CLK,ENABLE);
\t
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = ADC_1_PIN | ADC_2_PIN;

GPIO_Init(ADC_PORT, &GPIO_InitStructure);
}

static void ADCx_Mode_Config(void)
{<!-- -->
  DMA_InitTypeDef DMA_InitStructure;
  ADC_InitTypeDef ADC_InitStruct;
  
  // Turn on DMA clock
  RCC_AHBPeriphClockCmd(ADC_DMA_CLK, ENABLE);
  
  DMA_DeInit(ADC_DMA_CHANNEL);
  
  //Set DMA source address: ADC data register address*/
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)( & amp;(ADC_1->DR));
  // memory address
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConversionValue;
  // Direction: from peripheral to memory
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  //Transfer size
  DMA_InitStructure.DMA_BufferSize = NUMCHANNEL;
  // Peripheral address does not increase
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  //The memory address does not increase
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
  // Peripheral data unit
  DMA_InitStructure.DMA_PeripheralDataSize =
  DMA_PeripheralDataSize_Word;
  //Memory data unit
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  // DMA mode, loop mode
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  //Priority: high
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  // Disable memory-to-memory transfers
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  //Configure DMA channel
  DMA_Init(ADC_DMA_CHANNEL, & amp;DMA_InitStructure);
  // Enable DMA
  DMA_Cmd (ADC_DMA_CHANNEL,ENABLE);
  
  
//ADC1
RCC_APB2PeriphClockCmd(ADC_1_CLK ,ENABLE);
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // No external trigger conversion is required, just open the software
ADC_InitStruct.ADC_Mode = ADC_Mode_RegSimult;
ADC_InitStruct.ADC_NbrOfChannel = NUMCHANNEL;
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //Enable scan mode, required for multi-channel, not required for single channel
ADC_Init(ADC_1, & amp;ADC_InitStruct);
//Configure the ADC clock to be PCLK2 divided by 8, that is, 9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
//Configure ADC channel conversion sequence and sampling time
ADC_RegularChannelConfig(ADC_1,ADC_1_CHANNEL,1,ADC_SampleTime_55Cycles5);
  
  //ADC2
RCC_APB2PeriphClockCmd(ADC_2_CLK ,ENABLE);
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // No external trigger conversion is required, just open the software
ADC_InitStruct.ADC_Mode = ADC_Mode_RegSimult;
ADC_InitStruct.ADC_NbrOfChannel = NUMCHANNEL;
ADC_InitStruct.ADC_ScanConvMode = DISABLE; //Enable scan mode, required for multi-channel, not required for single channel
ADC_Init(ADC_2, & amp;ADC_InitStruct);

//Configure the ADC clock to be PCLK2 divided by 8, that is, 9MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
//Configure ADC channel conversion sequence and sampling time
ADC_RegularChannelConfig(ADC_2,ADC_2_CHANNEL,1,ADC_SampleTime_55Cycles5);
  
  
  //Generate ADC DMA request
  ADC_DMACmd(ADC_1, ENABLE);

ADC_Cmd(ADC_1,ENABLE);
\t
//ADC starts calibration
ADC_StartCalibration(ADC_1);
\t
//Wait for ADC calibration to complete
while(ADC_GetCalibrationStatus(ADC_1));
\t
  ADC_Cmd(ADC_2,ENABLE);
\t
//ADC starts calibration
ADC_StartCalibration(ADC_2);
\t
//Wait for ADC calibration to complete
while(ADC_GetCalibrationStatus(ADC_2));
  
  
  //Enable ADC2 as external trigger
  ADC_ExternalTrigConvCmd(ADC_2,ENABLE);
// Since no external trigger is used, software is used to trigger the ADC conversion.
ADC_SoftwareStartConvCmd(ADC_1, ENABLE);
\t
}


void ADCx_Init(void)
{<!-- -->
  ADCx_GPIO_Config();
  ADCx_Mode_Config();
\t
\t
}


bsp_adc.h

#ifndef _BSP_ADC_H
#define _BSP_ADC_H

#include "stm32f10x.h"

/*
  In dual mode, the data converted by ADC1 and ADC2 are stored in the data register of ADC1
  ADC1 is in the lower 16 bits, ADC2 is in the upper 16 bits
  In dual ADC mode, the first ADC must be ADC1, and the second ADC must be ADC2.
 */
#define ADC_GPIO_CLK RCC_APB2Periph_GPIOC
#define ADC_PORT GPIOC

#define ADC_1_PIN GPIO_Pin_1
#define ADC_2_PIN GPIO_Pin_4
#define ADC_1 ADC1
#define ADC_2 ADC2

#define NUMCHANNEL 1


#define ADC_1_CHANNEL ADC_Channel_11

#define ADC_2_CHANNEL ADC_Channel_14


#define ADC_1_CLK RCC_APB2Periph_ADC1
#define ADC_2_CLK RCC_APB2Periph_ADC2

#define ADC_DMA_CLK RCC_AHBPeriph_DMA1
#define ADC_DMA_CHANNEL DMA1_Channel1

void ADCx_Init(void);


#endif /*_BSP_ADC_H*/

mian.c

#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "./adc/bsp_adc.h"

// ADC single channel acquisition, does not use DMA, generally only ADC2 is used in this way, because ADC2 cannot use DMA


extern __IO uint32_t ADC_ConversionValue[NUMCHANNEL];

//Local variables are used to save the converted voltage value
float ADC_ConvertedValueLocal[NUMCHANNEL*2] = {<!-- -->0,0};

//Software delay
void Delay(__IO uint32_t nCount)
{<!-- -->
  for(; nCount != 0; nCount--);
}

int main(void)
{<!-- -->
  uint16_t temp0 = 0, temp1 = 0;
USART_Config();
  ADCx_Init();
  
  printf("\r\
 ----This is an ADC dual mode-multi-channel-rule synchronous reading experiment----\r\
");
while(1)
{<!-- -->
    //Get the high 16 bits of the ADC1 data register, this is the conversion data of ADC2
    temp0 = (ADC_ConversionValue[0] & amp; 0XFFFF0000) >>16;
    //Get the lower 16 bits of the ADC1 data register. This is the conversion data of ADC1.
    temp1 = (ADC_ConversionValue[0] & amp; 0X0000FFFF);
    
    ADC_ConvertedValueLocal[0] = (float) temp0/4096*3.3;
    ADC_ConvertedValueLocal[1] = (float) temp1/4096*3.3;
    
    printf("\r\
 ADC1 AD Value = %f V \r\
", ADC_ConvertedValueLocal[1]);
    printf("\r\
 ADC2 AD Value = %f V \r\
", ADC_ConvertedValueLocal[0]);
    printf("\r\
\r\
");
    Delay(0xffffee);
    
  }
\t
  
}