ADC (Analog-to-Digital Converter): Converts an analog signal (such as a voltage) into a digital value that can be processed by a microcontroller. The conversion process typically involves sampling the analog signal at a specific rate and quantizing the signal to produce a digital representation.
CH32V003 ADC has a 10-bit resolution. Values 0-1023.
DMA (Direct Memory Access): This is a feature that allows the transfer of data directly to or from memory without involving the CPU. The DMA controller can move data between memory and peripherals autonomously, freeing the CPU to perform other tasks and increasing the efficiency of data transfers.
When combined, ADC DMA refers to a setup where the ADC performs analog-to-digital conversions, and the resulting digital data is transferred directly to memory using DMA.
I am using Embeetle IDE for programming and Tauno Monitor for Serial Monitor.


I use my custom-made CH32V003 development board. However, the code should work on all boards that use this MCU. It has 8 ADC channels (Pins). We can not use ADC channels 0 and 1 if the external crystal is connected, like on my board.
My test setup:

This is my main.c file.
/*********************************************************************
* File Name : main.c
* Board : ch32v003
* Date : 13.07.2024
* Description : ADC DMA example
**********************************************************************/
#include "debug.h"
/* Global Variable */
u16 TxBuf[10];
// Pin: PA2 - channel 0 can not be used if the external crystal is connected
// PA1 - channel 1 can not be used if the external crystal is connected
// PC4 - channel 2
// PD2 - channel 3
// PD3 - channel 4
// PD5 - channel 5
// PD6 - channel 6
// PD4 - channel 7
u8 channel = 7;
/*********************************************************************
* @fn ADC_Init
*
* @brief Initializes ADC collection.
*
* @param channel - ADC channel.
*
* @return none
*/
void tauno_ADC_init(u8 channel)
{
ADC_InitTypeDef ADC_InitStructure = {0};
GPIO_InitTypeDef GPIO_InitStructure = {0};
switch (channel) {
case 0: // PA2
case 1: // PA1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
break;
case 2: // PC4
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
break;
case 3: // PD2
case 4: // PD3
case 5: // PD5
case 6: // PD6
case 7: // PD4
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
break;
default:
break;
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
switch (channel) {
case 0: // PA2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
break;
case 1: // PA1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
break;
case 2: // PC4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
break;
case 3: // PD2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
break;
case 4: // PD3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
break;
case 5: // PD5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
break;
case 6: // PD6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
break;
case 7: // PD4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
break;
default:
break;
}
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
switch (channel) {
case 0: // PA2
case 1: // PA1
GPIO_Init(GPIOA, &GPIO_InitStructure);
break;
case 2: // PC4
GPIO_Init(GPIOC, &GPIO_InitStructure);
break;
case 3: // PD2
case 4: // PD3
case 5: // PD5
case 6: // PD6
case 7: // PD4
GPIO_Init(GPIOD, &GPIO_InitStructure);
break;
default:
break;
}
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Calibration_Vol(ADC1, ADC_CALVOL_50PERCENT);
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
//s16 Calibrattion_Val = Get_CalibrationValue(ADC1); ????
}
/*********************************************************************
* @fn Get_ADC_Val
*
* @brief Returns ADCx conversion result data.
*
* @param ch - ADC channel.
* ADC_Channel_0 - ADC Channel0 selected.
* ADC_Channel_1 - ADC Channel1 selected.
* ADC_Channel_2 - ADC Channel2 selected.
* ADC_Channel_3 - ADC Channel3 selected.
* ADC_Channel_4 - ADC Channel4 selected.
* ADC_Channel_5 - ADC Channel5 selected.
* ADC_Channel_6 - ADC Channel6 selected.
* ADC_Channel_7 - ADC Channel7 selected.
* ADC_Channel_8 - ADC Channel8 selected.
* ADC_Channel_9 - ADC Channel9 selected.
*
* @return val
*/
u16 Get_ADC_Val(u8 channel)
{
u16 val;
ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_241Cycles);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
val = ADC_GetConversionValue(ADC1);
return val;
}
/*
* https://github.com/Community-PIO-CH32V/platform-ch32v/blob/develop/examples/adc-cpu-temp-none-os/src/main.c
*/
u16 Get_ADC_Average(u8 ch, u8 times)
{
u32 temp_val = 0;
u8 t;
u16 val;
for (t = 0; t < times; t++)
{
temp_val += Get_ADC_Val(ch);
Delay_Ms(5);
}
val = temp_val / times;
return val;
}
/*********************************************************************
* @fn DMA_Tx_Init
*
* @brief Initializes the DMAy Channelx configuration.
*
* @param DMA_CHx - x can be 1 to 7.
* ppadr - Peripheral base address.
* memadr - Memory base address.
* bufsize - DMA channel buffer size.
*
* @return none
*/
void DMA_Tx_Init(DMA_Channel_TypeDef *DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
{
DMA_InitTypeDef DMA_InitStructure = {0};
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA_CHx);
DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = bufsize;
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_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_CHx, &DMA_InitStructure);
}
/*********************************************************************
* @fn main
*
* @brief Main program.
*
* @return none
*/
int main(void) {
u16 adc_val; // 0-1023
u16 i;
Delay_Init();
USART_Printf_Init(115200);
Delay_Ms(3000); // Give serial monitor time to open
printf("SystemClk:%d\r\n", (unsigned)SystemCoreClock);
printf("DeviceID: %08x\r\n", (unsigned)DBGMCU_GetDEVID());
tauno_ADC_init(channel);
DMA_Tx_Init(DMA1_Channel1, (u32)&ADC1->RDATAR, (u32)TxBuf, 10);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 1, ADC_SampleTime_241Cycles);
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
Delay_Ms(50);
ADC_SoftwareStartConvCmd(ADC1, DISABLE);
for (i = 0; i < 10; i++) {
printf("%04d\r\n", TxBuf[i]);
Delay_Ms(10);
}
while (1) {
adc_val = Get_ADC_Val(channel);
printf("adc val:%d\r\n", adc_val);
Delay_Ms(100);
}
return 0;
}