I finally got the ADC to work with my code, so it will be possible for me to investigate it further.
You can use the Arduino Framework with analogRead, but it comes with a lot of limitations. The conversion time duration is very long and about 150 us. In the last part of analogRead, the AD-converter is set in Deep-power-down mode and the two clock signals to the ADC is removed in the RCC system. If you want to discover how analogRead uses the ADC, you cannot read the relevant registers afterword's to get information.
The ADCs use two different clock inputs, that can be switched on and off separately by the RCC controller. One input is for the digital interface, and the other is for the AD-conversion. But the clock for the AD-conversion can be made in more different ways and with different prescalers and switches needed to pass the clock signal. The clock configuration diagrams given seem misleading, and it is not always clear which of the two clock signals, that the functions address.
The Arduino Framework sets the Nucleo-STM32G474RE to use the HSE 24 MHz crystal clock input to control the PLL, so it provides a 170 MHz system clock. You might see the STM32CubeMX Clock Configuration diagram. It is not the default CubeMX setting, because it utilizes the HSI RC oscillator. Furthermore, the Arduino framework sets the divider before the PLLP output to 2. The ADCs are left in deep power down-mode with no clocks.
I have used AI for many questions. It is often helpful, but sometimes you get misleading answers. I had an issue with reading somewhat wrong levels. After some time, I found an error regarding a default channel setting of differential input, but I should set single ended channel setting. AI had proposed code without setting the hal data structure for single ended input. And when I asked about the wrong readings, the answer was that I did not calibrate the ADC as I should. I lost a lot of time here...
With an ADC clock of 42.5 MHz, I get a conversion time duration with HAL functions of about 3.3 us. If you disable the ADC after conversion the time increases to 5.64 us. With bare metal code using the registers as proposed by ag123, the time is reduced to 0.91 us.
I have not seen any significant reduction in the ADC noise by reducing the ADC clock below 42.5 MHz. I guess you should utilize other methods to reduce noise in the signal.
This is the code I used to also investigate the settings of the RCC clock and the ADC:
Code: Select all
/*
Tests with Nucleo-64 STM32G474RE summer 2026
Test of Analog input on via HAL procedures
Test of Digital input output via Arduino Framework
Test of Serial Monitor
*/
// the setup function runs once when you press reset or power the board
const uint32_t mask_RCC_CCIPR_ASC12_Clksource = 0B00110000000000000000000000000000;
const uint32_t set_RCC_CCIPR_ADC12_Clksource_PLL = 0B00010000000000000000000000000000;
const uint32_t set_RCC_CCIPR_ADC12_Clksource_sys = 0B00100000000000000000000000000000;
// 10987654321098765432109876543210
uint32_t RCCregister;
uint32_t ADCregister;
int potmeter = 0;
uint32_t adc_value;
// Initialization data structures for HAL functions
GPIO_InitTypeDef GPIO_InitStruct;
ADC_ChannelConfTypeDef sConfig;
ADC_HandleTypeDef hadc1{};
ADC_HandleTypeDef hadc2{};
ADC_HandleTypeDef hadc3{};
ADC_HandleTypeDef hadc4{};
ADC_HandleTypeDef hadc5{};
// Function to read out a 32bit hardware registers for me to analyze
void println32bin(uint32_t i) {
if (i<2147483648) Serial.print("0"); //32 bits
if (i<1073741824) Serial.print("0");
if (i<536870912) Serial.print("0");
if (i<268435456) Serial.print("0");
if (i<134217728) Serial.print("0"); //28 bits
if (i<67108864) Serial.print("0");
if (i<33554432) Serial.print("0");
if (i<16777216) Serial.print("0");
if (i<8388608) Serial.print("0"); //24 bits
if (i<4194304) Serial.print("0");
if (i<2097152) Serial.print("0");
if (i<1048576) Serial.print("0");
if (i<524288) Serial.print("0");
if (i<262144) Serial.print("0");
if (i<131072) Serial.print("0");
if (i<65536) Serial.print("0");
if (i<32768) Serial.print("0"); //16 bits
if (i<16384) Serial.print("0");
if (i<8192) Serial.print("0");
if (i<4096) Serial.print("0");
if (i<2048) Serial.print("0");
if (i<1024) Serial.print("0");
if (i<512) Serial.print("0");
if (i<256) Serial.print("0");
if (i<128) Serial.print("0");
if (i<64) Serial.print("0");
if (i<32) Serial.print("0");
if (i<16) Serial.print("0");
if (i<8) Serial.print("0");
if (i<4) Serial.print("0");
if (i<2) Serial.print("0");
Serial.println(i,BIN);
Serial.println("10987654321098765432109876543210");
};
void setup() {
// initialize digital pin LED_BUILTIN as an output. Prøver nu i stedet A2.
delay(8000);
pinMode(A2, OUTPUT); // External LED and in series with 180 ohm to GND. It is PA4
pinMode(A5, INPUT); // Switch is connected to VCC and recistor to GND. It is PC0
pinMode(LED_BUILTIN, OUTPUT); // LD2 on board. It is PA5
Serial.begin(115200);
// Checks some settings
// 1. Specify which ADC to initialize
// 2. Configure ADC Parameters
//hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV4; //SYNC clock do work, but ASYNX clock don't
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.SamplingMode = LL_ADC_REG_SAMPLING_MODE_NORMAL;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.OversamplingMode = DISABLE;
hadc2 = hadc1;
hadc3 = hadc1;
hadc4 = hadc1;
hadc5 = hadc1;
hadc1.Instance = ADC1;
hadc2.Instance = ADC2;
hadc3.Instance = ADC3;
hadc4.Instance = ADC4;
hadc5.Instance = ADC5;
// Use of Arduino framework to input analog isgnal
pinMode(PA0, INPUT_ANALOG);
analogReadResolution(12);
potmeter = analogRead(A0); //After this reading, the ADC enters Deep Power Down mode and all RCC clocks for ADC are removed.
Serial.println("Potvalue from analogRead: ");
Serial.println(potmeter);
//HAL code for setting input analog pin
GPIO_InitStruct.Pin = GPIO_PIN_0; // Change to your specific pin
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
__HAL_RCC_ADC12_CLK_ENABLE(); //changes bit 13 in RCC_AHB2ENR to 1 to enable the ADC12 digital interface clock
__HAL_RCC_PLLCLKOUT_ENABLE(RCC_PLL_ADCCLK); //should activate output PLLP. Division should already be 2 from Arduino setting
//it do set bit 17 in RCC_PLLCFGR
//Here you set the AD-conversion clock source to ADC12.
//LL_RCC_SetADCClockSource(LL_RCC_ADC12_CLKSOURCE_PLL); //seting bits 28,29 in RCC_CCIPR. Function seems not declared in Arduino framework
//Then this is used instead:
RCC->CCIPR = (RCC->CCIPR & ~mask_RCC_CCIPR_ASC12_Clksource) | set_RCC_CCIPR_ADC12_Clksource_PLL; //sets ADC12 clock source
//Some status output for checks
RCCregister = RCC->AHB2ENR;
Serial.println("RCC_AHB2ENR: ");
println32bin(RCCregister);
ADCregister = ADC12_COMMON->CCR;
Serial.println("ADC12_CCR: ");
println32bin(ADCregister);
ADCregister = ADC1->CR; //At this point the ADC1 is in Deep-power-down (default reset state)
Serial.println("ADC1_CR: ");
println32bin(ADCregister);
// Now other HAL initialization
// 1. Stop current ADC conversion
if (HAL_ADC_Stop(&hadc1) == HAL_OK )
Serial.println("ADC1 stopped OK");
else
Serial.println("ADC1 error in stop");
if (HAL_ADC_Stop(&hadc2) == HAL_OK )
Serial.println("ADC2 stopped OK");
else
Serial.println("ADC2 error in stop");
// 2. Configure the new channel (e.g., ADC_CHANNEL_1)
sConfig.Channel = ADC_CHANNEL_1; // Target new ADC Channel
sConfig.Rank = ADC_REGULAR_RANK_1; // Set as first rank in sequence
//sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; // Adjust as needed
sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED; // Very important initialization not documented everywhere
sConfig.OffsetNumber = ADC_OFFSET_NONE;
//sConfig.Offset = 1000;
//sConfig.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
//sConfig.OffsetSaturation = DISABLE;
switch (HAL_ADC_ConfigChannel(&hadc1, &sConfig)) {
case HAL_OK:
Serial.println("ADC1 Channel Config HAL_OK");
break;
case HAL_ERROR:
Serial.println("ADC1 Channel Config HAL_ERROR");
break;
case HAL_BUSY:
Serial.println("ADC1 Channel Config HAL_BUSY");
break;
case HAL_TIMEOUT:
Serial.println("ADC1 Channel Config HAL_TIMEOUT");
break;
default:
Serial.println("ADC1 Channel Config HAL_Otherwise");
break;
};
// Initialization of ADC1 from HAL ADC data structure
switch (HAL_ADC_Init(&hadc1)) {
case HAL_OK:
Serial.println("ADC1 Init HAL_OK");
break;
case HAL_ERROR:
Serial.println("ADC1 Init HAL_ERROR");
break;
case HAL_BUSY:
Serial.println("ADC1 Init HAL_BUSY");
break;
case HAL_TIMEOUT:
Serial.println("ADC1 Init HAL_TIMEOUT");
break;
default:
Serial.println("ADC1 Init HAL_Otherwise");
break;
};
delay(10);
//Prints some status regarding registers of ADC and RCC
ADCregister = ADC12_COMMON->CCR;
Serial.println("ADC12_CCR: ");
println32bin(ADCregister);
ADCregister = ADC1->CR;
Serial.println("ADC1_CR: ");
println32bin(ADCregister);
//Dump of important RCC registers
RCCregister = RCC->CR;
Serial.println("RCC_CR: ");
println32bin(RCCregister);
RCCregister = RCC->CFGR;
Serial.println("RCC_CFGR: ");
println32bin(RCCregister);
RCCregister = RCC->PLLCFGR;
Serial.println("RCC_PLLCFGR: ");
println32bin(RCCregister);
RCCregister = RCC->AHB2RSTR;
Serial.println("RCC_AHB2RSTR: ");
println32bin(RCCregister);
RCCregister = RCC->APB1RSTR1;
Serial.println("RCC_APB1RSTR1: ");
println32bin(RCCregister);
RCCregister = RCC->APB1RSTR2;
Serial.println("RCC_APB1RSTR2: ");
println32bin(RCCregister);
RCCregister = RCC->AHB2ENR;
Serial.println("RCC_AHB2ENR: ");
println32bin(RCCregister);
RCCregister = RCC->APB1ENR1;
Serial.println("RCC_APB1ENR1: ");
println32bin(RCCregister);
RCCregister = RCC->APB2ENR;
Serial.println("RCC_APB2ENR: ");
println32bin(RCCregister);
RCCregister = RCC->AHB2SMENR;
Serial.println("RCC_AHB2SMENR: ");
println32bin(RCCregister);
RCCregister = RCC->CCIPR;
Serial.println("RCC_CCIPR: ");
println32bin(RCCregister);
HAL_ADC_Start(&hadc1); //Start AD-conversion by ADC1
//LL_ADC_Enable(ADC1); //Alternative start command
//LL_ADC_REG_StartConversion(ADC1); //Alternative start command
//Print some ADC status just after start of conversion
ADCregister = ADC1->CR;
Serial.println("ADC1_CR when started: ");
println32bin(ADCregister);
ADCregister = ADC12_COMMON->CCR;
Serial.println("ADC12_CCR: ");
println32bin(ADCregister);
//Awaits ADC to finish conversion
switch (HAL_ADC_PollForConversion(&hadc1, 100)) {
case HAL_OK:
Serial.println("ADC1 Conversion HAL_OK");
break;
case HAL_ERROR:
Serial.println("ADC1 Conversion HAL_ERROR");
break;
case HAL_BUSY:
Serial.println("ADC1 Conversion HAL_BUSY");
break;
case HAL_TIMEOUT:
Serial.println("ADC1 Conversion HAL_TIMEOUT");
break;
default:
Serial.println("ADC1 Conversion HAL_Otherwise");
break;
};
adc_value = HAL_ADC_GetValue(&hadc1);
potmeter = adc_value;
Serial.print("Potvalue: ");
Serial.println(potmeter);
ADCregister = ADC1->CR;
Serial.println("ADC1_CR: ");
println32bin(ADCregister);
ADCregister = ADC12_COMMON->CCR;
Serial.println("ADC12_CCR: ");
println32bin(ADCregister);
};
// the loop function runs over and over again forever
void loop() {
digitalWrite(A2, digitalRead(A5)); // turn the A2 output according to A5 input
LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_5);
/*
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
adc_value = HAL_ADC_GetValue(&hadc1);
potmeter = adc_value;
*/
//HAL_ADC_Stop(&hadc1); // increases time from 3.3 us to 5.65 us. So 2.35 us longer to disable ADC between measurements this way.
//
//ADC using direct register manipulation below.
//The duration is 0.91 us with ADC clock of 42.5 MHz (or T=23.53 ns)
//Changing the ADC clock to 16 times slower makes T=376.6 ns and the conversion time 11.6 us.
//It means that a time difference of 30 ADC clock cycles. The Channel configuration of 12.5 ADC clock cycles sample time is likely
//somehow excecuted by the ADC hardware. Otherwise 30 ADC clock cycles don't make sense. Yes. This is confirmed by changing
//the channel configuration to 6.5 ADC clock cycles, and it reduces time from 11.6 us to 9.4 us.
//By the way. The slow ADC clock cause a significant change in the measured potvalue from about 1150 to 1350. This needs investigation.
//The input is very sensitive. Just putting a 10x oscilloprobe on makes a change from 1150 to 1230.
//Adding a 100 nF capacitor to GND changed reading to about 1350. Oscillocope confirm no shift in value by this decoupling. It is there
//with no capacitor. And with slow clock the voltage somehow recover with no capacitor. The capacitor do not reduce the
//noise seen in the measured value. Neither do the slow clock.
//
// 1. Clear previous conversion flags (flags are cleared by writing 1)
ADC1->ISR = ADC_ISR_EOC | ADC_ISR_EOS | ADC_ISR_OVR;
// 2. Start regular conversion
ADC1->CR |= ADC_CR_ADSTART;
// 3. Wait for conversion complete
while ((ADC1->ISR & ADC_ISR_EOC) == 0U) {
// wait
};
// 4. Read converted data
potmeter = (uint16_t)ADC1->DR;
LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_5);
Serial.print("Potvalue: ");
Serial.println(potmeter);
delay(1000); // wait for a 1 second
};
I have tried to make an enlarged clock diagram from page 597 in RM440 with focus on the ADC clock. But it is no official understanding of the design – only my beginner sketch. It is made after my faulty trials to make changes to the ADC clock frequency, so perhaps others might find this usefull. I cannot see a way to upload a picture here, so I make a link to the ST Community:
https://community.st.com/stm32-mcus-pro ... cus-167393