STM32 hangs when >100khz signal applied to input

Post here first, or if you can't find a relevant section!
funguamongu
Posts: 2
Joined: Sun Feb 25, 2024 9:13 am

STM32 hangs when >100khz signal applied to input

Post by funguamongu »

Hello, I am trying to make a frequency + duty cycle meter with a graphical display to be added in the future. I am using STM32F1038C8T6 with ArduinoSTM32 core from the github https://github.com/stm32duino.

Code: Select all

#include <stm32f1xx_ll_adc.h>

volatile bool wave_rise=false;
unsigned long start=0, first=0, second=0;
volatile double d; volatile int s;
volatile uint32_t analogvoltage;                             //FOR A DIFFERENT FUNCTION NOT YET IMPLEMENTED
bool set=false,flag=false;
#define LL_ADC_RESOLUTION LL_ADC_RESOLUTION_12B
#define VINT 1200

#define anpin PA10    //analogInput pin 
#define pin PA8      //PWM PIN


uint32_t channelRising, channelFalling;
volatile double FrequencyMeasured, DutycycleMeasured, LastPeriodCapture = 0, CurrentCapture, HighStateMeasured;
uint32_t input_freq = 0;
volatile uint32_t rolloverCompareCount = 0;
HardwareTimer *FRQ;

void TIMINPUT_Capture_Rising_IT_callback(void)
{
  CurrentCapture = FRQ->getCaptureCompare(channelRising);
  /* frequency computation */
  if (CurrentCapture > LastPeriodCapture)
  {
    FrequencyMeasured = 1.0*input_freq / (CurrentCapture - LastPeriodCapture);
    DutycycleMeasured = (HighStateMeasured * 100.0) / (CurrentCapture - LastPeriodCapture);
  }
  else if (CurrentCapture <= LastPeriodCapture)
  {
    /* 0x1000 is max overflow value */
    FrequencyMeasured = 1.0*input_freq / (0x10000 + CurrentCapture - LastPeriodCapture);
    DutycycleMeasured = (HighStateMeasured * 100.0) / (0x10000 + CurrentCapture - LastPeriodCapture);
  }

  LastPeriodCapture = CurrentCapture;
  rolloverCompareCount = 0;
}



void Rollover_IT_callback(void)
{
  rolloverCompareCount++;

  if (rolloverCompareCount > 1)
  {
    FrequencyMeasured = 0;
    DutycycleMeasured = 0;
  }
}


void TIMINPUT_Capture_Falling_IT_callback(void)
{
  /* prepare DutyCycle computation */
  CurrentCapture = FRQ->getCaptureCompare(channelFalling);

  if (CurrentCapture > LastPeriodCapture)
  {
    HighStateMeasured = CurrentCapture - LastPeriodCapture;
  }
  else if (CurrentCapture <= LastPeriodCapture)
  {
    /* 0x1000 is max overflow value */
    HighStateMeasured = 0x10000 + CurrentCapture - LastPeriodCapture;
  }
}


void Update_IT_callback(void)
{ // Update event correspond to Rising edge of PWM when configured in PWM1 mode
  
}

void Compare_IT_callback(void)
{ // Compare match event correspond to falling edge of PWM when configured in PWM1 mode
  
}




void setup() {
  // TIM_TypeDef *PWM1 = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin1), PinMap_PWM);
  // uint32_t p1 = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin1), PinMap_PWM));
  // HardwareTimer *PWMT1C1 = new HardwareTimer(PWM1);

  TIM_TypeDef *FR = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(anpin), PinMap_PWM);
  channelRising = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(anpin), PinMap_PWM));
  switch (channelRising) {
    case 1:
      channelFalling = 2;
      break;
    case 2:
      channelFalling = 1;
      break;
    case 3:
      channelFalling = 4;
      break;
    case 4:
      channelFalling = 3;
      break;
    }
  FRQ = new HardwareTimer(FR);
  FRQ->setMode(channelRising, TIMER_INPUT_FREQ_DUTY_MEASUREMENT, anpin);
  uint32_t PrescalerFactor = 1;
  FRQ->setPrescaleFactor(PrescalerFactor);
  FRQ->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  FRQ->attachInterrupt(channelRising, TIMINPUT_Capture_Rising_IT_callback);
  FRQ->attachInterrupt(channelFalling, TIMINPUT_Capture_Falling_IT_callback);
  FRQ->attachInterrupt(Rollover_IT_callback);
  FRQ->resume();
  input_freq = FRQ->getTimerClkFreq() / FRQ->getPrescaleFactor();
 TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
  uint32_t channel = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));



  HardwareTimer *MyTim = new HardwareTimer(Instance);

  MyTim->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin);
  MyTim->setOverflow(120000, HERTZ_FORMAT); //FREQUENCY IN MICROSEC
  MyTim->setCaptureCompare(channel, 85, PERCENT_COMPARE_FORMAT); // DUTY CYCLE IN %
  MyTim->attachInterrupt(Update_IT_callback);
  MyTim->attachInterrupt(channel, Compare_IT_callback);
  MyTim->resume();
  

}

