Simultaneous regular mode with two ADCs (F303)

What are you developing?
User avatar
Pito
Posts: 107
Joined: Tue Dec 24, 2019 1:53 pm

Simultaneous regular mode with two ADCs (F303)

Post by Pito »

Hi, my HAL skills have got pretty rusty, so out of curiosity I asked AI to help with a simple code which will print out two ADC
channels conversions triggered simultaneously by TIM8 with a 100mS period.
After a couple of discussions with AI :D it suggested to me following code, which looks complete on the first glance, and it builds and flashes into my F303 fine.
The issue is it hangs on calling the ADC routine readADCValues(); (when I comment it out it prints out zero results).
If you perhaps may spot an obvious issue there I would appreciate..

Code: Select all

#include <Arduino.h>
#include <stm32f3xx_hal.h>

// Define ADC pins
#define ADC1_CHANNEL_PIN PA0  // Example: ADC1_IN1 (PA0)
#define ADC2_CHANNEL_PIN PA1  // Example: ADC2_IN2 (PA1)

// Timer configuration
#define TRIGGER_TIMER TIM8 // Use Timer 8 for triggering

// Global variables to store ADC values
volatile uint16_t adc1_value = 0;
volatile uint16_t adc2_value = 0;

// Handles for ADCs and Timer
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
TIM_HandleTypeDef htim8;

void setupADC() {
  // Enable clocks for ADCs, GPIO, and Timer
  __HAL_RCC_ADC12_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_TIM8_CLK_ENABLE();

  // Configure GPIO pins for ADC (PA0 for ADC1, PA1 for ADC2)
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  // ADC1 initialization
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  HAL_ADC_Init(&hadc1);

  // Configure ADC1 channel (PA0)
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_61CYCLES_5;
  HAL_ADC_ConfigChannel(&hadc1, &sConfig);

  // ADC2 initialization
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  HAL_ADC_Init(&hadc2);

  // Configure ADC2 channel (PA1)
  sConfig.Channel = ADC_CHANNEL_2;
  HAL_ADC_ConfigChannel(&hadc2, &sConfig);

  // Enable dual ADC mode for simultaneous sampling
  ADC_MultiModeTypeDef multimode = {0};
  multimode.Mode = ADC_DUALMODE_REGSIMULT;
  HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode);

  // Enable ADCs
  HAL_ADC_Start(&hadc1);
  HAL_ADC_Start(&hadc2);
}

void setupTimer() {
  // Configure TIM8 for trigger generation
  htim8.Instance = TIM8;
  htim8.Init.Prescaler = (SystemCoreClock / 10000) - 1; // Set timer clock to 10 kHz
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim8.Init.Period = 1000 - 1; // Trigger every 100 ms (10 Hz)
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  HAL_TIM_Base_Init(&htim8);

  // Configure TIM8 to generate TRGO (trigger output)
  TIM_MasterConfigTypeDef masterConfig = {0};
  masterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; // Trigger on timer update event
  masterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htim8, &masterConfig);

  // Start the timer
  HAL_TIM_Base_Start(&htim8);
}

void readADCValues() {
  // Wait for conversion to complete
  while (!__HAL_ADC_GET_FLAG(&hadc1, ADC_FLAG_EOC)) {}
  while (!__HAL_ADC_GET_FLAG(&hadc2, ADC_FLAG_EOC)) {}

  // Read ADC values
  adc1_value = HAL_ADC_GetValue(&hadc1);
  adc2_value = HAL_ADC_GetValue(&hadc2);
}

void setup() {
  Serial.begin(9600);

  setupADC();
  setupTimer();

  Serial.println("Setup complete. Starting ADC sampling...");
}

void loop() {
  // Read ADC values (updated after each timer-triggered conversion)
  readADCValues();

  // Print ADC values
  Serial.print("ADC1 Value: ");
  Serial.print(adc1_value);
  Serial.print("\tADC2 Value: ");
  Serial.println(adc2_value);

  //delay(100);
}
Pukao Hats Cleaning Services Ltd.
ag123
Posts: 1881
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: Simultaneous regular mode with two ADCs (F303)

Post by ag123 »

well, I'd guess we'd need to keep the ref manual handy
https://www.st.com/resource/en/referenc ... ronics.pdf

the HAL codes on its own is quite complex, one of those ways is to use CubeMX or STM32CubeIDE to generate codes.

And thereafter, adapt them in stm32duino. It'd likely integrate well as 'official' core is based and developed around HAL.
But I'd guess a possible likely conflict could be with the 'built-in' analogRead() etc, it'd take reviewing the codes if it happens.

I took a look at that code, it is a little strange as it seem to be based on timer triggering and based on single conversion.
That'd probably work and a good thing is that you can vary the sampling rates from the timer.
Other things which seemed missing are the sampling time settings to get a better sample.
DMA is probably the other thing to look into, if you want to read a series of samples.

In terms of the 'multi' mode, it seemed it is setting it for simultaneous multi mode rather than interleaved mode.
Interleaved mode is probably what you wanted if you are intending to run both of them to get higher sample speeds.

The other thing it seemed it is based on single conversion mode, if you want a block of data at the highest sample rates, it'd probably take continuous mode and DMA.

oh and note it seem to set the timer to 10 samples per sec :lol:

and oh, there is something curious when I looked at the ref manual
chapter 20 Advanced-control timers (TIM1/TIM8/TIM20)
Figure 140. Advanced-control timer block diagram

the TRIGO output don't seem to be connected to the timer outputs !
instead that seem fed from section 20.3.4 External trigger input.
to let the timers directly trigger the ADC, you may need to setup one of the CC channels and to configure the ADC to perhaps sample on the falling edge. that would practically mean you can 'pwm' the ADC sampling :lol:

perhaps you may want to try different queries there :)
Last edited by ag123 on Mon Jan 20, 2025 7:26 am, edited 9 times in total.
ag123
Posts: 1881
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: Simultaneous regular mode with two ADCs (F303)

Post by ag123 »

in the ref manual rm0316 STM32F303xB/C/D/E, STM32F303x6/8, STM32F328x8,
STM32F358xC, STM32F398xE advanced Arm®-based MCUs
https://www.st.com/resource/en/referenc ... ronics.pdf
chapter 15 Analog-to-digital converters (ADC)
This section describes the implementation of up to 4 ADCs:
• ADC1 and ADC2 are tightly coupled and can operate in dual mode (ADC1 is master).
• ADC3 and ADC4 are tightly coupled and can operate in dual mode (ADC3 is master).
other 'off-topic' but interesting details are like
ADC1 internal channels
1 channel
connected to
temperature
sensor.
– 1 channel
connected to
VBAT/2
– 1 channel
connected to
VREFINT
– 1 channel
connected to
OPAMP1 reference
voltage output
(VREFOPAMP1).
etc, ^ above shows how to 'connect' the opamp if one wants to, useful for current measurements and small voltages.
15.3.29 Dual ADC modes
In devices with two ADCs or more, dual ADC modes can be used (see Figure 97):
• ADC1 and ADC2 can be used together in dual mode (ADC1 is master)
• ADC3 and ADC4 can be used together in dual mode (ADC3 is master)
In dual ADC mode the start of conversion is triggered alternately or simultaneously by the
ADCx master to the ADC slave, depending on the mode selected by the bits DUAL[4:0] in
the ADCx_CCR register.
Four possible modes are implemented:
• Injected simultaneous mode
• Regular simultaneous mode
• Interleaved mode
• Alternate trigger mode
It is also possible to use these modes combined in the following ways:
• Injected simultaneous mode + Regular simultaneous mode
• Regular simultaneous mode + Alternate trigger mode
...
In regular simultaneous or interleaved modes: once the user sets bit ADSTART or bit
ADSTP of the master ADC, the corresponding bit of the slave ADC is also automatically
set. However, bit ADSTART or bit ADSTP of the slave ADC is not necessary cleared at the
same time as the master ADC bit.
...
^ this kind of says that interleaving the 2 interleaved ADC groups would take some 'tricks' if you want to do 4x5 Msps
further down
DMA requests in dual ADC mode
^ this is likely also relevant

I took a look at the chapter on DMA, the DMA architecture looks closer to F1 (channels) than F4 (channels and streams).
in F4 the streams are parallel, that could mean much faster transfers if DMA is used for simultaneous different purposes.
User avatar
Pito
Posts: 107
Joined: Tue Dec 24, 2019 1:53 pm

Re: Simultaneous regular mode with two ADCs (F303)

Post by Pito »

Thanks!

I've tried the Cube, looked for an F303 ADC example which would build/flash/run here, but after perhaps 6 hours of messing with it (and downloading aprox 6GB of some packages etc) I've uninstalled it.
What is left is the CubeProgrammer as it makes DFU working in Arduino with your STM32 environment..

So I have to investigate how to get the simultaneous ADC mode working in this stm32duino environment. I do not target DMA and high speed conversion at this moment, I do step by step, and the first step is to get the simultaneous regular mode with ADC1 and ADC2 triggered by a timer output..

