cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F0xx : Timer interrupt occasionally processed late or early? Glitch.

David Ledger
Associate III

I am using a STM32F072 microcontroller with a matrix keypad. 

The matrix pins are essentially bit banged, using ODR mask commands. 

When I set up a timer(doesn't matter which one) and I use the timers interrupt (update and a single capture compare) to toggle a pin (manually) it seems to be late processing some of the capture compare and update interrupts. 

This results in a waveform that looks like this:

0690X000006C3KeQAK.png

Even weirder it doesn't appear to have anything to-do with the speed of the interrupt, as I get the same result at 100KHz as I do 1KHz (as seen above).

The code to setup the timer is as follows:

CR1().UDIS() = true;    //Disable updates
CR1().CEN() = false;    //Disable counter
CR1().CMS() = 0;        //Edge aligned mode
CR1().DIR() = 0;        //Up-Counting
CR1().OPM() = false;
CR1().URS() = true;     //Only update on overflow or underflow or DMA request
CR2().CCDS() = false;
CR2().CCPC() = false;
 
auto const prescales = CalculatePrescale(System::SystemClock_t::Timer(), pFrequency);
ARR() = prescales.ARR;
CCR1() = prescales.ARR / 2;
PSC() = prescales.PSC;
CR1().ARPE() = true;        //Buffered auto-reload
DIER().UIE() = true;
DIER().CC1IE() = true;
 
SR().Clear(); //Clears interrupt flags
CR1().UDIS() = false;       //Enable update
CR1().CEN() = true;         //Enable counter

In the interrupt service routine I simply clear the flags that were set. The interrupt code is as follows:

static void Interrupt() 
{
  if (SR().UIF()) 
  {
    SR().UIF() = false;
    //SET PIN LOW
  }
  if (SR().CC1IF()) 
  {
    SR().CC1IF() = false;
    //SET PIN HIGH
  }
}

The glitch only happens when the keypad is enabled.

The keypad only does GPIO pin reads, sets and clears,

The keypad sampling is constantly called from the main loop, not an interrupt so I don't think priority has anything to do with this issue. It doesn't disable interrupts or anything like that.

I don't see reducing the keypad sample speed as a real solution as I suspect that would only make the problem less frequent.

I have now confirmed this, the issue is the same, just much less frequent, it needs to go away entirely.

1 ACCEPTED SOLUTION

Accepted Solutions
David Ledger
Associate III

SOLUTION:

An AND/OR operation on the ODR register ultimately ends up being three instructions, a read, a modify and then a write.

This evaluates to multiple instructions.

When an interrupt is called and another pin is set which is on the port the copied value from the first operation is made redundant as the registers value is changed. This means after the interrupt returns the change from within the interrupt is undone by the assignment of the modified copy of the port.

Example:

In main loop:

PORTA &= MASK1

In interrupt:

PORTA &= MASK2

This compiles to something like:

In main loop:

COPY1 = PORTA;

COPY1 = COPY1 & MASK1; //KEY POINT 1

PORTA = COPY1;

In Interrupt:

COPY2 = PORTA;

COPY2 = COPY2 & MASK1;

PORTA = COPY2;

If an interrupt occurs at KEY POINT 1 then on a single core the instructions look like this:

COPY1 = PORTA;

COPY1 = COPY1 & MASK1; //Enter interrupt after here

COPY2 = PORTA;

COPY2 = COPY2 & MASK1;

PORTA = COPY2; //Return from interrupt after here

PORTA = COPY1;

Note that PORTA is immediately reassigned to the value of COPY1 when the interrupt returns. This is what the issue was here.

View solution in original post

6 REPLIES 6

Not sure what library/language you're using here, but the actions on TIM->SR must NOT use RMW

TIM->SR &= ~1; // IS WRONG

TIM->SR = ~1; // IS CORRECT WAY TO CLEAR BIT ZERO

Using RMW will result in loss of interrupts as it creates an window where you inadvertently clear something by ANDing it with ZERO. This is a peripheral register, is does not act like a memory cell.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
David Ledger
Associate III

Thanks, trying that now, although internally the library performs an assignment but I may have messed it up in a way I haven't figured out...

Attempt 1, HAL Code simple clear (still glitches):

TIM1->SR = 0;

Attempt 2, HAL Code (still glitches):

static void Interrupt()
{
	if (SR().UIF())
	{
		//Set pin false
		TIM2->SR = ~TIM_SR_UIF;
	}
	if (SR().CC1IF())
	{
		//Set pin true
		TIM2->SR = ~TIM_SR_CC1IF;
	}
}

The language is C++, the library is an attempt to get rid of the runtime bloat in HAL by doing all the runtime checks it performs at compile-time.

EDIT:

What I cannot understand is why the matrix keypad being active makes such a difference. It only performs operations on the ODR. I could try the BSRR and BRR but I don't understand why ODR is at all interfering.

WEIRD SOLUTION?

Changing the GPIO pin set and clear commands to use BSRR and BRR fixed the issue WHY?:\

This answer hints at why, still puzzled:

https://electronics.stackexchange.com/questions/99095/stm32f0-gpiox-odr-vs-gpiox-bsrr

TIM1->SR = 0; // This clears ALL interrupts indiscriminately

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
David Ledger
Associate III

SOLUTION:

An AND/OR operation on the ODR register ultimately ends up being three instructions, a read, a modify and then a write.

This evaluates to multiple instructions.

When an interrupt is called and another pin is set which is on the port the copied value from the first operation is made redundant as the registers value is changed. This means after the interrupt returns the change from within the interrupt is undone by the assignment of the modified copy of the port.

Example:

In main loop:

PORTA &= MASK1

In interrupt:

PORTA &= MASK2

This compiles to something like:

In main loop:

COPY1 = PORTA;

COPY1 = COPY1 & MASK1; //KEY POINT 1

PORTA = COPY1;

In Interrupt:

COPY2 = PORTA;

COPY2 = COPY2 & MASK1;

PORTA = COPY2;

If an interrupt occurs at KEY POINT 1 then on a single core the instructions look like this:

COPY1 = PORTA;

COPY1 = COPY1 & MASK1; //Enter interrupt after here

COPY2 = PORTA;

COPY2 = COPY2 & MASK1;

PORTA = COPY2; //Return from interrupt after here

PORTA = COPY1;

Note that PORTA is immediately reassigned to the value of COPY1 when the interrupt returns. This is what the issue was here.

David Ledger
Associate III

@Clive Two.Zero

You are right of course, was just test code.

In the test scenario it was guaranteed that:

  1. only two interrupts (CC and Update) could trigger the handler.
  2. no other interrupts were called with a higher priority.
  3. interrupt handler was constant time.
  4. interrupt handler executes faster than the timespan between any interrupts.

This means that only one of the flags are possible at any time meaning that indiscriminate clearing of flags wasn't any different to clearing individual flags. In the general case not a great idea to clear everything, but for such a specific test it made no difference.

This is why the GPIO on STM32 have the BSRR register.

JW