micros() within Interrupt possible?

Status
Not open for further replies.

NikoTeen

Well-known member
Hi,
I want to use interrupts to measure pulse lengths with a Teensy 3.2. The time between interrupts shall be measured by micros().
But anywhere I read that using micros() within interrupts might cause problems. I can't remember where I read this.

Therefore my question ist: can I call micros() within an interrupt and what has to be considered when using it?
 
Indeed micros() isn't a clean/easy function on T_3.x's.
Better to use the CPU cycle counter - T_LC doesn't have one but T_3.x's do though not enabled by default, it is on T4 though:

This test seems to compare and initialize when needed - place in setup():
Code:
	if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) {
		// Enable CPU Cycle Count
		ARM_DEMCR |= ARM_DEMCR_TRCENA;
		ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
	}

On T_3.2 the cycles per second is known with :: F_CPU

So tracking :: uint32_t countNow = ARM_DWT_CYCCNT

Is a simple read of the running count - like micros() - except instead of 1M/sec it will change at 96M/sec when running at F_CPU==96 MHz.

So much better resolution and much less overhead.
 
You probably saw that on Arduino's site. On Teensy 3.x, micros() does support use from within interrupts. For many of Arduino's boards, micros() should not be used from interrupts.

And yes, Defragster is right, the ARM DWT cycle counter is the most precise and lowest overhead way. Just remember it will overflow after 44.7 seconds when counting at 96 MHz, or only 7.15 seconds if you someday step up to Teensy 4.0 which runs at 600 MHz. Best to use the cycle counter only when you know the period to be measured will be fairly short.
 
You probably saw that on Arduino's site. On Teensy 3.x, micros() does support use from within interrupts. For many of Arduino's boards, micros() should not be used from interrupts.

The background for my question was, that a code which successfully runs on an Arduino Nano makes problems when run on T3.2.
The sketch is decoding a Manchester coded signal from a 433 MHz receiver. Both edges of the pulses from the receiver are triggering an interrupt. For decoding, the pulse width shall be measured which is done by calling micros() - on Nano and on T3.2.
This works on the Nano. But on T3.2 there are a lot of unknown messages ("unbekanntes Signal") and only sometimes (every 3 to 6 times) the signal is decoded correctly.

Here is the code of the sketch:
Code:
/* Test der Decodierung der Oregon-Daten auf einem Teensy 3.2   */

// Decodierung Oregon-Daten:
#include <Arduino.h>
#include <stdio.h>
#include <math.h>
#include "OregonDecodeV3_2.h"
#include "digitalWriteFast.h"

#include <util/atomic.h>

// Decodierung Oregon-Daten:
#define SYNCPIN 5   // zum Triggern Oszi
#define MARKSENT   A1  // zeigt 1/2 Sek. lang an LED an, dass Datenblock gesendet wurde
//#define HOT       // HOT fuer WDFA
//#define MEAN_SPEED  // gemittelte Windgeschw. anstatt aktueller

OOK_OregonDecodeV3_2 orscV3;
boolean SerMonitor;

uint8_t   fstat;
elapsedMicros blinkPrint=0;
uint16_t looping=0;
byte isr_disen =0;

// Decodierung Oregon-Daten:
bool tglbit=false;
unsigned long tm; // Zeit für einen loop-Durchlauf
// Winddata:
word wv;
byte wdir, lowbatt;
// for temperature data:
int temp_act;
byte rh_act;

// Parameter in loop():
byte n, dtyp;
static word Scount=0;
static short int v16;
static byte v8;
static char outstr[12];
static byte newdata=0;
unsigned long tm_wd;  // Zeit zwischen WGR800-Winddaten
byte norecdat;        // Luecke zwischen Winddaten in 14sec-Einheiten
char ic='n';
byte respos_cpy, respos_cold;

// Parameter, die von der Komparator-Interrupt-Routine veraendert werden koennen:
volatile byte res_valid[N_DATASETS];
volatile byte res_pos=0, dbcount;
volatile byte dtmin=255, dtmax=0;
volatile word pulse, tot_p;
//volatile byte Wdone=1;

void waitSM(const char* msg) {
  Serial.println(msg);
  while (Serial.available()>0) Serial.read();
  while (Serial.available()==0);
  while (Serial.available()>0) Serial.read();
}

// Interrupt Routine

void ext_int_2(void) {  // ohne Comparator
    static unsigned long last, pw, t00; // mit static muss Speicher für die Parameter
                                       //   nur einmal reserviert werden
    static byte dt;

    // determine the pulse length in microseconds, for either polarity
    t00 = micros();   // nur zum Testen
    pw = micros() - last;
    last += pw;
    if (pw < 65536) pulse=(word)pw;
    else pulse=65535;

    // OregonDecoder
    if ( (orscV3.total_bits > 0) || (pulse > PW1) ) {
      if (orscV3.nextPulse(pulse)==1) {
        dbcount = orscV3.pos;  // pos retten, ehe es zu 0 wird
        orscV3.resetDecoder ();
        res_valid[res_pos]=1;  // valid Flag
        res_pos++;
        if (res_pos > (N_DATASETS-1)) res_pos = 0;
        orscV3.setdb(res_pos);  // für nächsten Datensatz
        tot_p = orscV3.anz_p;  // Wert retten
      }
    }
    dt=micros()-t00;
    if (dt<dtmin) dtmin=dt;
    if (dt>dtmax) dtmax=dt;
}

byte readRFData(byte r_pos, short int* val16, byte* val8, byte* lowBatt, boolean mean_speed) {
  // liest Daten des Empfaengers aus dem Datensatz r_pos und liefert sie in val16 und val8 zurück;
  //  mean_speed==true: gemittelte Windgeschwindigkeit
  //  mean_speed==false: aktuelle Windgeschwindigkeit
  //            WGR800        THGN          TFA    
  // val16    Geschw.*10    Temperatur*10  Geschw.*10 simuliert
  // val8     Richtung      Luftfeuchte    Richtung simuliert
  // return     1             0             2
  // 
  volatile const byte* data = orscV3.getData(r_pos);
  static int  wv=36, tmp;   // für Geschw.,  Temp. 
  byte wd, rh;              // für Richtung, rel. Luftfeuchte
  byte retv=0;

//Serial.print("von Datensatz ");Serial.print(r_pos);Serial.print(" ");
  if ( (data[0] == 26) & (data[1] == 137) ) { // WGR800: 1A89
    if (mean_speed)
      wv = (data[8] >> 4)*100 + (data[8] & 0x0F)*10 + (data[7] >> 4);
    else
      wv = (data[7] & 0x0F)*100 + (data[6] >> 4)*10 + (data[6] & 0x0F);
    Serial.print(": ");Serial.print(wv);Serial.print("m/s*10 ");
//    wv = word(round(wv * 3.6));   // kmh*10
    wd = (data[4] >> 4);

    *val16 = wv;
    *val8 = wd;

    if (data[4] & 0x04)
      *lowBatt = 4;   // battery low Windsensor
    else
      *lowBatt = 0;
      
    retv = 1;
  }
  else if ( (data[0] == 250) && (data[1] == 40) ) {  // THGR810: FA28
    tmp = (data[5] >> 4)*100 + (data[5] & 0x0F)*10 + (data[4] >> 4);
    if (data[6] & 0x0F) tmp = -tmp;
    rh = (data[7] & 0x0F)*10 + (data[6] >> 4);

    *val16 = tmp;
    *val8 = rh;
    
    if (data[4] & 0x04)
      *lowBatt = 5;   // battery low Temperatursensor
    else
      *lowBatt = 0;
      
    retv = 0;
  }
  else {  // unbekannte Daten
    retv = 2;
  }

  return(retv);
}

void mark_dataReceived() {
  digitalWrite(MARKSENT,HIGH);
  delay(500);
  digitalWrite(MARKSENT,LOW);
  delay(200);
}