Btw. - this ADC mode is pretty important these days as all modern signal processing is based on I/Q for which you need that ADC mode..

PS: below another version I've elaborated, which throws the error in "HAL_ADC_Init ADC1" off my Error_Handler() routine (serial over USB works fine)..

Code: Select all

#include <Arduino.h>
#include <stm32f3xx_hal.h>

// Define ADC handles
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;

// Define Timer 8 handle
TIM_HandleTypeDef htim8;

// Define the ADC pin numbers
#define ADC1_CHANNEL    PA1  // For example, PA1
#define ADC2_CHANNEL    PA2  // For example, PA2

// Define a simple error handler
void Err_handler(const char *routine_name) {
  Serial.print("Error occurred in: ");
  Serial.println(routine_name);
  while (1);  // Stay here indefinitely for debugging
}

// Timer 8 initialization for triggering ADC conversion every 100ms
void Timer8_Init() {
  __HAL_RCC_TIM8_CLK_ENABLE();

  htim8.Instance = TIM8;
  htim8.Init.Prescaler = 7200 - 1;  // Prescaler value (for 72MHz clock -> 10kHz)
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim8.Init.Period = 1000 - 1;  // 100ms period (10kHz / 1000 = 10Hz)
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim8.Init.RepetitionCounter = 0;
  
  if (HAL_TIM_Base_Init(&htim8) != HAL_OK) {
    Err_handler("HAL_TIM_Base_Init");
  }

  if (HAL_TIM_PWM_Init(&htim8) != HAL_OK) {
    Err_handler("HAL_TIM_PWM_Init");
  }

  // Configure Timer 8 trigger output (TRGO)
  TIM_MasterConfigTypeDef sMasterConfig;
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;  // TRGO on update event
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

  if (HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig) != HAL_OK) {
    Err_handler("HAL_TIM_MasterConfigSynchronization");
  }

  if (HAL_TIM_Base_Start(&htim8) != HAL_OK) {
    Err_handler("HAL_TIM_Base_Start");
  }
}

// ADC initialization in simultaneous mode
void ADC_Init() {
  __HAL_RCC_ADC1_CLK_ENABLE();
  __HAL_RCC_ADC2_CLK_ENABLE();

  // Configure ADC1
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE; // Enable scanning (multiple channels)
  hadc1.Init.ContinuousConvMode = DISABLE;   // Disable continuous mode
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.NbrOfDiscConversion = 0;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO; // Triggered by Timer 8 TRGO
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;  // Two channels (Simultaneous mode)
  hadc1.Init.DMAContinuousRequests = DISABLE;

  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Err_handler("HAL_ADC_Init ADC1");
  }

  // Configure ADC2
  hadc2.Instance = ADC2;
  hadc2.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.NbrOfDiscConversion = 0;
  hadc2.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO; // Triggered by Timer 8 TRGO
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.NbrOfConversion = 2;  // Simultaneous mode for both ADCs
  hadc2.Init.DMAContinuousRequests = DISABLE;

  if (HAL_ADC_Init(&hadc2) != HAL_OK) {
    Err_handler("HAL_ADC_Init ADC2");
  }

  // Configure ADC regular channels for both ADC1 and ADC2
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
  sConfig.Rank = 1;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;

  // Channel 1 for ADC1 (PA1)
  sConfig.Channel = ADC1_CHANNEL;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    Err_handler("HAL_ADC_ConfigChannel ADC1");
  }

  // Channel 2 for ADC2 (PA2)
  sConfig.Channel = ADC2_CHANNEL;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK) {
    Err_handler("HAL_ADC_ConfigChannel ADC2");
  }
}

// Start ADC conversions
void Start_ADC() {
  if (HAL_ADC_Start(&hadc1) != HAL_OK) {
    Err_handler("HAL_ADC_Start ADC1");
  }

  if (HAL_ADC_Start(&hadc2) != HAL_OK) {
    Err_handler("HAL_ADC_Start ADC2");
  }
}

void setup() {

  Serial.begin(9600); // Initialize serial communication

  Serial.println("ADC1 ADC2 Sim conversion test ");
  delay(1000);

  Timer8_Init();  // Initialize Timer 8

  ADC_Init();     // Initialize ADCs in simultaneous mode

  Start_ADC();    // Start ADCs
}

