ESP32-multiple timer

Hey every body. I have a problem regarding setting up two timers on ESP32 using Arduino.h library.

when I set up the first timer it works properly but when I set up the second one, The ESP32 crashes every one minutes and reboots probably when the the flags set or reset in my program.

Timer1 overflows each 4ms
Timer3 overflows every 1 second.

As it is obvious from the results, timer 1 works properly and also Timer 3 works properly because every second the build in LED of ESP32 blinks but every one minute, my ESP crashes. I would appreciate any feedback.

Thanks in advance.
Kind Regards

#include <SPI.h>
#include <Arduino.h>

#define HSPI_MISO   12
#define HSPI_MOSI   13
#define HSPI_SCLK   14
#define HSPI_SS     15

//function prototypes;
bool SPI_Write_Reg(uint8_t register_addr, uint8_t register_value);
uint8_t SPI_Read_Reg(uint8_t Reg_Addr);
float Read_X_Axis(int8_t *xdata);
float Read_Y_Axis(int8_t *xdata);
float Read_Z_Axis(int8_t *xdata);
void Wall_Clock_Manager(void);

static const int spiClk = 1000000; // 1 MHz

//Address for the Range Register
const uint8_t RANGE = 0x2C;    //Considered Reset value for this Register is 0x81.
const uint8_t RANGE_2G = 0x01;  //value to set the 2G_range of the sensor
const uint8_t POWER_CTL = 0x2D; //Power control Register's Address.
const uint8_t MEASURE_MODE = 0x06; // Only accelerometer
const uint8_t ADXL_Reset_Reg_Address= 0x2F;  //this Reg is only Writable. put 0x00 to this Reg onorder to reset it.
                                             //Senors's reset register address

uint8_t  READ_BYTE = 0x01;
uint8_t WRITE_BYTE = 0x00;
uint8_t reg_adress = 0x00;

//address of the X axis
const uint8_t XDATA3 = 0x08;
const uint8_t XDATA2 = 0x09;
const uint8_t XDATA1 = 0x0A;

//address of the Y axis
const uint8_t YDATA3 = 0x0B;
const uint8_t YDATA2 = 0x0C;
const uint8_t YDATA1 = 0x0D;

//address of the Z axis
const uint8_t ZDATA3 = 0x0E;
const uint8_t ZDATA2 = 0x0F;
const uint8_t ZDATA1 = 0x10;

//X axis variables
int8_t xdata[3]={0,0,0};
int32_t Xdata_i=0;
float Xdata_d[1000] = {0};

//Y axis variables
int8_t ydata[3]={0,0,0};
int32_t Ydata_i=0;
float Ydata_d[1000] = {0};

//Z axis variables
int8_t zdata[3]={0,0,0};
int32_t Zdata_i=0;
float Zdata_d[1000] = {0};

char uart_buff[200];

uint8_t second = 0;
uint8_t minute = 0;
uint8_t hour = 0;
uint32_t Sample_Counter = 0;

//Corresponding global variable to Timer 0. 
hw_timer_t * timer3 = NULL;
hw_timer_t * timer1 = NULL;
portMUX_TYPE timerMux3 = portMUX_INITIALIZER_UNLOCKED;
portMUX_TYPE timerMux1 = portMUX_INITIALIZER_UNLOCKED;
volatile byte state = LOW;

uint8_t Sampling_Permision_perhour = 1;
uint8_t Sampling_Permision_per4ms = 0;
//bool Show_DataSet_Flag = false;
uint8_t wall_clock_Flag = 0;


void IRAM_ATTR onTimer3(){
  portENTER_CRITICAL_ISR(&timerMux3);
  wall_clock_Flag = 1;
  portEXIT_CRITICAL_ISR(&timerMux3);
}


void IRAM_ATTR onTimer1(){

  portENTER_CRITICAL_ISR(&timerMux1);
  if(Sampling_Permision_perhour == 1){
    Sampling_Permision_per4ms = 1;
  }
  portEXIT_CRITICAL_ISR(&timerMux1);
  
}

