maanantai 1. syyskuuta 2014

Fakin' it (part 3)


Previous parts here and here.

Previously I've thrown in some ideas considering speed signal tampering that some less than honest drivers may be employing. This series is primarily focused on what kind of signals to expect and low-cost tampering detection on cheap(ish) MCU. So now it's time for some actual code to do the magic (or the difficult part of it, at least).

Let's start with constraints. Cars typically generate between 1000 and 28000 pulses per kilometer (mostly depending on manufacturer), or 1 to 28 pulses per meter. Lower bound that interests us is, say, 20km/h, and upper for example at 200km/h. So valid frequency range is from 5.5 Hz to 1.5 kHz (roughly).

To make any judgement on pulse length the timer resolution will need to be at least one decade, preferably two above these, so we'll want our timer that does the measurement to run at approximately 150kHz. This pretty much rules out interrupts, there is no way you could get acceptable performance from any (microcontroller) software at that rate.

Fortunately most MCUs expose the underlying accumulator counter that is used to set timer interrupt periods, so we can use that to our advantage. For quick test I used a PIC24-based board I happened to have handily available. The processor (specific model not that important) has main oscillator at 8 MHz and has 5 timers, of which only one was used. So I chose to implement this measurement system using Timer 2 of the MCU.

PIC24's timers come with prescaler that is used to scale down the main oscillator frequency. In this case 1:64 prescaler gives us 125kHz resolution which is close enough to previously calculated 150kHz so we can work with it. Since the counter is 16-bit, this gives us minimum frequency of 1.9 Hz so lower bound is no problem either - anything slower is too slow to be indication of tampering (in this application at least).

So, variables and initialization of timer and the ISR (I'm using MPLAB X as IDE and Microchip's C compilers here);

// Calculate new value if these are set
volatile unsigned int tmrAccStop, tmrAccCnt; 
// Accumulator in interrupt 
volatile unsigned int tmrAccCntInt; 

void timer2Init()
{
  T2CON = 0; // stop timer, reset control reg
  TMR2 = 0; // clear timer acc register
  PR2 = 65535; // period; set to maximum 16-bit value
  IPC1bits.T2IP = 1; // interrupt priority
  IFS0bits.T2IF = 0; // clear interrupt status flag
  IEC0bits.T2IE = 1; // enable timer1 int

   // TON=1 - start timer
   // TCKPS=2 (1:64) - prescaler
  T2CON = (1<<15) | (2<<4);
}

void __attribute__((interrupt, auto_psv, shadow)) _T2Interrupt(void)
{
  ++tmrAccCntInt;
  IFS0bits.T2IF = 0;
}

Since we are mostly interested of first period, if the tmrAccCntInt variable is set it can simply be used as indication that pulse period was longer than our lower bound and further processing is not needed.

Then the IO Change Notification interrupt (I'm not including IO setup since there's nothing special there);

void __attribute__((interrupt, auto_psv, shadow)) _CNInterrupt(void)
{
  // Change notification is signalled on both rising and lowering edge. 
  // Here we want just rising edge, so checking for it.
  // By triggering on both rising and lowering edge you could easily add 
  // duty cycle measurement too.
  if (PORTE & 2) // Pin E1
    { tmrAccStop = TMR2;
      TMR2 = 0;
       // Counter; if non-zero, timer has overflowed  
       // so rate is lower than 1,9Hz (approx)
      tmrAccCnt = tmrAccCntInt;           
      tmrAccCntInt = 0;  // Clear internal counter
    }
 
  IFS1bits.CNIF = 0; // clear Input Change Notification
}

Since we are not sharing Timer2 with other functions we can simply clear its accumulator in interrupt. This makes the following math a bit easier since we don't have to deal with partial periods or timer overflows.

Note that I didn't put any math in interrupt. In this example involved math is very light, but nevertheless, in general rule you should never do any complex math in interrupts if you can avoid it; store results to suitable temporaries and do the math in main() to make time spent in interrupts lower.

Then the main loop (again, I'm excluding setup since with exception of parts listed above it's pretty trivial);

  while (1)
    {
      unsigned int n, end;

      __asm__ volatile("disi #0x3FFF"); // Disable interrupts

      if (tmrAccStop != 0xffff) // if not ffff, we have new reading ready.
        {
          n = tmrAccCnt;
          end = tmrAccStop;

          tmrAccStop = 0xffff; // clear reading for next
        }
      else
        {
          end = 0xffff; // no new reading; don't do anything.
        }

      __asm__ volatile("disi #0x0"); // Enable interrupts.

       // It would be possible to do entire analysis with interrupts disabled
       // but I prefer to make critical sections as short as possible for same
       // reason I don't do math in interrupts.

      if (end != 0xffff)
        {        
          if ( (n > 0) || (end > 65000) )
            { // Timer interrupt triggered or period long enough; can be ignored
              debugWrite("no event\r");
            }
          else
            {  
              debugWriteInt("period=", end);
            }
        }
    }

DebugWrite functions are simple utility functions that write given string and data to serial output. Nothing fancy in there.

Yes, since stop isn't initialized this will trigger spurious first reading. Yes, this will also miss readings if  main doesn't run fast enough. And the end result isn't exactly useful alone. And I'm not sure if this even compiles as this is a copy-pate from several thousands of lines long project I am using to quickly test these ideas. But this shows you how to measure shorter signal periods that typical interrupts allow, and further refinement of results aren't exactly rocket science either.

Happy hunting :)



Ei kommentteja:

Lähetä kommentti