void setup() { // -------------------------------------------------------------
  uint32_t read_time;
  int16_t anz_errors;
  
  analogWriteResolution(8);
  pinMode(A14, OUTPUT);

  Serial.begin(38400);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println(F("<<< Sprache + Oregon-Decodierung >>>"));

  SerMonitor=true;
  
  // Vorbelegungen ohne cli() ok, weil erst
  //  anschließend die Interrupt-Routine scharf wird
    for (byte nn=0; nn<N_DATASETS; nn++)
      res_valid[nn]=0; // Flags der Datenbaenke
    res_pos=0;
    respos_cpy=respos_cold=0;
    orscV3.setdb(0);

  // Empfaenger-Interrupt aktivieren, Pin 23
    attachInterrupt(digitalPinToInterrupt(23), ext_int_2, CHANGE);

    Serial.println("es ist ein Teensy");

#ifdef WGR800
  Serial.println("Pulssequenzen wie WGR800");
#else
  Serial.println("Pulssequenzen wie TFA-Sensor");
#endif

  tm_wd=millis();
  tm=tm_wd;
}

void loop() { // ---------------------------------------------------------
  // to read a variable which the interrupt code writes, we
  // must temporarily disable interrupts, to be sure it will
  // not change while we are reading.  To minimize the time
  // with interrupts off, just quickly make a copy, and then
  // use the copy while allowing the interrupt to keep working
  boolean dvalid;
  byte dtmin_c, dtmax_c;
  
  if (Serial.available()) {
    ic=Serial.read();
    while (Serial.available()) Serial.read();
  }

  // --------------------------------------
  // Trigger-Sync:
  digitalWrite(SYNCPIN,HIGH);
  digitalWrite(SYNCPIN,LOW);

    delay(200);

      for (n=0; n<N_DATASETS; n++) {
          ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
          {
            dvalid = res_valid[n] == 1; 
          }
          if (dvalid)  {  // Daten sind verfuegbar
#ifdef MEAN_SPEED
            dtyp = readRFData(n, &v16, &v8, &lowbatt, true);
#else
            dtyp = readRFData(n, &v16, &v8, &lowbatt, false);
#endif
            switch (dtyp) {
              case 0:  // Temperaturdaten
                sprintf(outstr,"n=%d dbcnt=%d T%04dH%02dB%d",n,dbcount,v16,v8,lowbatt);
                Serial.print(outstr);
                mark_dataReceived();
                break;
              case 1: // Winddaten WGR800
#ifndef HOT
                Serial.print(millis()-tm_wd);Serial.print(": ");
                tm_wd=millis();
  
                sprintf(outstr,"n=%d dbcnt=%d W%03dR%02dB%d",n,dbcount,v16,v8,lowbatt);
                Serial.print(outstr);
  
#ifdef MEAN_SPEED
                Serial.print(F(" mean="));
                Serial.print(v16);
#endif
                
#else
                sprintf(outstr,"n=%d W%03dR%02dB%d",n,v16,v8,lowbatt);
                Serial.println(outstr);
#endif
                mark_dataReceived();
                break;
              case 2:  // unbekanntes Signal
                  Serial.print(F("unbekanntes Signal: n="));Serial.print(n);
                  Serial.print(F(", dbcnt="));Serial.print(dbcount);
            } // switch
  
            Serial.print(F(" Interrupt-Dauer von "));
            ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
            {
              dtmin_c = dtmin;
              dtmax_c = dtmax;
              dtmin=255;
              dtmax=0;
            }
            Serial.print(dtmin_c);
            Serial.print(" bis ");Serial.println(dtmax_c);
  
            res_valid[n] = 0;   // wenn es 1 war, dann wurde in der Interrupt-Routine
                                // bestimmt nicht daran gearbeitet
          } // if (res_valid..)
          
          Scount++;
          tm=millis();
      } // for n
//    }

}
Code of class OOK_OregonDecodeV3_2:
Code:
#ifndef _myOregonDecoderV3_2_
#define _myOregonDecoderV3_2_

// 2012-06-21 - Oregon V3 decoder revisited - Dominique Pierre
// New code to decode OOK signals from weather sensors, etc.
// 2010-04-11 <jcw@equi4.com> http://opensource.org/licenses/mit-license.php
// $Id: ookDecoder.pde 5331 2010-04-17 10:45:17Z jcw $

#include <Arduino.h>

#define WGR800    // auskommentiert heißt TFA-Sensor

