CH32V003 ADC scan mode - DMA required? - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: March 09, 2023, 03:56:20 pm
content: Can anyone confirm whether or not the scan mode of the CH32V003's ADC can be used without DMA?I have been trying to use scan mode to read two channels in sequence, but couldn't get it to work. I had the sequence length set up in RSQR1 (noting that it's a zero-based count, i.e. length of 2 = 1), and the relevant channels selected as 1st and 2nd in sequence in RSQR3, SCAN flag set in CTLR1, ADON in CTLR2. Then I assumed you could just do the following to perform a single scan conversion:1. Set ADON bit in CTLR2 to initiate the scan.2. Wait for EOC to be set in STATR.3. Read first channel's value from RDATAR.4. Repeat steps 2 & 3 for second channel.But EOC never gets set, so the code just gets stuck in the polling loop.The peripherals of the CH32V003 seem to be 99% identical to the STM32F10x, and looking at the reference manual for that (RM0008), it says that"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". But I can find nothing in the CH32V003 manual that says that DMA must be used with scan mode.

author: thm_w
author_rank: Super Contributor
timestamp: March 10, 2023, 12:17:18 am
content: I would assume its not possible for the same reason its not possible on the STM32: where is the ADC going to store the second conversion value if you don't grab it in time?Maybe try first getting a single conversion with interrupt working (EOCIE), then enable scan, see what happens.If you do not want DMA, you may end up having to either:- manually start each conversion and poll for the result- or trigger the second conversion in the 1st ADC EOC interrupt

author: HwAoRrDk
author_rank: Super Contributor
timestamp: March 10, 2023, 05:31:59 pm
content: Quote from: thm_w on March 10, 2023, 12:17:18 amYes, I suppose that's true. Although, I was imagining that perhaps it would not start the next conversion in the sequence until the EOC flag had been cleared from the prior.Quote from: thm_w on March 10, 2023, 12:17:18 amI do have single conversion working already, so that's no issue.Will probably look into trying to use DMA, although I've not yet put any effort into understanding the DMA system on the CH32V003. Presumably I just set up a buffer of N elements, then configure DMA to copy from ADC RDATAR register to memory when triggered, incrementing the memory address each time, a total of N times. Suppose in order to wait for completion I wait for DMA transfer completion flag, rather than any kind of ADC flag (there appears to be nothing else apart from EOC). And I'm not exactly sure what it would be that actually triggers the DMA transfers - the docs don't mention how that happens. Perhaps it's EOC - but then if that is raised after every conversion in sequence, why did I never see the flag being set in the status register in my earlier attempt?

author: thm_w
author_rank: Super Contributor
timestamp: March 10, 2023, 11:54:58 pm
content: You have single conversion working, but was it interrupt? Adding to the end of the interrupt routine to select channel 2 and start a new scan, would be basically what you are trying to do right (maybe with a flag if you want it to keep going CH1 -> Ch2 -> CH1 etc).Quote from: HwAoRrDk on March 10, 2023, 05:31:59 pmYeah there is your buffer array in memory, your DMA source (ADC) and destination address (array), and you either do regular DMA or circular mode.Making sure to set the array variable to match the DMA setting (16-bit, 32-bit, whatever). DMA_CFGRx->MSIZE and PSIZEIt might be possible to setup the DMA and poll until its complete for initial testing:QuoteI'm thinking how you have it setup either the EOC flag is never "seen", as its taken by DMA engine, or, the ADC conversion is just not starting if ADC_CTLR2->DMA is off and settings are for continuous? Not sure.If you have a good debugger that can view the registers live its nice, unsure if thats available for CH32v003 though.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: March 11, 2023, 04:24:58 pm
content: I got scan mode working with DMA. I decided to just use continuous mode and have the DMA cyclically transfer the scan sequence of channel readings into a struct with an appropriate number of fields.It was actually pretty easy and clear what to do from looking at some of the SPL/HAL code and example code on WCH's GitHub. Although I am curious as to why on their ADC DMA example they choose to use a transfer size of 16 bits, when the ADC data register is 32 bit. I suppose it doesn't really matter, as the ADC has only 10 bit resolution, so values will never be larger than 16 bits. Maybe transfers with 16 bit size occupies the peripheral bus for less time? Anyway, it all works fine for me with 32-bit transfer size.I'm still none the wiser exactly what mechanism is used to trigger the DMA transfer by the ADC. At the moment my best guess is "magic!".I wish their documentation was a bit better.By the way, because the docs state that 32-bit transfer sizes must have a 4-byte-aligned source and destination address, I did this for the memory destination:Code:static struct {uint32_t ch_a2 __attribute__((warn_if_not_aligned(4)));uint32_t ch_a3 __attribute__((warn_if_not_aligned(4)));} adc_values __attribute__((aligned(4)));Overkill?Quote from: thm_w on March 10, 2023, 11:54:58 pmHmm, maybe you're right. Perhaps the EOC is 'stolen' by the DMA in scan mode, or scanning doesn't start without DMA mode set too.Quote from: thm_w on March 10, 2023, 11:54:58 pmYes, you're able to debug with GDB, although I can't right now because for some reason OpenOCD is not cooperating lately, even though I had it working before and haven't changed anything. Oh well...

author: Sacodepatatas
author_rank: Regular Contributor
timestamp: March 12, 2023, 01:38:49 am
content: Have you tested if the ADC has really 10bit resolution? Just for the sake of curiosity, because according to the datasheet, the ADC value is 12 bit aligned and the blocks diagram shows a 12bit Converter (maybe it is a typo), but all of this make me think that WCH just used the same ADC peripheral block from their higher MCU models. Maybe the 10bit limit comes from the number of conversion cycles in the SAR, or maybe the 2 LSB are shadowed by noise generated by a poor quantizer, in such a case the two missing bits could be retrieved just by using an averaging filter.Edit: I bet for the second case, because 14 cycles are needed for the EOC.

author: HwAoRrDk
author_rank: Super Contributor
timestamp: March 12, 2023, 03:46:20 pm
content: Quote from: Sacodepatatas on March 12, 2023, 01:38:49 amI think that is just a mistake in the reference manual in the section talking about data alignment (right- or left-aligned) - probably a copy-paste mistake. Their other chips (e.g. CH32V103, CH32V30x) have 12-bit ADCs, so probably copied over. Everywhere else refers to the ADC in the CH32V003 as 10-bit.

author: Sajeev
author_rank: Contributor
timestamp: January 23, 2024, 07:46:21 pm
content: Dear Sir,Please find the attached code which scans 2 channels in continues mode and prints result in polling mode to UART. Scan converts PD4 and PD3 and result is printed by polling EOC. works without DMA

author: jlsilicon
author_rank: Contributor
timestamp: April 28, 2024, 03:53:40 am
content: Here is a simple Example of ADC without DMA.Code:/*Create project as : ch32v003 - f4p6 (- Not 103/203)- copy main.c from /EVT/EXAM/...WORKS :*/#include "debug.h"//void ADCConfig(void) ;#define ANALOG1_PIN GPIO_Pin_4#define ANALOG1_PORT GPIODuint16_t adcReading ;int adcFlag = 1 ;void ADCConfig(void){ADC_InitTypeDef ADC_InitStructure = {0};GPIO_InitTypeDef GPIO_InitStructure = {0};NVIC_InitTypeDef NVIC_InitStructure = {0};RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div8);GPIO_InitStructure.GPIO_Pin = ANALOG1_PIN;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;GPIO_Init(ANALOG1_PORT, &GPIO_InitStructure);ADC_DeInit(ADC1);ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;ADC_InitStructure.ADC_ScanConvMode = DISABLE;ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigInjecConv_None;ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;ADC_InitStructure.ADC_NbrOfChannel = 1;ADC_Init(ADC1, &ADC_InitStructure);ADC_InjectedSequencerLengthConfig(ADC1, 1);ADC_InjectedChannelConfig(ADC1, ADC_Channel_7, 1, ADC_SampleTime_241Cycles);ADC_ExternalTrigInjectedConvCmd(ADC1, DISABLE);NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);ADC_Calibration_Vol(ADC1, ADC_CALVOL_50PERCENT);ADC_ITConfig(ADC1, ADC_IT_JEOC, ENABLE);ADC_Cmd(ADC1, ENABLE);ADC_ResetCalibration(ADC1);while (ADC_GetResetCalibrationStatus(ADC1)){}ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1)){}}void ADC1_IRQHandler() __attribute__((interrupt("WCH-Interrupt-fast")));/*** The function ADC1_IRQHandler handles the interrupt for ADC1 and prints the value of the injected* conversion.*/void ADC1_IRQHandler(){if (ADC_GetITStatus(ADC1, ADC_IT_JEOC) == SET){adcFlag = 1;ADC_ClearITPendingBit(ADC1, ADC_IT_JEOC);}}///********************************************************************** @fn main** @brief Main program.** @return none*/int main(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);SystemCoreClockUpdate();Delay_Init();USART_Printf_Init(115200);printf(\r\n : ADC Test : r\n",SystemCoreClock);printf("SystemClk:%d\r\n",SystemCoreClock);printf( "ChipID:%08x\r\n", DBGMCU_GetCHIPID() );adcFlag = 1 ;ADCConfig();;while(1){if( adcFlag == 1 ){adcReading = ADC_GetInjectedConversionValue( ADC1 , ADC_InjectedChannel_1 );adcReading = (adcReading * 325)/100; // Converting to real voltage w.r.t. VCC 3.3V, 0.985 multiplier factor is used with 3.3V for calibration. 330 * 0.985 = 325. This was as per my board, you might need to do it with different multiplier value.adcFlag = 0;printf(" AdcIn = %d \r\n" , adcReading );// Restart Read ADC : //ADC_SoftwareStartInjectedConvCmd(ADC1, ENABLE);Delay_Ms(500);}}return(0);}///-- Welcome !