void loop() {
  // Wait for ADC conversion to complete
  if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
    uint32_t adc1_result = HAL_ADC_GetValue(&hadc1); // Read ADC1 result
    Serial.print("ADC1 Result: ");
    Serial.println(adc1_result);
  }

  if (HAL_ADC_PollForConversion(&hadc2, 100) == HAL_OK) {
    uint32_t adc2_result = HAL_ADC_GetValue(&hadc2); // Read ADC2 result
    Serial.print("ADC2 Result: ");
    Serial.println(adc2_result);
  }

  delay(100); // Delay (100ms)
}
Pukao Hats Cleaning Services Ltd.
ag123
Posts: 1881
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: Simultaneous regular mode with two ADCs (F303)

Post by ag123 »

I'm thinking an 'easy' way to test if the ADC is triggering is to do a software trigger

I asked 'chatGPT' ;)
it says

Code: Select all

HAL_ADC_Start(&hadc1); 
in libmaple I used the 'lazy' way and simply toggle the SW_START bit in the register :lol:

poll for EOC
then read the value

Code: Select all

HAL_ADC_GetValue(&hadc1);
btw this is what ChatGPT posted:

Code: Select all

#include "stm32f4xx_hal.h"

ADC_HandleTypeDef hadc1;

void ADC_Init(void)
{
    __HAL_RCC_ADC1_CLK_ENABLE();  // Enable ADC1 clock

    hadc1.Instance = ADC1;
    hadc1.Init.Resolution = ADC_RESOLUTION_12B;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    HAL_ADC_Init(&hadc1);  // Initialize the ADC peripheral
}

void ADC_Start_Conversion(void)
{
    HAL_ADC_Start(&hadc1);  // Start the ADC conversion

    // Wait for the conversion to be completed
    if (HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
    {
        uint32_t adc_value = HAL_ADC_GetValue(&hadc1);  // Get the ADC result
        // Now you can use adc_value for further processing
    }
}

int main(void)
{
    HAL_Init();
    ADC_Init();

    while (1)
    {
        ADC_Start_Conversion();
        HAL_Delay(500);  // Delay before next conversion
    }
}

I'd guess in stm32duino that'd normally be

Code: Select all

analogRead() 
:lol:
ag123
Posts: 1881
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: Simultaneous regular mode with two ADCs (F303)

Post by ag123 »

btw I used stm32cubeIDE to generate 'sample' codes, e.g. for ADC etc as it'd generate HAL codes, then copy them over into stm32duino and work further on them.

these days it'd seem those 'AI' options out there are viable as well.
User avatar
Pito
Posts: 107
Joined: Tue Dec 24, 2019 1:53 pm

Re: Simultaneous regular mode with two ADCs (F303)

Post by Pito »

I've installed the MX and IDE again, configured the ADC12 and TIM8 best I could, and got a source. I will try to embed it into the above duino..
Pukao Hats Cleaning Services Ltd.
ag123
Posts: 1881
Joined: Thu Dec 19, 2019 5:30 am
Answers: 30

Re: Simultaneous regular mode with two ADCs (F303)

Post by ag123 »

@Pito
I think the thing about using timer to trigger the ADC is that those TRIGO signals won't do as those are literally external inputs.
To trigger the ADC from the timer, I think it'd take one of the CC (capture compare) channels. Hence, you'd need to look into that if you want to trigger the ADC directly from timers.

I used to have some codes that deal with the ADC (well it is still there), what I did is to trigger the ADC by 'software trigger' from the timer ISR
https://github.com/ag88/GirinoSTM32F103 ... r.cpp#L131

Code: Select all

void CAdcMgr::adctimerhandle(void) {
	//start conversion when called
	ADC1->regs->CR2 |= ADC_CR2_SWSTART;
}
you could practically adapt that for F303, but that it is originally based on F103 and libmaple.

The trouble with using the timer ISR is that it max out at about 500 khz / k samp per secs, I think a limit of the NVIC.
but that well 'it works' ;)
User avatar
Pito
Posts: 107
Joined: Tue Dec 24, 2019 1:53 pm

Re: Simultaneous regular mode with two ADCs (F303)

Post by Pito »

BTW how can I install the json cmsis package

https://github.com/stm32duino/ArduinoMo ... index.json

I put it into the Additional boards manager urls, but I do not see it there, or ?
Pukao Hats Cleaning Services Ltd.
GonzoG
Posts: 481
Joined: Wed Jan 15, 2020 11:30 am
Answers: 36
Location: Prudnik, Poland

Re: Simultaneous regular mode with two ADCs (F303)

Post by GonzoG »

That's not a board package, like stm32duino.
And you don't have to install it separately. It's installed with stm32duino.
Post Reply

Return to “Projects”