#ifdef WGR800
#define PW1 350     // Original 200
#define PW2 720     // Original 700
#define PW3 1150     // Original 1200
#define FLIP 32
#define TOTALB 80
#define N_DATASETS 4  // 1 mehr als TFA für Temperatursensor
#else       // TFA
#define PW1 200
#define PW2 750
#define PW3 1200
#define FLIP 5
#define TOTALB 104
#define N_DATASETS 6  // TFA-Windsensor sendet Daten 3 mal
#endif

// 433 MHz Oregon-decoder:

class OOK_OregonDecodeV3_2 {
  // Die Klassen DecodeOOK und OregonDecoderV3 sind hier zusammengefasst

protected:
    byte bits, flip, state;
    volatile byte data[N_DATASETS][15];
    // 15 reicht für TFA und WGR800
    char dc;
public:
    byte total_bits, pos;  // hier wegen Direktzugriff
    volatile byte* data_pnt; // wird auf eine der N_DATASETS Datenbänke zeigen
    word anz_p=0;   // zählt die Pulse pro Datensatz
    enum { UNKNOWN, T0, T1, T2, T3, OK, DONE };

    OOK_OregonDecodeV3_2 () { resetDecoder(); }

    bool isDone () {
      return state == DONE;
    }
   
    // Pointer auf einen der Datenblöcke setzen:
    void setdb(byte rpos) {
      data_pnt = data[rpos];
    }
    
    volatile const byte* getData (byte rpos) const {
        return data[rpos]; 
    }
   
    void resetDecoder () {
        total_bits = bits = pos = flip = 0;
        anz_p = 0;
        state = UNKNOWN;
    }
    
    // add one bit to the packet data buffer (Methode V3)
    void gotBit (char value) {
        data_pnt[pos] = (data_pnt[pos] >> 1) | (value ? 0x80 : 00); // LSB comes first
        total_bits++;
        pos = total_bits >> 3;
        if (pos >= sizeof data) {
            resetDecoder();
            return;
        }
        state = OK;
    }
    
    // store a bit using Manchester encoding
    void manchester (char value) {
        flip ^= value; // manchester code, long pulse flips the bit
        gotBit(flip);
    }

    void done () {
        while (bits)
            gotBit(0); // padding
        state = DONE;
    }
    
    bool nextPulse (word width) {
        if (state != DONE) {

            anz_p++;
//            switch (decode(width)) {
//                case -1: resetDecoder();  break;  // ergibt Fehlermeldung mit
                                                    //  Teensy Compiler
//                case 1:  done(); break;
//            }
            dc=decode(width);
            if (dc==-1) resetDecoder();
            else if (dc==1) done();
        }
        return isDone();
    }

    char decode (word width) {
        if (PW1 < width && width < PW3) {
            byte w = width >= PW2;
            switch (state) {
                case UNKNOWN:
                    if (w == 0)
                        ++flip;
                    else if (FLIP <= flip) {
                        flip = 1;
                        manchester(1);
                    } else
                        return -1;
                    break;
                case OK:
                    if (w == 0)
                        state = T0;
                    else
                        manchester(1);
                    break;
                case T0:
                    if (w == 0)
                        manchester(0);
                    else
                        return -1;
                    break;
            }
        } else 
            return -1;

        return total_bits == TOTALB ? 1: 0;
    }

};

#endif
The interrupt is filling one of 4 buffers, data[][], with the received data and then sets a flag res_valid[].
In loop() the flag is checked and if set the data are interpreted and output on Serial Monitor.

The true Manchester signal is generated and sent from a test setup in my lab. Of course the 433 MHz receiver is seeing also signals from sensors outside my lab sending any Manchester codes. But the Nano is decoding just the true signals and the T3.2 is reporting many false signals ("unbekanntes Signal") and only a few of the true signals.

I would be happy to get some ideas why T3.2 is performing so different and poor.
 
Without thinking about it too much, it seems you'd be better off using one of the K20's FTMs set up for Input Capture mode.
 
Is it possible to reset the ARM DWT Cycle counter after reading it, so you don’t suffer an overflow?

Better to let it run - others may be using it - for instance it drives micros() on T_4

It returns a uint32_t , and it tracked in a uint32_t rollover works naturally for tracking. Depending on how you want to use it you can count rollovers or track differences.
 