void setup() {

  //Blue LED of NODE MCU initialization.
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 0);

  //SPI Initializations 
  SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SPI-BUS initializing 
  pinMode(HSPI_SS, OUTPUT); //HSPI Slave select pin defined as output

  //ADXL355 initialiyation
  SPI_Write_Reg (ADXL_Reset_Reg_Address , 0x52);           //resetting the ADXL
  SPI_Write_Reg (RANGE, RANGE_2G);            //Setting the ADXL Range to 2G
  SPI_Write_Reg (POWER_CTL, MEASURE_MODE);    //Setting the ADXL to the measurment mode. 

  //Initializing the UART
  Serial.begin(921600);   

  //Timer3 Initialization
  timer3 = timerBegin(0, 80, true); //uint8_t num, uint16_t prescalar 80, bool countUp . our clock is 80MHz 
  timerAttachInterrupt(timer3, &onTimer3, true); 
  timerAlarmWrite(timer3, 1000000, true); //the callback function will be executed when timer ticks 1000000 times
  Serial.println("The timer3 has been activated");
  

  //Timer1 Initialization
  timer1 = timerBegin(1, 80, true); //uint8_t num, uint16_t prescalar 80, bool countUp . our clock is 80MHz 
  timerAttachInterrupt(timer1, &onTimer1, true);
  timerAlarmWrite(timer1, 4000, true); 

  timerAlarmEnable(timer3);
  timerAlarmEnable(timer1);
  Serial.println("The timer1 has been activated");

  //delay(1000);
  
}

// the loop function runs over and over again until power down or reset
void loop() {

  //Wall_Clock_Manager();
  if(wall_clock_Flag){
      Wall_Clock_Manager();
    }

  if (Sampling_Permision_per4ms == 1 && Sampling_Permision_perhour == 1) {

    Sampling_Permision_per4ms = 0;
    Xdata_d[Sample_Counter] = Read_X_Axis(xdata);
    Ydata_d[Sample_Counter] = Read_Y_Axis(ydata);
    Zdata_d[Sample_Counter] = Read_Z_Axis(zdata);
    Sample_Counter++;
    if (Sample_Counter == 1000) {
        Sampling_Permision_perhour = 0;
        Sample_Counter == 0;
        for (int i = 0; i < 1000; i++){ 
      
          sprintf(uart_buff, "Sensor data: Ax = %.3f   Ay = %.3f   Az = %.3f", Xdata_d[i], Ydata_d[i], Zdata_d[i]);
          Serial.println(uart_buff);
          }
      sprintf(uart_buff, "DataSet for hour %d", hour);
      Serial.println(uart_buff);
      }
       
    }    
}


bool SPI_Write_Reg (uint8_t register_addr , uint8_t register_value){
  uint8_t Modif_addr = 0;
  Modif_addr = (register_addr << 1) | WRITE_BYTE;
  digitalWrite(HSPI_SS, LOW);
  SPI.transfer(Modif_addr);
  SPI.transfer(register_value);
  digitalWrite(HSPI_SS, HIGH);

  return true;
}


uint8_t  SPI_Read_Reg(uint8_t Reg_Addr){
  uint8_t Mod_Reg_Addr = (Reg_Addr << 1) | READ_BYTE;
  uint8_t Reg_Value = 0;
  digitalWrite(HSPI_SS, LOW);
  SPI.transfer(Mod_Reg_Addr);
  Reg_Value = SPI.transfer(0x00);
  digitalWrite(HSPI_SS, HIGH);

  return Reg_Value;
}


float Read_X_Axis(int8_t* xdata){

  int32_t Xdata_i;
  float Xdata_d;
  xdata[0] = SPI_Read_Reg(XDATA1);
  xdata[1] = SPI_Read_Reg(XDATA2);
  xdata[2] = SPI_Read_Reg(XDATA3);
  Xdata_i = ((int32_t)xdata[0] >> 4) + ((int32_t)xdata[1] << 4) + ((int32_t)xdata[2] << 12);
  Xdata_d = (float)Xdata_i/256000;
  return Xdata_d;
};

