0

I've been trying to test the stability and consistency of hardware interrupts on my NodeMCU ESP8266. I'm using the Arduino IDE. This sketch should not require any external circuitry and I think, but am not certain that the problem is a value overflowing somewhere. QUESTION 1: Can anyone point out where the problem that's causing Exception(0)?

The hw_timer_* functions are documented in the ESP8266 NONOS SDK reference pdf. The hw_timer_* declarations where not found by the Arduino IDE after I set it up with the board manager url here, so I copied hw_timer.h and hw_timer.c from the Expressif github repo into the same folder as my .ino file. The interrupt functions run now, but after four iterations, I get the exception & a stack dump and then the board reboots with this rst message:

 ets Jan  8 2013,rst cause:4, boot mode:(3,6)

The code of my sketch is here. You shouldn't need any pins or anything. EDIT: I added volatile before the 3 vars referenced in interrupt and in the other fns.

#include "user_interface.h";
#include "hw_timer.h";
#include "hw_timer.c";

// interrupt interval used for my hw_timer
#define timerInterval 100

// the number of cycle count samples we collect before analyzing
#define totalSamples 500

volatile uint32_t cycleSamples[totalSamples];
volatile uint16_t cycleSampleCount = 0;

// calculate min/max/mean
uint32_t elapsedCycles; // the number of cycles elapsing between two samples
float elapsedSum = 0; // cycles are large numbers...just in case we exceed 2^32 we use a float
uint32_t elapsedMax = 0;
uint32_t elapsedMin = 0xFFFFFFFF;
uint32_t elapsedCount = 0;
float elapsedMean;

// for std dev calculation
float diffSumSquared = 0;
float variance = 0;
float stdDev = 0;

// set to true by the timer fn when it has gathered the samples
volatile bool analyzeSamples = false;

// The hardware timer callback function must NOT be defined with ICACHE_FLASH_ATTR.
// Interrupt callback functions must be in IRAM, because the flash may be in the middle
// of other operations when they occur. Do this by adding the ICACHE_RAM_ATTR attribute
// on the function definition
// @see https://arduino-esp8266.readthedocs.io/en/latest/reference.html#interrupts
ICACHE_RAM_ATTR void my_timer_callback(void) {

  if (analyzeSamples) {
    // if this fn has collected all the samples, we have to skip this until it’s finished analyzing them
    // and resets this
    return;
  }

  if (cycleSampleCount >= totalSamples) {
    // we can't disable this timer
    analyzeSamples = true;
    return;
  }

  // take a peek at the cycles counter to check timing accuracy  
  cycleSamples[cycleSampleCount] = ESP.getCycleCount();
  cycleSampleCount++;

}


void setup() {

  Serial.begin(115200);

  for(uint8_t i=5; i>0; i--) {
    Serial.printf("counting %d\n", i);
    delay(1000);
  }

  // START THE SAMPLE TIMER USING HARDWARE INTERRUPTS
  // use NMI_SOURCE for highest priority and quickest/most accurate timing
  hw_timer_init(NMI_SOURCE, 1);
  hw_timer_set_func(my_timer_callback);

  // If NM is used as the ISR source for auto-loading the timer, parameter val of hw_timer_arm can not be less than 100
  hw_timer_arm(timerInterval);

}

