Skip to main content
Graduate
July 30, 2024
Solved

how to use the ADC the old-fashioned way.

  • July 30, 2024
  • 3 replies
  • 1700 views

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.
    Best answer by KnarfB
    #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

    3 replies

    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

     

    GastonAuthor
    Graduate
    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

     

    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...

    Super User
    July 30, 2024
    KnarfBAnswer
    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

    GastonAuthor
    Graduate
    July 31, 2024

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

    Gaston