float Read_Y_Axis(int8_t* xdata){

  int32_t Ydata_i;
  float Ydata_d;
  ydata[0] = SPI_Read_Reg(YDATA1);
  ydata[1] = SPI_Read_Reg(YDATA2);
  ydata[2] = SPI_Read_Reg(YDATA3);
  Ydata_i = ((int32_t)ydata[0] >> 4) + ((int32_t)ydata[1] << 4) + ((int32_t)ydata[2] << 12);
  Ydata_d = (float)Ydata_i/256000;
  return Ydata_d;
};

float Read_Z_Axis(int8_t* xdata){

  int32_t Zdata_i;
  float Zdata_d;
  zdata[0] = SPI_Read_Reg(ZDATA1);
  zdata[1] = SPI_Read_Reg(ZDATA2);
  zdata[2] = SPI_Read_Reg(ZDATA3);
  Zdata_i = ((int32_t)zdata[0] >> 4) + ((int32_t)zdata[1] << 4) + ((int32_t)zdata[2] << 12);
  Zdata_d = (float)Zdata_i/256000;
  return Zdata_d;
};

void Wall_Clock_Manager(void){

  wall_clock_Flag = 0;
  second++;
  if(second == 60){
     minute++;
     second = 0;
    if(minute == 1){
       minute = 0;
       hour++;
       Sampling_Permision_perhour = 1;
          }
     }
  state = !state;
  digitalWrite(LED_BUILTIN, state); 

}

But if you really knew where the problem was, you would have fixed it already. Better to post a Minimal Reproducible Example. This is the smallest possible complete code that compiles and displays the problem at hand.

Also, unless variables accessed in the ISR are accessed by BOTH cores, there's no need for the mutex. Interrupts in the core handling the ISR will already be disabled. So, any main code running in that core will not run until the ISR finishes.

Actually most people who make an MRE almost immediately find the problem themselves. :slight_smile:

1 Like

Thanks for your feedback.

I have uploaded the whole code.

about the mutex, previously I removed the corresponding lines but the ESP32 crashes
ESP-1

here are the output of the VS code.

Do you have any Idea?

But... was that the only change that you made, or was that a previous version of the program, with multiple differences compared with the failed code?

At first, I wrote a program which could set up the sensor and read the data from sensor side every 4 millisecond. it was working properly. there was only one timer. then I set up the second one which only was responsible for LED blinking. as the third step, when I wrote the clock manager function and used the two flags ( Sampling_Permision_perhour and Sampling_Permision_per4ms) crashing showed itself up.

The OP might want to consider how being in a mixed environment of using the ESP32's API and the Arduino ESP32's core of possible conflicts of interaction.

Example.

Using the library ESP32Servo which does not respect timer assignments made in setup(). To avoid those issues I use timer 4 first then 3 then 2 then 1 last.

  /* Use 4th timer of 4.
    1 tick 1/(80MHZ/80) = 1us set divider 80 and count up.
    Attach onTimer function to timer
    Set alarm to call timer ISR, every 1000uS and repeat / reset ISR (true) after each alarm
    Start an timer alarm
  */
  timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 1000, true);
  timerAlarmEnable(timer);

The line timer = timerBegin( 3, 80, true ); sets timer 4 for operation.

Next enable a debug level if you can so you can see the ESP32's core debug message.

I do not format my timer interrupt code in the same way that you are doing. Here is an example of how I format the timer interrupt code.

void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  iDoTheBME280Thing++;
  if ( iDoTheBME280Thing == 60000 )
  {
    xEventGroupSetBitsFromISR( eg, evtGroupBits, &xHigherPriorityTaskWoken );
    iDoTheBME280Thing = 0;
  }
}

This does not mean you'll get a float stored in

however this float Xdata_d[1000] = {0.0}; insures that a float will be the thing.

This may work for the high end $30.00+ SPI devices

but the cheap hobby grade stuff... Try dropping down to 7Mhz.

If minute equals 1 minute then minute is set back to 0 and hour is incremented and sampling_permission is equal to a 1.

Would it not make more sense to count up to 60 minutes before incrementing to an hour?

Oh and funny how the code runs for one minute then crashes when the one minute code has issues.

For starters, I suspect the issue is the in the sections that are using arrays. Comment out code sections till the issue goes away then add them back in bits at a time till the issue shows up. One may find one is going out of bounds with an array(s).

I’ve had several discussions on FreeRTOS and ESP32 forums regarding the “High Priority Task Woken” concept. It appears that the proper way to use it in the ESP32 port of FreeRTOS is:

void IRAM_ATTR onTimer() {
  BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  iDoTheBME280Thing++;
  if ( iDoTheBME280Thing == 60000 )  {
    xEventGroupSetBitsFromISR( eg, evtGroupBits, &xHigherPriorityTaskWoken );
    iDoTheBME280Thing = 0;
  }
  if (xHigherPriorityTaskWoken == pdTRUE) {
    portYIELD_FROM_ISR();
  }
}

Okay...
Confused No GIF - Confused No Nope GIFs|833x610.5321285140562

Certain FreeRTOS functions that can be run from an ISR (like xEventGroupSetBitsFromISR) set a variable on return .... in this case named 'xHigherPriorityTaskWoken'.

Your ISR uses that variable to determine if the action performed by that function caused a task to wake up (i.e. leave the Blocked state) AND that task has a higher priority than the one that was interrupted by the ISR.

If that's the case, your ISR requests a context switch (portYIELD_FROM_ISR). So, now when the ISR exits, the new Higher Priority task will execute immediately rather than control being returned to the one that was originally interrupted.

1 Like

I'm mostly on ESP32 platform now and don't have any excuse not to start doing this stuff, except for time. Thanks for the explanation.

What do you see when you run the exception decoder on the stack trace after the crash?

https://github.com/me-no-dev/EspExceptionDecoder

The two references here are pretty good. Note though that they document the "vanilla" version of FreeRTOS. The ESP32 port has additional features to support the dual-core architecture and perhaps other stuff.

So, you'll also need the ESP32 Reference..

@Idahowalker

Hi
Thanks for your replies. I found the problem of my program and was I think two things.

1- the vdd wire connected to the sensor was loose that was the reason we had zero values in our
results,
2- the bug of the timers could be solved temporarily using setting only flags in the interrupt service routines. the crash problem only is solved in this case.

The interrupt service routines:

void IRAM_ATTR onTimer0(){
 // portENTER_CRITICAL_ISR(&timerMux0);
    Sampling_Permision_per4ms = true;
   // Serial.println("/");
 // portEXIT_CRITICAL_ISR(&timerMux0);
}

void IRAM_ATTR onTimer1(){
 // portENTER_CRITICAL_ISR(&timerMux0);
    sampling_permission = true;
   // Serial.println("15 seconds has passed");
 // portEXIT_CRITICAL_ISR(&timerMux0);
}

Now, if I uncomment the print lines inside the time ISRs the ESP 32 crashes. my main question now what is the reason ?

I understand you suggested a better way to set up the timers using RTOS but I am confused why these strange problems happen? which is processor crash in case of adding more lines inside the ISRs.

another ridiculous problem is when I increase the size of the buffers more than 200 the program does not show any thing on terminal !!!

float Zdata_d[200]
float Ydata_d[200]
float Xdata_d[200]

facing theses problems also previously has led me toward not using Arduino.... I implemented this logic on STM32 and was much more convenient.

Here is code:

#include <Arduino.h>
#include <SPI.h>

#define HSPI_MISO   12
#define HSPI_MOSI   13
#define HSPI_SCLK   14
#define HSPI_SS     15

//function protopypes
 bool SPI_Write_Reg(uint8_t register_addr, uint8_t register_value);
 uint8_t SPI_Read_Reg(uint8_t Reg_Addr);
 float Read_X_Axis(int8_t *xdata);
 float Read_Y_Axis(int8_t *ydata);
 float Read_Z_Axis(int8_t* zdata);


//Global variables