Hi defragster,

I guess I could write my own elapsednano() fucntion, which would check if the counter is less than the previous read value and handles the delta accordingly... But that is more heavyweight than I had hoped for... perhaps the T4 has a one shot timer based on the cycle counter?

-edit-
I’ve just realised that you stated the rollovers are counted in a separate variable, so is in essence the high 32bits of the counter... it would be simple enough to return the two counters as a single 64bit value! Which if my backOfAFagPacket sums are correct would roll over every 500 years...
 
Last edited:
Making it more exact will not help, I think.
I assume it's a hardwareproblem - the Teensy interrupts are really much faster, and I.e. a slow rising signal may cause 2 interrupts - for example.
There were many issues in the past with the Teensy being too fast for programs written for the slower AVRs.
You might have luck if you just try to reduce F_CPU to 24MHz - if that works you've found the problem :)
 
Kannst du das Signal mal als Audio sampeln (mit dem PC) und mir als wav-file senden?

Can you record the signal as audio-wav and send it to me?
I can try to make it work.
 
Hi defragster,

I guess I could write my own elapsednano() fucntion, which would check if the counter is less than the previous read value and handles the delta accordingly... But that is more heavyweight than I had hoped for... perhaps the T4 has a one shot timer based on the cycle counter?

-edit-
I’ve just realised that you stated the rollovers are counted in a separate variable, so is in essence the high 32bits of the counter... it would be simple enough to return the two counters as a single 64bit value! Which if my backOfAFagPacket sums are correct would roll over every 500 years...

No interrupt I've seen - just a simple running count of clock cycles.

And there is no rollover to another value - that would be 'an exercise for the reader' as they say - as it depends on use. For instance when the 'base' CYCCNT is recorded also record the micros() or millis() and 'note' when that value would indicate that CYCCNT rolled over based on F_CPU_ACTUAL. It rolls over in about 7.1582788266666666666666666666667 seconds at 600 MHz. Add logic as needed to check that 'noted' value against the current value and if over the '7.15' second period, and the DIFF of CYCCNT now versus 'base' value, then the CYCCNT has rolled over - but not before. Not known is how often this code gets run or how critical that rollover is but those pieces in some fashion should lead toward an answer of how to do it.
 
uint32_t countNow = ARM_DWT_CYCCNT

Is a simple read of the running count - like micros() - except instead of 1M/sec it will change at 96M/sec when running at F_CPU==96 MHz.

So much better resolution and much less overhead.

Does this mean it would be highly preferably to use this method instead of micros() or an elapsedMicros() object? Therefore can the elapsedMicros() class be rewritten to be faster for short period reads?
 
Getting running/elapsed elapsedMillis or elapsedMicros is possible now with use of pjrc.com/teensy/td_timing_elaspedMillis.html

Code:
void loop() {                // each time loop() runs,
  elapsedMillis waiting;     // "waiting" starts at zero
  while (waiting < 1000) {
    if (Serial.available()) {
      char c = Serial.read();
      Serial.print("got char = ");  // do something with c
      Serial.println(c);
      waiting = 0;           // reset waiting back to zero
    }
  }
  Serial.println("waited 1 second, no data arrived");  
}

And IIRC there is a forum post about 'Class' for elapsedNanos or elapsedCycles … forget which

It was :: A-simple-class-for-sub-microsecond-timing

A simple class for sub microsecond timing
Using the elapsedMillis and elapsedMicros as template I wrote the following class elapsedCycles that uses DWT_CYCCNT as clock.
 
Perfect! Many thanks for providing this link, I need to run some tests to check it’s good on a Teensy 4, but otherwise this is exactly what I need.

No Problem.

> One thing to not do in that code is any change to the ARM_DWT_CTRL value - it is used by the T4 system to track micros()
> T_LC doesn't have the ARM_DWT_CTRL
> T4 has it running before setup : for fun I do "if ( ARM_DWT_CTRL == ARM_DWT_CTRL ) {// then start the counter}", if running the two will not be the same
> T4 uses F_CPU_ACTUAL not F_CPU as the speed can change :: (F_CPU/8000000UL)
 
Status
Not open for further replies.
Back
Top