void loop()
{
  /* Print frequency and dutycycle measured on Serial monitor every seconds */
  Serial.print("Frequency = ");
  Serial.print(FrequencyMeasured/1.0,4);
  Serial.print("    Dutycycle = " );
  Serial.println(DutycycleMeasured/1.0,4);
  delay(500);
}
Using the examples Frequency_Dutycycle_measurement.ino and PWM_FullConfiguration.ino in https://github.com/stm32duino/STM32Examples, I can measure the pwm generated on pin A8 with no issues. But when I change it to something higher than 100kHz, the serial monitor writes painfully slow (gives me 0 values) or just stops giving me output. When I disconnect PA10 from PA8 it comes back to life and shows the default 0 value because nothing is connected on input pin PA10. I also measured PA8 with voltmeter and it gives correct voltage according to the duty cycle I set. Can anyone help me figure out whats the problem or find a better solution? Sorry for bad english and bad code :(
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: STM32 hangs when >100khz signal applied to input

Post by dannyf »

Can anyone help me figure out ...
you are invoking the two interrupts at 100K times per second, and you are using floating point math in them. each invocation is likely hundreds if not a thousand cycles. so you need something like 2 x 100K * 1K = some out of this world fast processor to handle that.

there are much simpler ways to measure frequency / duty cycle.
funguamongu
Posts: 2
Joined: Sun Feb 25, 2024 9:13 am

Re: STM32 hangs when >100khz signal applied to input

Post by funguamongu »

So do I change the prescaler? I already changed the double to uint32. I just copied my example code because the one I wrote is kinda slow? and trying to get duty cycle out of it makes it inaccurate.
Here is my attempt at it

Code: Select all

void freq(){
  if(true==wave_rise){    // check for second rising edge and then calculate frequency
    hertz=micros()-start;
    wave_rise=false;
    flag=true; 
  }
  else{
    start=micros();
    wave_rise=true;   //start on rising edge
  }
}
uint32_t getHertz(){
  start=micros(); wave_rise=false;  //initialize
  attachInterrupt(anpin,freq,RISING); 
  if(flag){ 
    hertz=(1000000/hertz);                            //wait for interrupt function to finish, return the hertz value and detach interrupt
    flag=!flag;
    return hertz;
    detachInterrupt(anpin);
    wave_rise=false;
  }
}
I just want to get the wave details like the frequency,voltage, on and off time. analogRead() doesnt work if the interrupt is active. Will you help me change the code? If this core doesnt satisfy my needs ill get an st link for the cubeide as last resort
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: STM32 hangs when >100khz signal applied to input

Post by dannyf »

the solution is fairy simple:
1. find a better way of doing what you are trying to do;
2. if not, write faster ISR - try using integers only;
3. if not, lengthen the ISR invocation frequency - use the input filter;
4. if not, live with the limitation.

and yes, reading the reference manual would be quite helpful - it has a hardware solution for the exact problem.
ag123
Posts: 1653
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

Re: STM32 hangs when >100khz signal applied to input

Post by ag123 »

try something like a stm32 f4xx (e.g. stm32f401 / f411 ) board, those has the FPU, but that it is for floats only.

other things to try are to move the computation codes into loop() and only use the ISR callback to simply count cycles, e.g. increment a global variable.
stevestrong
Posts: 502
Joined: Fri Dec 27, 2019 4:53 pm
Answers: 8
Location: Munich, Germany
Contact:

Re: STM32 hangs when >100khz signal applied to input

Post by stevestrong »

The best way to measure high frequencies is to setup a timer to measure nr of pulses you apply within a predetermined period (for which you can also use another timer). If you need to measure duty cycle the the things get more complicated.
I think I posted such kind of example couple of years ago, which you can only find on the wayback machine.
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32 hangs when >100khz signal applied to input

Post by ozcar »

I lifted up the first code you posted (that uses input capture) and tried it on 72MHz F103. I could not get it to work anywhere near 100kHz. At 20kHz it was producing output, but looking like this:

Code: Select all

Frequency = 0.0000    Dutycycle = 0.0000
I then changed it to produce the pwm output on pin PA1 instead of PA8 (and moved the jumper), and I get valid output:

Code: Select all

Frequency = 20000.0000    Dutycycle = 85.0000
So, how can just changing the pwm pin make a difference? The problem is that with the input capture set to PA10, and the pwm on PA8, the two HardwareTimer objects are trying to use the same STM32 timer (TIM1). That means that when you set the frequency for the pwm, you are overwriting the setPrescaleFactor and setOverflow values in the timer registers for the input capture. With the pwm on PA1instead it uses TIM2 for that so it does not conflict with the input capture on TIM1.

The next thing that I noticed is that you are not causing 2 interrupts per cycle, it is actually 4! That is because you have attached the Update_IT_callback and Compare_IT_callback routines to the pwm timer. Even though those two routines "do nothing" each is going to stop the mainline code from executing for a few microseconds. Without those interrupts the timer is capable of producing a signal well into the tens of MHz range without any software overhead once you set it running.

With those interrupts removed from the pwm timer, I could get it the frequency and duty cycle measurements to work at 30kHz, but not much more that that. It likely depends a bit on the optimization set. You could get it down to 1 interrupt per cycle because the rising and falling captures are on two different channels, but getting it to work over 100kHz is going to be difficult no matter how little you do in the interrupt routine.

