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.
volatile
, which you haven't done. – StarCat Jun 11 '20 at 13:22volatile
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 '20 at 16:18