Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preventing torn reads with an HCS12 microcontroller

Summary

I'm trying to write an embedded application for an MC9S12VR microcontroller. This is a 16-bit microcontroller but some of the values I deal with are 32 bits wide and while debugging I've captured some anomalous values that seem to be due to torn reads.

I'm writing the firmware for this micro in C89 and running it through the Freescale HC12 compiler, and I'm wondering if anyone has any suggestions on how to prevent them on this particular microcontroller assuming that this is the case.

Details

Part of my application involves driving a motor and estimating its position and speed based on pulses generated by an encoder (a pulse is generated on every full rotation of the motor).

For this to work, I need to configure one of the MCU timers so that I can track the time elapsed between pulses. However, the timer has a clock rate of 3 MHz (after prescaling) and the timer counter register is only 16-bit, so the counter overflows every ~22ms. To compensate, I set up an interrupt handler that fires on a timer counter overflow, and this increments an "overflow" variable by 1:

// TEMP
static volatile unsigned long _timerOverflowsNoReset;

// ...

#ifndef __INTELLISENSE__
__interrupt VectorNumber_Vtimovf
#endif
void timovf_isr(void)
{
  // Clear the interrupt.
  TFLG2_TOF = 1;

  // TEMP
  _timerOverflowsNoReset++;

  // ...
}

I can then work out the current time from this:

// TEMP
unsigned long MOTOR_GetCurrentTime(void)
{
  const unsigned long ticksPerCycle = 0xFFFF;
  const unsigned long ticksPerMicrosecond = 3; // 24 MHZ / 8 (prescaler)
  const unsigned long ticks = _timerOverflowsNoReset * ticksPerCycle + TCNT;
  const unsigned long microseconds = ticks / ticksPerMicrosecond;

  return microseconds;
}

In main.c, I've temporarily written some debugging code that drives the motor in one direction and then takes "snapshots" of various data at regular intervals:

// Test
for (iter = 0; iter < 10; iter++)
{
  nextWait += SECONDS(secondsPerIteration);
  while ((_test2Snapshots[iter].elapsed = MOTOR_GetCurrentTime() - startTime) < nextWait);
  _test2Snapshots[iter].position = MOTOR_GetCount();
  _test2Snapshots[iter].phase = MOTOR_GetPhase();
  _test2Snapshots[iter].time = MOTOR_GetCurrentTime() - startTime;
  // ...

In this test I'm reading MOTOR_GetCurrentTime() in two places very close together in code and assign them to properties of a globally available struct.

In almost every case, I find that the first value read is a few microseconds beyond the point the while loop should terminate, and the second read is a few microseconds after that - this is expected. However, occasionally I find the first read is significantly higher than the point the while loop should terminate at, and then the second read is less than the first value (as well as the termination value).

The screenshot below gives an example of this. It took about 20 repeats of the test before I was able to reproduce it. In the code, <snapshot>.elapsed is written to before <snapshot>.time so I expect it to have a slightly smaller value:

For snapshot[8], my application first reads 20010014 (over 10ms beyond where it should have terminated the busy-loop) and then reads 19988209. As I mentioned above, an overflow occurs every 22ms - specifically, a difference in _timerOverflowsNoReset of one unit will produce a difference of 65535 / 3 in the calculated microsecond value. If we account for this:

19988209 + \frac{65535}{3} - 20010014 = 20010054 - 20010014 = 40

A difference of 40 isn't that far off the discrepancy I see between my other pairs of reads (~23/24), so my guess is that there's some kind of tear going on involving an off-by-one read of _timerOverflowsNoReset. As in while busy-looping, it will perform one call to MOTOR_GetCurrentTime() that erroneously sees _timerOverflowsNoReset as one greater than it actually is, causing the loop to end early, and then on the next read after that it sees the correct value again.

I have other problems with my application that I'm having trouble pinning down, and I'm hoping that if I resolve this, it might resolve these other problems as well if they share a similar cause.

Edit: Among other changes, I've changed _timerOverflowsNoReset and some other globals from 32-bit unsigned to 16-bit unsigned in the implementation I now have.

like image 991
Tagc Avatar asked Jul 02 '18 09:07

Tagc


1 Answers

You can read this value TWICE:

unsigned long GetTmrOverflowNo()
{
    unsigned long ovfl1, ovfl2;
    do {
        ovfl1 = _timerOverflowsNoReset;
        ovfl2 = _timerOverflowsNoReset;
    } while (ovfl1 != ovfl2);
    return ovfl1;
}

unsigned long MOTOR_GetCurrentTime(void)
{
  const unsigned long ticksPerCycle = 0xFFFF;
  const unsigned long ticksPerMicrosecond = 3; // 24 MHZ / 8 (prescaler)
  const unsigned long ticks = GetTmrOverflowNo() * ticksPerCycle + TCNT;
  const unsigned long microseconds = ticks / ticksPerMicrosecond;

  return microseconds;
}

If _timerOverflowsNoReset increments much slower then execution of GetTmrOverflowNo(), in worst case inner loop runs only two times. In most cases ovfl1 and ovfl2 will be equal after first run of while() loop.

like image 193
Alexey Esaulenko Avatar answered Sep 22 '22 22:09

Alexey Esaulenko