You have already had some suggestions. The frequency range that it needs to work over, (min as well as max), what resolution is needed, and how quickly it has to respond to changing frequency are all things that could come into it.
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: STM32 hangs when >100khz signal applied to input

Post by dannyf »

I would approach the problem this way.

1. Set up an input capture on falling edge. No interrupt.
2. Set up one input capture on rising edge, interrupt enabled. In this isr, you will need to calculate the timer count elapsed between two isr invocations, and the difference between the count captures in #1 above - precious timer count, and set a flag.
3. In your loop, if the flag is set, calculate the frequency and duty cycle, and then clear the flag.

I would be surprised if this takes more than 100 ticks to complete.
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

Re: STM32 hangs when >100khz signal applied to input

Post by ozcar »

dannyf wrote: Tue Feb 27, 2024 11:41 pm I would approach the problem this way.

1. Set up an input capture on falling edge. No interrupt.
2. Set up one input capture on rising edge, interrupt enabled. In this isr, you will need to calculate the timer count elapsed between two isr invocations, and the difference between the count captures in #1 above - precious timer count, and set a flag.
3. In your loop, if the flag is set, calculate the frequency and duty cycle, and then clear the flag.

I would be surprised if this takes more than 100 ticks to complete.
100 ticks on a 72MHz F103 would be around 1.4µs. I think that is being a bit optimistic. Sure you can probably stash the two captured counts very quickly, but there is the hardware interrupt latency, and the HAL and STM32DUINO overhead as well.

I tried doing one on my infamous OTL tests ("Out to Lunch").

I changed the loop() routine to just spin toggling a GPIO:

Code: Select all

void loop()
{
  while ( 1 ) 
  {
    GPIOB->BSRR = GPIO_BSRR_BS9;
    GPIOB->BSRR = GPIO_BSRR_BR9; 
  }  
}
That produces a continuous signal on PB9, but that stops of course as soon as it gets interrupted. By observing that you can see just how long it goes "out to lunch" to process an interrupt ( digitalWriteFast() might be just as good, but old habits die hard with me.)

I then replaced all the processing in the rising-edge interrupt routine with code to just put a marker pulse on PB6, and commented out two of the attachInterrupt() calls in setup(), so leaving only the attachInterrupt() for the rising edge.

Code: Select all

void TIMINPUT_Capture_Rising_IT_callback(void)
{
  GPIOB->BSRR = GPIO_BSRR_BS6;
  GPIOB->BSRR = GPIO_BSRR_BR6; 
}

...

  FRQ = new HardwareTimer(FR);
  FRQ->setMode(channelRising, TIMER_INPUT_FREQ_DUTY_MEASUREMENT, anpin);
  uint32_t PrescalerFactor = 1;
  FRQ->setPrescaleFactor(PrescalerFactor);
  FRQ->setOverflow(0x10000); // Max Period value to have the largest possible time to detect rising edge and avoid timer rollover
  FRQ->attachInterrupt(channelRising, TIMINPUT_Capture_Rising_IT_callback);
  //FRQ->attachInterrupt(channelFalling, TIMINPUT_Capture_Falling_IT_callback);
  //FRQ->attachInterrupt(Rollover_IT_callback);
  FRQ->resume();
  input_freq = FRQ->getTimerClkFreq() / FRQ->getPrescaleFactor();
In the jpg below, the top channel is the test pwm signal (now on PA1, and connected to PA10 for the input capture), with pwm set to a leisurely 10kHz, 85% duty cycle. The middle trace is PA6, which shows a blip when the interrupt routine gets called shortly after the rising edge of the test signal.

The bottom trace is PA9, showing activity in loop(), there you can see that the loop processing stops right at the rising edge, and only resumes a while after the interrupt routine blip.

The elephant in the room (or at least, in the bottom trace there) is that not only does the loop processing take a lunch break, it also goes out to morning tea. What I mean is that the loop processing also stops immediately after the falling edge as well, despite the fact that we have not attached an interrupt routine for the falling capture channel. On checking the timer DIER register I see that interrupts are enabled for both the rising and falling channels. Further investigation shows that the attachInterrupt() for the rising channel only enables interrupts for the rising channel, but when the resume() is done, that turns on the interrupt for the "associated" falling channel. I'm not sure why they do this - maybe fpiSTM will see this and can enlighten us.

In any event, even if the falling edge interrupt can be eliminated, the rising edge interrupt puts the mainline code out of action for around 5µs (and of course, that is with our interrupt routine doing practically nothing).

DS1Z_QuickPrint7.jpg
DS1Z_QuickPrint7.jpg (62.12 KiB) Viewed 294 times
dannyf
Posts: 446
Joined: Sat Jul 04, 2020 7:46 pm

Re: STM32 hangs when >100khz signal applied to input

Post by dannyf »

for around 5µs
that's too long. your ISR overhead will be 20 ticks. Hard to imagine this thing goes over 100 ticks end-to-end. I will do some testing later on as well.
Post Reply

Return to “General discussion”