Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 15 of 15

Thread: micros() within Interrupt possible?

  1. #1

    micros() within Interrupt possible?

    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?

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,560
    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.

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,864
    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.

  4. #4
    Quote Originally Posted by PaulStoffregen View Post
    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.

  5. #5
    Senior Member
    Join Date
    Feb 2017
    Posts
    403
    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.

  6. #6
    Senior Member
    Join Date
    Jan 2014
    Location
    London, UK
    Posts
    110
    Is it possible to reset the ARM DWT Cycle counter after reading it, so you don’t suffer an overflow?

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,560
    Quote Originally Posted by bloodline View Post
    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.

  8. #8
    Senior Member
    Join Date
    Jan 2014
    Location
    London, UK
    Posts
    110
    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 by bloodline; 02-19-2020 at 07:58 AM.

  9. #9
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,896
    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

  10. #10
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,896
    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.

  11. #11
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,560
    Quote Originally Posted by bloodline View Post
    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.

  12. #12
    Junior Member
    Join Date
    Oct 2016
    Posts
    15
    Quote Originally Posted by defragster View Post
    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?

  13. #13
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,560
    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.

  14. #14
    Senior Member
    Join Date
    Jan 2014
    Location
    London, UK
    Posts
    110
    Quote Originally Posted by defragster View Post

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

    It was :: A-simple-class-for-sub-microsecond-timing
    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.

  15. #15
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,560
    Quote Originally Posted by bloodline View Post
    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)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •