1
votes

I am having problem of executing deepsleep and i2c communication in the ISR(Interrupt Mode).

I am using this library coding it in Arduino IDE :

https://github.com/espressif/arduino-esp32

https://techtutorialsx.com/2017/09/30/esp32-arduino-external-interrupts/

It's working fine for i2c (such as turning on LED) when I run it in the void loop() function, but when I port it to interrupt it doesn't work.

Same with deepsleep, I can't execute it in the interrupt mode. The way I go around it is that I set a flag in interrupt mode to show that I want to deepsleep and then execute it in the void loop() function.

Does anyone have any solution on how to make this work? (code is just for i2c and esp32)

#include <Wire.h>

#if defined(ARDUINO_ARCH_SAMD)
// for Zero, output on USB Serial  console, remove line below if using programming port to program the Zero!
   #define Serial SerialUSB
#endif

// Interrupt Setup - TIMER
hw_timer_t * timer = NULL; //configure the timer, need pointer to a variable type of hw_timer_t
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; // used to sync main loop and ISR
RTC_DATA_ATTR bool should_sleep = false;

// Setting ADC Properties - BATTERY
int voltage_amplifier = 0; 
int battery_percentage = 0; 

// Set i2c Address - I/O EXPANDER
const int address = 0x20;
uint16_t led_status = word(B11111111,B11111111);

// INTERRUPT MODE - INSERT INBETWEEN portENTER and portEXIT
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  // led_battery();        led doesn't update if used here
  portEXIT_CRITICAL_ISR(&timerMux);
}

void led_battery(){
    voltage_amplifier = analogRead(34);
    Serial.println(voltage_amplifier);
    int bit_max = 4096;
    int battery_percentage = voltage_amplifier*100/bit_max;

    // If battery is below 20%
    if (battery_percentage <= 20){
      led_status &= word(B00111111,B11111111); // clearing the bits that we want to change whilst preserving the other unchanged bits
      led_status |= ~word(B11000000,B00000000); // setting up the bits that we want to change
      pf575_write(led_status);
    }

    else if (battery_percentage <= 40){
      led_status &= word(B00011111,B11111111); // clearing the bits that we want to change whilst preserving the other unchanged bits
      led_status |= ~word(B11100000,B00000000); // setting up the bits that we want to change
      pf575_write(led_status);
    }

    else if (battery_percentage <= 60){
      led_status &= word(B00001111,B11111111); // clearing the bits that we want to change whilst preserving the other unchanged bits
      led_status |= ~word(B11110000,B00000000); // setting up the bits that we want to change
      pf575_write(led_status);
    }

    else if (battery_percentage <= 80){
      led_status &= word(B00000111,B11111111); // clearing the bits that we want to change whilst preserving the other unchanged bits
      led_status |= ~word(B11111000,B00000000); // setting up the bits that we want to change
      pf575_write(led_status);
    }

    else if (battery_percentage <= 100){
      led_status &= word(B00000011,B11111111); // clearing the bits that we want to change whilst preserving the other unchanged bits
      led_status |= ~word(B11111100,B00000000); // setting up the bits that we want to change
      pf575_write(led_status);
    }
}

void ioexpander_setup(){
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\n Blinker Ready");
  Wire.begin();
}

void pf575_write(uint16_t data) {
  Wire.beginTransmission(address);
  Wire.write(lowByte(data));
  Wire.write(highByte(data));
  Wire.endTransmission();
}

void timer_setup(){
  // Base Clock Frequency = 80MHz ; Timer Frequency = 1MHz | Clock Cycle = 1us [in this case]
  timer = timerBegin(0,80,true); // return a pointer to a structure of type hw_timer_t

  // Timer binded to a handling function
  timerAttachInterrupt(timer, &onTimer, true); // Parameter : (timer_initialization, address_interrupt,flag_to_activate - true(edge)/false(level))

  // Specify the counter value in which the timer interrupt will be generated (set every 10 ms)
  timerAlarmWrite(timer, 10000, true); // Parameter : (timer_initialization, when_to_interrupt (us), flag_to_reload)

  // Enable the timer
  timerAlarmEnable(timer);
}

void setup() {
  Serial.begin(115200);

  // IO Expander
  ioexpander_setup();

  // Timer
  timer_setup();

}

void loop() {  
  led_battery();    //led update if used here
}
1
Please share a minimal, viable, complete example of code that demonstrates your problem.romkey
@JohnRomkey here you go, hope it is clear. Sorry if there's any annoying formatting, I'm new to stackoverflow.coyodha

1 Answers

1
votes

When you call led_battery() from the interrupt handler, you're doing waaaaay too much work there.

The interrupt can interrupt anything that doesn't have interrupts locked out.

Suppose your code is outputting something using Serial and the timer interrupt happens. Now your code was running code somewhere inside Serial, and you call Serial again... while the software and hardware can be in inconsistent states.

This is the case with every subroutine and hardware access you do from an interrupt handler. The only way to protect against this is to disable interrupts whenever your code might be accessing hardware or might have modifying data structures.

Unfortunately, disabling interrupts is error-prone - if you forget to do it, you'll have mysterious crashes. If you forget to reenable them, you're in big trouble - your network, timers and Serial will all stop working. It also adds a lot of overhead to your code. And it degrades your overall system performance - it will delay or cause you to miss network and timer events. You may drop characters from the serial port. And you can be certain that none of the code in the Arduino Core is doing this for you.

So, long story short, locking out interrupts so that you can do a lot in an interrupt handler is just not practical.

You also want to minimize the time you spend in the interrupt handler, as this preempts network stack, timer, serial and other hardware processing and may block other

You pointed out the way we deal with this in your original post: set a flag (make sure it's volatile) in the interrupt handler and handle it in a task. Unless you really, really know what you're doing and how all the software works in your system, this is the only practical way to handle this. If you try to do the amount of work and call the things you're calling from an interrupt handler, your program will malfunction and crash.