static const int spiClk = 4000000; // 1 MHz

//Address for the Range Register
 const uint8_t RANGE = 0x2C;    //Considered Reset value for this Register is 0x81.
 const uint8_t RANGE_2G = 0x01;  //value to set the 2G_range of the sensor
 const uint8_t POWER_CTL = 0x2D; //Power control Register's Address.
 const uint8_t MEASURE_MODE = 0x06; // Only accelerometer
 const uint8_t ADXL_Reset_Reg_Address= 0x2F;  //this Reg is only Writable. put 0x00 to this Reg onorder to reset it.
                                             //Senors's reset register address

static uint8_t  READ_BYTE = 0x01;
static uint8_t WRITE_BYTE = 0x00;
static uint8_t reg_adress = 0x00;

//address of the X axis
 const uint8_t XDATA3 = 0x08;
 const uint8_t XDATA2 = 0x09;
 const uint8_t XDATA1 = 0x0A;

//address of the Y axis
 const uint8_t YDATA3 = 0x0B;
 const uint8_t YDATA2 = 0x0C;
 const uint8_t YDATA1 = 0x0D;

//address of the Z axis
 const uint8_t ZDATA3 = 0x0E;
 const uint8_t ZDATA2 = 0x0F;
 const uint8_t ZDATA1 = 0x10;

//X axis variables
 int8_t xdata[3]={0,0,0};
 int32_t Xdata_i=0;
 float Xdata_d[200] = {0.0};
//static float Xdata_d = 0;

//Y axis variables
 int8_t ydata[3]={0,0,0};
 int32_t Ydata_i=0;
 float Ydata_d[200] = {0.0};
//static float Ydata_d = 0;

//Z axis variables
 int8_t zdata[3]={0,0,0};
 int32_t Zdata_i=0;
 float Zdata_d[200] = {0.0};
//static float Zdata_d = 0;

 char uart_buff[200]={0};
 uint8_t sample_counter = 0;

 bool Sampling_Permision_per4ms = false;
 bool sampling_permission = false;

 uint64_t dataset_counter = 0;

hw_timer_t * timer0 = NULL;
portMUX_TYPE timerMux0 = portMUX_INITIALIZER_UNLOCKED;

hw_timer_t * timer1 = NULL;
portMUX_TYPE timerMux1 = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onTimer0(){
 // portENTER_CRITICAL_ISR(&timerMux0);
    Sampling_Permision_per4ms = true;
   // Serial.println("/");
 // portEXIT_CRITICAL_ISR(&timerMux0);
}

void IRAM_ATTR onTimer1(){
 // portENTER_CRITICAL_ISR(&timerMux0);
    sampling_permission = true;
   // Serial.println("15 seconds has passed");
 // portEXIT_CRITICAL_ISR(&timerMux0);
}

void setup() {
  // put your setup code here, to run once:
//Blue LED of NODE MCU initialization.
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, 0);

  //SPI Initializations 
  SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SPI-BUS initializing
  //SPI.begin(); 
  pinMode(HSPI_SS, OUTPUT); //HSPI Slave select pin defined as output

  //ADXL355 initialiyation
  SPI_Write_Reg (ADXL_Reset_Reg_Address , 0x52);           //resetting the ADXL
  SPI_Write_Reg (RANGE, RANGE_2G);            //Setting the ADXL Range to 2G
  SPI_Write_Reg (POWER_CTL, MEASURE_MODE);    //Setting the ADXL to the measurment mode. 

  //Initializing the UART
  Serial.begin(921600);   
  
  //Timer0 Initialization
  timer0 = timerBegin(0, 80, true); //timer 0, uint16_t prescalar 80, bool countUp . our clock is 80MHz 
  timerAttachInterrupt(timer0, &onTimer0, true);
  timerAlarmWrite(timer0, 4000, true); 
  timerAlarmEnable(timer0);

  //Timer0 Initialization
  timer1 = timerBegin(1, 80, true); //uint8_t num, uint16_t prescalar 80, bool countUp . our clock is 80MHz 
  timerAttachInterrupt(timer1, &onTimer1, true); //true: trigger on edge
  timerAlarmWrite(timer1, 15000000, true); // timer tick = 1u sec
  timerAlarmEnable(timer1);


  Serial.println(" start of the program ");

  Serial.print("setup() running on core ");
  Serial.println(xPortGetCoreID());
  
}

