cancel
Showing results for 
Search instead for 
Did you mean: 

how to use the ADC the old-fashioned way.

Gaston
Senior

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

1 ACCEPTED SOLUTION

Accepted Solutions
KnarfB
Principal III
#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

View solution in original post

6 REPLIES 6
Andrew Neil
Evangelist III

@Gaston wrote:

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


So "Polling", then:

AndrewNeil_0-1722359756741.png

 

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

 

TDK
Guru

You can see an example project to convert a channel in polling mode here:

STM32CubeF4/Projects/STM32F413ZH-Nucleo/Examples/ADC/ADC_RegularConversion_Polling/Src/main.c at d7cb2bb74395b7f27e137da8adb5ad25f0aa0e78 · STMicroelectronics/STM32CubeF4 (github.com)

 

If you feel a post has answered your question, please click "Accept as Solution".

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 "Accept as Solution".
KnarfB
Principal III
#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

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

Gaston