In STM32F103, How to create two timer interrupt running without disturb each other?

Using the STM32F103C8

  • One timer - Trigger - every 10microseconds
  • second timer - Trigger - every 100microseconds
    when I create two timer interrupt with above condition, problem one timer interrupt function is disturbing other timer interrupt.
    Here I used the Hardware timer library from Arduino_STM32 core library

For Hardware details, please refer this post link
cc: maxgerhardt

The timers run in parallel, in hardware, when you use different timer instances (e.g., TIM2, TIM3, etc.).

But the CPU that executes the interrupt service routines (aka, the functions that get triggered by the timer) does not run in parallel. So, if one takes a long time, it will block the execution of the other ISR too, or get interrupted itself (depending on interrupt priority in the NVIC).

Do you have an exact piece of code that behaves unexpectedly? Hard to say anything otherwise.

With Timer1 Code - DAC Output - every 10microseconds - generate 1kHz Sine wave

void DAC_analogWrite_B0_B7(uint8_t _dat1)
{
  GPIOB->ODR = (GPIOB->ODR & 0xFFFFFF00) | (_dat1);
}

void DAC_sine_wave(int _frequency0)
{
  int time_us = 1000000 / (_frequency0 * _no_of_sample_per_sine); // Microseconds
  uint32_t _temp1_ovf = CPU_FREQ / _timer1_prescaler; // For one seconds time
  _temp1_ovf *= time_us;

  _temp1_ovf /= 1000; // uS -> mS
  _temp1_ovf /= 1000; // mS -> Seconds

  timer1_DAC.setOverflow(_temp1_ovf);
  // timer1_DAC.refresh();
  // timer1_DAC.resume();
}

void timer1_setup()
{
  timer1_DAC.setPrescaleFactor(_timer1_prescaler);
  DAC_sine_wave(100);//timer1_DAC.setOverflow(32761);
  timer1_DAC.attachInterrupt(OnTimer1Interrupt);
  timer1_DAC.refresh();
  timer1_DAC.resume();
}

void OnTimer1Interrupt()
{
  DAC_analogWrite_B0_B7(sine_data[_pos_sine_data]);
  if (_pos_sine_data >= (_no_of_sample_per_sine - 1))
    _pos_sine_data = 0;
  else
    _pos_sine_data++;
}

With Timer 2 - ADC Measure - every 100microseconds - to store last 50 value

void store_VI_data(int16_t _sdata, bool _mode0)
{
  if (!_mode0)
  {
    if (volt_sample_count < _sample_size)
    {
      volt_raw_data[volt_sample_count] = _sdata;
      volt_sample_count++;
    }
    else
    {
      // volt_sample_count = 0;
      for (int _c = 0; _c < (_sample_size - 1); _c++)
        volt_raw_data[_c] = volt_raw_data[_c + 1];
      volt_raw_data[_sample_size - 1] = _sdata;
    }
  }
}

void timer2_time_gap(int16_t _measure_freq, int _n_samples)
{
  int _time_us = 1000000 / (_measure_freq * _n_samples); // Microseconds
  uint32_t _temp2_ovf = CPU_FREQ / timer2_prescaler;     // Timer frequency
  _temp2_ovf *= _time_us;

  _temp2_ovf /= 1000; // uS -> mS
  _temp2_ovf /= 1000; // mS -> Seconds

  timer2.setOverflow(_temp2_ovf); // Set overflow value
}

// Interrupt service routine for the timer
void OnTimer2Interrupt()
{
  int16_t adc_pa0 = analogRead(PA0);
  store_VI_data(adc_pa0, 0); // Store Voltage data
  print_if_adc_read = 1;
}

void timer2_setup()
{
  // Configure the timer
  timer2.setPrescaleFactor(timer2_prescaler); // Set prescaler
  timer2_time_gap(100, 10);                     // Set overflow value 100Hz,10 sample per wave
  timer2.attachInterrupt(OnTimer2Interrupt);  // Attach the interrupt
  timer2.refresh();                           // Apply changes
  timer2.resume();                            // Start the timer
}

If I enable the second timer(Timer 2) - DAC Output 1kHz frequency got disturbed.

Oh no. Very wrong approach. analogRead() will be blocking for quite some time to start the ADC and wait for the measurement result. Thus you’ll be blocking OnTimer1Interrupt() to be executed (if its priority is lower). Blocking is exactly what you should not do inside an interrupt. This needs to finish as fast as possible. Starting the ADC conversion in there is far too late.

The Timer and ADC peripherals of the STM32(F1) can play together very nicely here: The timer can generate TRGO (trigger out) events at a specified rate, e.g., 100Hz. The ADC can then be setup to start a conversion from a trigger, that will e.g. be the TRGO of a timer. Then, when the ADC conversion (measurement) finishes, an ADC interrupt fires and a function of yours can be invoked so that you can retrieve the prepared result and save it in a buffer.

In fact, even the “save ADC result into a buffer” can be done automatically if you involve the DMA (direct memory access) peripheral with a Peripheral2Memory transfer.

The point is that you want to push as many responsibilities you can from the software (CPU) to hardware (ADC, TIM, DMA, …). Don’t fetch the ADC result into a buffer by software if there’s a peripheral that can do it for you in hardware. Don’t start the ADC by software if there’s a peripheral that can do it for you in hardware.

The only problem here is that this whole setup is not covered by the Arduino APIs at all. They don’t give you the possibility to configure the ADC for e.g., continuous conversion, or being triggered by a timer / TRGO. Other Arduino cores are better here, providing a dedicated ADC library with more functionality, but they are also outdated.

Look at resources like

The same thing can be said about you generating your sine wave. After all, this can be realized by periodically triggering a DMA transfer from memory (RAM) to the GPIO->ODR register.

1 Like

Hi @maxgerhardt

With your suggest I tried different way of ADC setup to read data. Let me explain

  1. Setting up Timer 3 to Output TRGO
  2. Setting up ADC to Trigger on Timer 3 TRGO
  3. From the ADC Conversion Complete Interrupt I will store the ADC data
  4. Start the ADC

Problem

If I Enable ADC interrupt in NVIC, STM32 get into Hang or stuck mode on same line. Also I tried by set ADC interrupt to lower priority
HAL_NVIC_EnableIRQ(ADC1_2_IRQn);


Complete code details

#include <Arduino.h>
#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim3;
ADC_HandleTypeDef hadc1;
volatile uint16_t adcValue = 0;

void setupTimerTRGO() {
    __HAL_RCC_TIM3_CLK_ENABLE();
    
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 7200 - 1;  // Set timer to 10 kHz
    htim3.Init.Period = 100 - 1;      // 100 Hz TRGO frequency
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_Base_Init(&htim3);

    TIM_MasterConfigTypeDef masterConfig = {0};
    masterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;  // Trigger on update
    masterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim3, &masterConfig);

    HAL_TIM_Base_Start(&htim3);
}

void setupADCWithTimerTrigger() {
    __HAL_RCC_ADC1_CLK_ENABLE();
    
    hadc1.Instance = ADC1;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;  // Triggered by Timer 3 TRGO
    hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    hadc1.Init.NbrOfConversion = 1;

    HAL_ADC_Init(&hadc1);

    ADC_ChannelConfTypeDef adcChannelConfig = {0};
    adcChannelConfig.Channel = ADC_CHANNEL_0;  // PA0
    adcChannelConfig.Rank = 1;
    adcChannelConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5;
    HAL_ADC_ConfigChannel(&hadc1, &adcChannelConfig);
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    if (hadc->Instance == ADC1) {
        adcValue = HAL_ADC_GetValue(hadc);
    }
}

void setupADCInterrupt() {
    HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);  
    HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
    HAL_ADC_Start_IT(&hadc1);
}

void setup() {
    HAL_Init();
    Serial.begin(115200);
    pinMode(PC13,OUTPUT);
    setupTimerTRGO();
    setupADCWithTimerTrigger();
    setupADCInterrupt();
}
bool ledstate=0;
void loop() {
    Serial.print("ADC Value: ");
    Serial.println(adcValue);
    delay(1000);
    digitalWrite(PC13, ledstate);
    ledstate = !ledstate;
}

You’re missing the interrupt handler for the ADC that actually triggers this callback.

extern "C" void ADC1_2_IRQHandler(void) {
  HAL_ADC_IRQHandler(&hadc1);
}

I’ve verified that this example works fine:

#include <Arduino.h>

ADC_HandleTypeDef hadc1;
TIM_HandleTypeDef htim3;

void init_adc_and_tim() {
    __HAL_RCC_ADC1_CLK_ENABLE();

  ADC_ChannelConfTypeDef sConfig = {0};
  /* Common config */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Error_Handler();
  }

  /* Configure Regular Channel */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){
    Error_Handler();
  }
  /* TIM3 Init */
  __HAL_RCC_TIM3_CLK_ENABLE();
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 7200 - 1;
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 100 - 1;
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim3) != HAL_OK) {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK) {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK) {
    Error_Handler();
  }

  // Enable interrupt for ADC1_2
  HAL_NVIC_SetPriority(ADC1_2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(ADC1_2_IRQn);
}

/* interrupt handler for ADC */
extern "C" void ADC1_2_IRQHandler(void) {
  HAL_ADC_IRQHandler(&hadc1);
}

static volatile uint16_t adcValue = 0;
static volatile bool adcChanged = false;

/* called by the HAL ADC in interrupt context when conversion is complete */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
    if (hadc->Instance == ADC1) {
        adcValue = (uint16_t) HAL_ADC_GetValue(hadc);
        adcChanged = true;
    }
}

void setup() {
    Serial.begin(921600);
    // use one analogRead to setup pin correctly in analog mode
    (void) analogRead(PA0);
    init_adc_and_tim();
    // kick off timer to generate TRGO events
    HAL_TIM_Base_Start(&htim3);
    // kick off process once
    HAL_ADC_Start_IT(&hadc1);
}

void loop() {
    if (adcChanged) {
        __disable_irq(); // so that the value doesn't change as we read it out
        String line = "[" + String(millis()) + "] New ADC value: " + String(adcValue);
        adcChanged = false;
        __enable_irq();
        Serial.println(line);
    }
}

It consistently produces a new ADC value every 10 milliseconds, which is 100 Hz.

grafik

This example still doesn’t use DMA to transfer the value from the ADC peripheral into a e.g. (circular) RAM buffer, but it’s a start. Now, the interrupt processing time for the ADC should be much, much lower than before, because the ADC result is already available.

1 Like

This solution is working
Thanks so much for your continuous support!!!


Github source code here

How to Implement the DMA for this ADC?