Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HardwareTimer freezes when both rollover and channel capture/compare interrupts are attached. #158

Open
peterpresti opened this issue Oct 9, 2024 · 1 comment

Comments

@peterpresti
Copy link

peterpresti commented Oct 9, 2024

This was tested on a Adafruit QT Py with a CH32V203G6.

When a hardwareTimer is set up to generate both rollover and channel interrupts, execution sometimes freezes. This is due to both the internal rollover and internal capture/compare handler being called during a rollover interrupt. During the rollover interrupt the capture/compare handler doesn't identify the channel but still attempts to call a handler.

To reproduce the behavior:

#include <Arduino.h>
#include <Adafruit_TinyUSB.h> // required for USB Serial

#define output_timer_module TIM3
#define output_pin PA6
#define output_timer_channel 1
#define output_pin_2 PA7
#define output_timer_channel_2 2

HardwareTimer *output_tim;
int rollover_count = 0;
int outputcompare_count = 0;
int outputcompare2_count = 0;

void RolloverCallback(void) {
  Serial.printf("Rollover: %u\r\n", millis());
  rollover_count++;
}

void OutputCompareCallback(void) {
  Serial.printf("Output Compare 1: %u\r\n", millis());
  outputcompare_count++;
}

void OutputCompareCallback2(void) {
  Serial.printf("Output Compare 2: %u\r\n", millis());
  outputcompare2_count++;
}

void setup()
{
  Serial.begin(115200);
  while(!Serial);

  output_tim = new HardwareTimer(output_timer_module);

  output_tim->setPrescaleFactor(64);
  output_tim->setOverflow(0x10000);
  output_tim->setMode(output_timer_channel, TIMER_OUTPUT_COMPARE_ACTIVE, output_pin);
  output_tim->setCaptureCompare(output_timer_channel, 50, PERCENT_COMPARE_FORMAT);
  output_tim->setMode(output_timer_channel_2, TIMER_OUTPUT_COMPARE_ACTIVE, output_pin_2);
  output_tim->setCaptureCompare(output_timer_channel_2, 50, PERCENT_COMPARE_FORMAT);
  output_tim->attachInterrupt(RolloverCallback);
  output_tim->attachInterrupt(output_timer_channel, OutputCompareCallback);
  output_tim->attachInterrupt(output_timer_channel_2, OutputCompareCallback2);
  output_tim->resume();
}

unsigned long last_millis = 0;

void loop()
{
  if(millis()-last_millis >= 1000)
  {
    last_millis = millis();
    Serial.printf("Rollover: %d \tOutput Compare: %d \tOutput Compare 2: %d\r\n",rollover_count, outputcompare_count, outputcompare2_count);
    rollover_count = 0;
    outputcompare_count = 0;
    outputcompare2_count = 0;
  }
}

The function in hardwareTimer.c to correct the problem: (Please see the first message below for a better solution.)

void HardwareTimer::captureCompareCallback(TIM_HandleTypeDef *htim)
{
	if (!htim) {
		Error_Handler();
	}

	uint32_t channel = 0;

	if( (htim->Instance->DMAINTENR & TIM_IT_CC1) || (htim->Instance->DMAINTENR & TIM_IT_CC2) \
	 || (htim->Instance->DMAINTENR & TIM_IT_CC3) || (htim->Instance->DMAINTENR & TIM_IT_CC4) )
	{
		if( TIM_GetITStatus(htim->Instance, TIM_IT_CC1) ) 
		{
			channel = 1;
			TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC1);
		}
		else if(TIM_GetITStatus(htim->Instance, TIM_IT_CC2) )
		{
			channel = 2;
			TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC2);
		}
		else if(TIM_GetITStatus(htim->Instance, TIM_IT_CC3) )
		{
			channel = 3;
			TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC3);
		}
		else if(TIM_GetITStatus(htim->Instance, TIM_IT_CC4) )
		{
			channel = 4;
			TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC4);
		}

		if(channel > 0) 
		{
			timerObj_t *obj = get_timer_obj(htim);
			HardwareTimer *HT = (HardwareTimer *)(obj->__this);
			if (HT->callbacks[channel]) {
				HT->callbacks[channel]();
			}
		}
	}
}
@peterpresti
Copy link
Author

peterpresti commented Oct 9, 2024

In my opinion, this is a better solution that handles all outstanding capture/compare events in one call:

void HardwareTimer::captureCompareCallback(TIM_HandleTypeDef *htim)
{
	if (!htim) {
		Error_Handler();
	}

	timerObj_t *obj = get_timer_obj(htim);
	HardwareTimer *HT = (HardwareTimer *)(obj->__this);
	if((htim->Instance->DMAINTENR & TIM_IT_CC1) && TIM_GetITStatus(htim->Instance, TIM_IT_CC1) ) 
	{
		TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC1);
		if (HT->callbacks[1]) {
			HT->callbacks[1]();
		}
	}
	if((htim->Instance->DMAINTENR & TIM_IT_CC2) && TIM_GetITStatus(htim->Instance, TIM_IT_CC2) )
	{
		TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC2);
		if (HT->callbacks[2]) {
			HT->callbacks[2]();
		}
	}
	if((htim->Instance->DMAINTENR & TIM_IT_CC3) && TIM_GetITStatus(htim->Instance, TIM_IT_CC3) )
	{
		TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC3);
		if (HT->callbacks[3]) {
			HT->callbacks[3]();
		}
	}
	if((htim->Instance->DMAINTENR & TIM_IT_CC4) && TIM_GetITStatus(htim->Instance, TIM_IT_CC4) )
	{
		TIM_ClearITPendingBit( htim->Instance, TIM_IT_CC4);
		if (HT->callbacks[4]) {
			HT->callbacks[4]();
		}
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant