Skip to main content
Gaston
Associate III
July 30, 2024
Solved

how to use the ADC the old-fashioned way.

Hi,

I need to use the STM32C011F4 multichannel ADC like I used to 20 years ago with other microprocessors. That is:

  • Select ADC channel (0, 1, 5 or 6)
  • Start ADC conversion
  • Wait for EOC
  • Read ADC value
  • Stop ADC

In this application I don't want to use DMA or Interrupts. Can someone please help me with this? I'm not able to find any example of this basic operation.

gaston

This topic has been closed for replies.
KnarfB
KnarfBBest answer
Super User
July 30, 2024
#include <stm32c011xx.h>

// global variables can be read by cortex-debug live watch or STM32CubeMonitor
uint32_t adc_data_raw; // raw 12-bit ADC result
uint32_t adc_data_mV; // in millivolt

int main(void)
{
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // enable peripheral clock
 (void)RCC->IOPENR; // read back to make sure that clock is on
 // Set PA8 to analog input mode (mode 3) for ADC, other regs defaults are okay
 GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE8_Msk) | (3 << GPIO_MODER_MODE8_Pos);

 // let ADC (digital block) be clocked by: SYSCLK
 RCC->CCIPR = (RCC->CCIPR &~RCC_CCIPR_ADCSEL_Msk) | (0<<RCC_CCIPR_ADCSEL_Pos); 

 RCC->APBENR2 |= RCC_APBENR2_ADCEN; // turn ADC clock on
 (void)RCC->APBENR2; // read back to make sure that clock is on
 ADC1->CR |= ADC_CR_ADVREGEN; // power up ADC voltage regulator 

 // wait t_ADCVREG_STUP (ADC voltage regulator start-up time), 
 for(volatile int i=0; i<12*20; ++i); // min 20 µs see data sheet

 // do self calibration
 ADC1->CR |= ADC_CR_ADCAL;
 while(ADC1->CR & ADC_CR_ADCAL); // wait for calibration to finish
 uint8_t calibration_factor = ADC1->DR;

 ADC1->CFGR1 = 0; 	// default config after reset
 ADC1->CFGR2 = 0; 	// default config after reset
 ADC1->SMPR = 0; 	// sampling time register, default after reset

 // "enable the ADC" procedure from RM0490 Rev 3:
 ADC1->ISR |= ADC_ISR_ADRDY; // Clear the ADRDY bit in ADC_ISR register 
 ADC1->CR |= ADC_CR_ADEN; // Set ADEN = 1 in the ADC_CR register.
 while(!(ADC1->ISR & ADC_ISR_ADRDY)); // Wait until ADRDY = 1 in the ADC_ISR register

 ADC1->CALFACT = calibration_factor;

 // above: CHSELRMOD = 0 in ADC_CFGR1, so every channel has a bit. set bit to activate that channel
 ADC1->CHSELR = ADC_CHSELR_CHSEL8; // select channel ADC_IN8 which is PA8 connected to joystick
 while(!(ADC1->ISR & ADC_ISR_CCRDY)); // wait until channel configuration update is applied

 uint32_t Vdda_mV = 3300; // there are better ways to estimate Vdda

 while(1) {
 ADC1->CR |= ADC_CR_ADSTART; // start ADC conversion
 while(!(ADC1->ISR & ADC_ISR_EOC)); // wait for end of conversion
 adc_data_raw = ADC1->DR; // conversion done. store result
 adc_data_mV = (adc_data_raw * Vdda_mV) / 4095; // Vdda == 4095 digital reading
 }
}

hth

KnarfB

6 replies

Andrew Neil
Super User
July 30, 2024

@Gaston wrote:

In this application I don't want to use DMA or Interrupts


So "Polling", then:

AndrewNeil_0-1722359756741.png

 

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Gaston
GastonAuthor
Associate III
July 30, 2024

And how do you select the channel you want to convert? For example:

  • Select ADC channel 0
  • Start ADC conversion
  • Wait for EOC
  • Read ADC value
  • Stop ADC
  • Select ADC channel 4
  • Start ADC conversion
  • Wait for EOC
  • Read ADC value
  • Stop ADC

 

AScha.3
Super User
July 30, 2024

Hi,

just - how i would do it (kiss - Keep it Short and Simple :) )

setup with Cube the ADCx (adc1 if you want) regular conversion , set ch0 and sample time (7.5) , one conversion only.