void loop() {
  // put your main code here, to run repeatedly:
  if(Sampling_Permision_per4ms && sampling_permission){
    
    Sampling_Permision_per4ms = false;
    
    Xdata_d[sample_counter] = Read_X_Axis(xdata);
    Ydata_d[sample_counter] = Read_Y_Axis(ydata);
    Zdata_d[sample_counter] = Read_Z_Axis(zdata);
    sample_counter++;
    
    if(sample_counter == 200){
      sampling_permission = false;
      sample_counter = 0;
      dataset_counter++;
      for ( int i = 0; i<200 ; i++) 
      {
        sprintf(uart_buff, "Sensor data: Ax = %.3f  Ay = %.3f  Az = %.3f", Xdata_d[i], Ydata_d[i], Zdata_d[i]);
        Serial.println(uart_buff);
      }
      sprintf(uart_buff, "end of data set %d", dataset_counter);
      Serial.println(uart_buff);
      Serial.print("loop() running on core ");
      Serial.println(xPortGetCoreID());
    }
    
    
  }
     
}


 bool SPI_Write_Reg (uint8_t register_addr , uint8_t register_value){
  uint8_t Modif_addr = 0;
  Modif_addr = (register_addr << 1) | WRITE_BYTE;
  digitalWrite(HSPI_SS, LOW);
  SPI.transfer(Modif_addr);
  SPI.transfer(register_value);
  digitalWrite(HSPI_SS, HIGH);
  return true;
}

 uint8_t  SPI_Read_Reg(uint8_t Reg_Addr){
  uint8_t Mod_Reg_Addr = (Reg_Addr << 1) | READ_BYTE;
  uint8_t Reg_Value = 0;
  digitalWrite(HSPI_SS, LOW);
  SPI.transfer(Mod_Reg_Addr);
  Reg_Value = SPI.transfer(0x00);
  digitalWrite(HSPI_SS, HIGH);
  return Reg_Value;
}


 float Read_X_Axis(int8_t* xdata){
  int32_t Xdata_i;
  float Xdata_d;
  xdata[0] = SPI_Read_Reg(XDATA1);
  xdata[1] = SPI_Read_Reg(XDATA2);
  xdata[2] = SPI_Read_Reg(XDATA3);
  Xdata_i = ((int32_t)xdata[0] >> 4) + ((int32_t)xdata[1] << 4) + ((int32_t)xdata[2] << 12);
  Xdata_d = (float)Xdata_i/256000;
  return Xdata_d;
};

 float Read_Y_Axis(int8_t* ydata){

  int32_t Ydata_i;
  float Ydata_d;
  ydata[0] = SPI_Read_Reg(YDATA1);
  ydata[1] = SPI_Read_Reg(YDATA2);
  ydata[2] = SPI_Read_Reg(YDATA3);
  Ydata_i = ((int32_t)ydata[0] >> 4) + ((int32_t)ydata[1] << 4) + ((int32_t)ydata[2] << 12);
  Ydata_d = (float)Ydata_i/256000;
  return Ydata_d;
};

 float Read_Z_Axis(int8_t* zdata){

  int32_t Zdata_i;
  float Zdata_d;
  zdata[0] = SPI_Read_Reg(ZDATA1);
  zdata[1] = SPI_Read_Reg(ZDATA2);
  zdata[2] = SPI_Read_Reg(ZDATA3);
  Zdata_i = ((int32_t)zdata[0] >> 4) + ((int32_t)zdata[1] << 4) + ((int32_t)zdata[2] << 12);
  Zdata_d = (float)Zdata_i/256000;
  return Zdata_d;
};

about the counting minutes, of course , I set hour to 2 minutes such that seeing results in terminal takes less time.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.