void loop() {
  // TODO we need to do something nondeterministic here to see if it fucks up timing of interrupts


  if (analyzeSamples) {

    // this doesn’t work to try and disable the interrupt fn
    // hw_timer_set_func(0);
    // nor does this
    //hw_timer_arm(0);

    Serial.println("Sampling complete, analyzing...");

    elapsedSum = 0; // cycles are large numbers...just in case we exceed 2^32 we use a float
    elapsedMax = 0;
    elapsedMin = 0xFFFFFFFF;
    elapsedCount = 0;

    // we loop from the 1th (i.e, the second) item
    for(uint32_t i = 1; i<totalSamples; i++) {
      elapsedCycles = cycleSamples[i] - cycleSamples[i-1];
      Serial.printf("%d %d\n", i, elapsedCycles);
      elapsedSum += (float)elapsedCycles;
      elapsedMax = max(elapsedMax, elapsedCycles);
      elapsedMin = min(elapsedMin, elapsedCycles);

      elapsedCount++;
    }
    // calculate mean
    elapsedMean = elapsedSum / (float)elapsedCount;
    Serial.print("Average elapsed cycles: ");
    Serial.println(elapsedMean);
    // report min/max
    Serial.print("Elapsed cycles max: ");
    Serial.println(elapsedMax);
    Serial.print("Elapsed cycles min: ");
    Serial.println(elapsedMin);


    // calculate the variance/std dev
    diffSumSquared = 0;
    for(uint32_t i = 1; i<totalSamples; i++) {
      elapsedCycles = cycleSamples[i] - cycleSamples[i-1];
      diffSumSquared +=  sq(elapsedCycles - elapsedMean);
    }
    variance = diffSumSquared / elapsedCount;
    Serial.print("Variance: ");
    Serial.println(variance);
    stdDev = sqrt(variance);
    Serial.print("Std Dev: ");
    Serial.println(stdDev);

    // reset the samples to zero and let's try again
    Serial.println("Gonna reset...");

    ets_intr_lock( ); //close interrupt
    for(uint32_t i = 0; i<totalSamples; i++) {
      cycleSamples[i] = 0;
    }
    cycleSampleCount = 0;
    analyzeSamples = false;
    interrupts();
    ets_intr_unlock(); //open interrupt
    system_soft_wdt_restart();


    Serial.println("Reset complete.");

//    stopStopStop = true;

  } else {
    Serial.println("not yet!");
  }
  Serial.println("sleeping 1...");
  delay(1000); // sleep 1000ms


}

This brings me to QUESTION 2: How do you disarm/cancel a hw_timer? As you can see in this code, I've tried setting the FN to zero and/or null and I've also tried setting hw_timer_arm(0).

The exception and stack dump seem to consistently happen after the sleep 1... debug message which corresponds to the end of the loop() function. Some typical debug output:

// useful and happy output above right up to this point
497 8000
498 8000
499 8000
Average elapsed cycles: 8000.00
Elapsed cycles max: 8263
Elapsed cycles min: 7738
Variance: 396.13
Std Dev: 19.90
Gonna reset...
Reset complete.
sleeping 1...

Exception (0):
epc1=0x40201030 epc2=0x00000000 epc3=0x400043a3 excvaddr=0x00000000 depc=0x00000000

>>>stack>>>

ctx: sys
sp: 3ffedf20 end: 3fffffb0 offset: 0190
3ffee0b0:  00000000 00000000 00000000 4010480d  
3ffee0c0:  40105513 00000000 00000000 00000000  
3ffee0d0:  00000010 00000000 00000000 40000f68  
3ffee0e0:  00000004 00000000 40000f58 00000000  
3ffee0f0:  60000200 3fffeed0 00000002 7c097c08
// giant stack dump continues....

Any help would be much appreciated.

  • 2
    I have not looked at your code in detail, but you should declare any global variable that can be changed inside an ISR as volatile, which you haven't done. – StarCat Jun 11 at 13:22
  • @StarCat thanks for that. I've added volatile before each of the 3 vars mentioned in both the interrupt fn and in the other fns. It still crashes and dumps the stack after about four iterations. – S. Imp Jun 11 at 16:18
0

So these docs say that RST cause 4 is watchdog reset. Evidently arduino has a watchdog timer to check and make sure that your program isn't stuck in an infinite loop. Therea are some tips in that link on how to avoid this reset. Generally speaking, you need to make sure your loop doesn't spend too much time doing any one thing or handling too many interrupts or it might start to get overwhelmed and then the arduino code can't get around to all the housekeeping it has to do.

Regarding the disabling of a hardware timer function, I posted an issue on the ESP8266 github repo and someone offered what looks like a helpful response, which I have not tested. They suggest:

Call hw_timer_init with second argument = 0; then in your ISR call hw_timer_arm() if you want to keep it going and do not call it if you want to "disarm."

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.