(short "sequence" ) - gen. code , look at the program:  

HAL_ADC_Start(&hadc1, ...)  will convert this channel, ch0 , thats what you want.

 

But now you want ch4 : so just look, how Cube made the adc_init :

 

 /** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_0;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 

For ch4 : so just look, how Cube made the adc_init, copy it, and change it to ch4 .

 

 ADC_ChannelConfTypeDef sConfig = {0};
/** Configure Regular Channel
 */
 sConfig.Channel = ADC_CHANNEL_4;
 sConfig.Rank = ADC_REGULAR_RANK_1;
 sConfig.SingleDiff = ADC_SINGLE_ENDED;
 sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
 sConfig.OffsetNumber = ADC_OFFSET_NONE;
 sConfig.Offset = 0;
 if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
 {
 Error_Handler();
 }

 

call/run this then...ch4 is now the"sequence" .

HAL_ADC_Start(&hadc1, ...)  will convert this channel, ch4 , thats what you want now.

And so on...

If you feel a post has answered your question, please click on " Best Answer ".
TDK
July 30, 2024
"If you feel a post has answered your question, please click ""Accept as Solution""."
KnarfB
KnarfBBest answer
Super User
July 30, 2024
#include <stm32c011xx.h>

// global variables can be read by cortex-debug live watch or STM32CubeMonitor
uint32_t adc_data_raw; // raw 12-bit ADC result
uint32_t adc_data_mV; // in millivolt

int main(void)
{
 RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // enable peripheral clock
 (void)RCC->IOPENR; // read back to make sure that clock is on
 // Set PA8 to analog input mode (mode 3) for ADC, other regs defaults are okay
 GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE8_Msk) | (3 << GPIO_MODER_MODE8_Pos);

 // let ADC (digital block) be clocked by: SYSCLK
 RCC->CCIPR = (RCC->CCIPR &~RCC_CCIPR_ADCSEL_Msk) | (0<<RCC_CCIPR_ADCSEL_Pos); 

 RCC->APBENR2 |= RCC_APBENR2_ADCEN; // turn ADC clock on
 (void)RCC->APBENR2; // read back to make sure that clock is on
 ADC1->CR |= ADC_CR_ADVREGEN; // power up ADC voltage regulator 

 // wait t_ADCVREG_STUP (ADC voltage regulator start-up time), 
 for(volatile int i=0; i<12*20; ++i); // min 20 µs see data sheet

 // do self calibration
 ADC1->CR |= ADC_CR_ADCAL;
 while(ADC1->CR & ADC_CR_ADCAL); // wait for calibration to finish
 uint8_t calibration_factor = ADC1->DR;

 ADC1->CFGR1 = 0; 	// default config after reset
 ADC1->CFGR2 = 0; 	// default config after reset
 ADC1->SMPR = 0; 	// sampling time register, default after reset

 // "enable the ADC" procedure from RM0490 Rev 3:
 ADC1->ISR |= ADC_ISR_ADRDY; // Clear the ADRDY bit in ADC_ISR register 
 ADC1->CR |= ADC_CR_ADEN; // Set ADEN = 1 in the ADC_CR register.
 while(!(ADC1->ISR & ADC_ISR_ADRDY)); // Wait until ADRDY = 1 in the ADC_ISR register

 ADC1->CALFACT = calibration_factor;

 // above: CHSELRMOD = 0 in ADC_CFGR1, so every channel has a bit. set bit to activate that channel
 ADC1->CHSELR = ADC_CHSELR_CHSEL8; // select channel ADC_IN8 which is PA8 connected to joystick
 while(!(ADC1->ISR & ADC_ISR_CCRDY)); // wait until channel configuration update is applied

 uint32_t Vdda_mV = 3300; // there are better ways to estimate Vdda

 while(1) {
 ADC1->CR |= ADC_CR_ADSTART; // start ADC conversion
 while(!(ADC1->ISR & ADC_ISR_EOC)); // wait for end of conversion
 adc_data_raw = ADC1->DR; // conversion done. store result
 adc_data_mV = (adc_data_raw * Vdda_mV) / 4095; // Vdda == 4095 digital reading
 }
}

hth

KnarfB

Gaston
GastonAuthor
Associate III
July 31, 2024

Great! This is just what I needed. Many thanks also to AScha.3 and TDK

Gaston