Arduino pin interrupts

So…

The Serial.print function is interrupt driven. What is happening is that it starts outputting the message, character at a time under control of interrupts and you put it to sleep but the serial peripheral causes an interrupt when the transmit holding register is empty waking up the processor.

You are also calling Serial.print both inside and outside the interrupt handler - this is bad for multiple reasons.

To fix this, at the end of setup add a delay so that the “init done” message has been fully transmitted. Similarly add another delay after the “just woke up!” message before putting it to sleep again. Then, inside the interrupt handler, get rid of the Serial.print statements.

1 Like

Thanks for your help :wink:

I tried as follow:

#include <avr/sleep.h>
#include <Arduino.h>
const uint8_t done_pin = PIN_A1;
const uint8_t drvn_pin = PIN_A2;

void wakeUp(){
   sleep_disable();//Disable sleep mode
   detachInterrupt(done_pin); //Removes the interrupt from pin 2;
   delay(5000); //let’s pretend we do some calculations here for 5 second
   digitalWrite(done_pin, HIGH); //restart timer
}

void setup() {
  Serial.begin(9600);
  pinMode(done_pin, OUTPUT);
  digitalWrite(done_pin, LOW);
  pinMode(drvn_pin, INPUT);
  Serial.println("init done");
  delay(1000);
}

void loop() {
    sleep_enable();//Enabling sleep mode
    attachInterrupt(done_pin, wakeUp, LOW);//attaching a interrupt to pin d2
    delay(1000);
    sleep_cpu();
    Serial.println("just woke up!");//next line of code executed after the interrupt
	delay(1000);
}

but it keep printing:

init done
just woke up
juste woke up

every seconds , coming from esp32 world where all i had to do for sleeping was calling a function is a big change for me, and i might overestimated myself :smile:

You are detaching the interrupt (no real need to do that) but you are not clearing the interrupt flag so when you re-enable the interrupt, an interrupt will occur.

You do not need to attach and detach the interrupt. You can attach it once in setup but disable it. Then when you want to enable it again (just before going to sleep), you clear the interrupt and enable the interrupt.

I can’t remember if the Arduino Mini includes USB, if it does, then you also need to disable the USB peripheral otherwise it will wake up the processor based on normal USB activity.

It’s out of my league, i really didn’t understood what you meant when you say:

You do not need to attach and detach the interrupt. You can attach it once in setup but disable it. Then when you want to enable it again (just before going to sleep), you clear the interrupt and enable the interrupt.

I need to digest that and watch some more videos about arduino interrupts coz i can’t decipher your statement :frowning:

Or more likely switching the board off like that will consume more power …

1 Like

Rather than use a delay use Serial.flush(); this will wait until the serial out buffer is empty, then continue. This way you dont have to guess how long the delay should be.

1 Like

thanks for all your suggestions, after having read them and also watch this video

I came to this version :

#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Arduino.h>
#include <rBase64.h>

//LORA
#define NSS    10
#define RST     A0
#define DI0     2
#define SCK     13
#define MISO    12
#define MOSI    11
#define BAND  868E6
#define BLUE_LED 13
#define SF_FACTOR 7
#ifndef SERIAL_DEBUG
#define SERIAL_DEBUG 9600
#endif

const uint8_t done_pin = PIN_A1;
const uint8_t drvn_pin = PIN_A2;

void do_smth(){
	  bool drvn;
	  drvn = digitalRead(drvn_pin);
		if (drvn == LOW){
	    LoRa.beginPacket();
	    LoRa.print("hello world");
	    LoRa.endPacket();
        // reset tpl5110 timer for an another round
        digitalWrite(done_pin, HIGH);
	}
}

void setup() {
	Serial.begin(SERIAL_DEBUG);
	Serial.println(F("Starting TPL loop"));


	LoRa.setPins(SS,RST,DI0);
	    if (!LoRa.begin(BAND)) {
	      Serial.println("no lora");
	      while (1);
	}
    LoRa.setSpreadingFactor(SF_FACTOR);
	pinMode(done_pin, OUTPUT);
	digitalWrite(done_pin, LOW);
	pinMode(drvn_pin, INPUT);
	digitalWrite(done_pin, HIGH);
	// when the DRIVERPIN switch from HIGH to LOW, it means the timer is expired, thus time to trigger ISR
	attachInterrupt(digitalPinToInterrupt(drv_pin), do_smth, mode)
	Serial.println("init done");
	Serial.flush();
}

void loop() {
}

Still no joy :frowning: when the tpl switch receive HIGH on done_pin, i expect it to start its timer, and when timer expire, goes from HIGH to LOW on the drv_pin, thus triggering a FALLING event, thus calling my ISR , i don’t get it why this time it doesn’t work still :frowning:

Well you need to sort out the code so it compiles first.

And then go back and study the tutorial again and check what it says about what should not be in an interrupt service routine.

Perhaps you should start with the most basic example of an Interrupt tutorial, pressing a button to light an LED for instance, then add more functions and see what happens.

but the code does compile and run on the board, im just using lora transmission instead of a LED since we can’t serial print stuff in the ISR, i can’t find more simple example. But im afraid nobody actually knows how to make this board working with the TPL as i never seen a success story so far :sleepy: I feel like christopher columbus , walking into untouched lands :desert_island: :joy:

attachInterrupt(digitalPinToInterrupt(drv_pin), do_smth, mode)

Where are ‘drv_pin’ and ‘mode’ defined ?

ah but it was a tiny typo :wink:

attachInterrupt(digitalPinToInterrupt(drvn_pin), do_smth, FALLING);

    #include "PinChangeInterrupt.h" // https://github.com/NicoHood/PinChangeInterrupt this one

    const int drvPin = A2;
    const int DONEPin = A1;


    void setup() {
      // set pin to input with a pullup, DONE to output
      pinMode(drvPin, INPUT_PULLUP);
      pinMode(DONEPin, OUTPUT);

      Serial.begin(9600);

      attachPCINT(digitalPinToPCINT(drvPin), do_smth, FALLING); // DRV goes LOW, when TPL time has elapsed

      //ENABLE SLEEP - this enables the sleep mode
      SMCR |= (1 << 2); //power down mode
      SMCR |= 1;//enable sleep

    }


    bool sendData = true; // tracks if you need to send data
    void do_smth(void) {

      //say tahat you need to send data
      //nothing else goes here!
      sendData = true;
    }

    void loop() {
      if (sendData)
      {
        //here you read data, process it and send!
        
        Serial.println("Send data now");

        /*

            LORA SEND HERE

        */

        sendData = false; //so do_smth() can alter state;
        
        digitalWrite(DONEPin, HIGH); // reset TPL timer;
        delay(50); // have to test this on real Hardware
        digitalWrite(DONEPin, LOW); //so that TPL doesn't go to sleep at time it wakes.
        
        goToSleep();
      }
    }

    void goToSleep()
    {
      Serial.println("GOING TO SLEEP");
      Serial.flush();
      
      //BOD DISABLE - this must be called right before the __asm__ sleep instruction
      MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
      MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time
      __asm__  __volatile__("sleep");//in line assembler to go to sleep
    }

I don’t have HW, but try this out.

1 Like

sendData probably should be volatile

1 Like

OH mennn!!! seriously, you come and post your code just like this and it freaking work beautifully !!!
Well it does, it does work amazing, goes to sleep, wake up according the resistor value i set, it bring tears to my eyes !! Thanks, thanks a millions time, the first working code for the board v1.2 , you made it xD

1 Like

Yes, but the code could not have been tested with the ‘typos’ in place.

Dont forget to take the serial prints out of the interrupt service routine, as the tutorial suggested.

1 Like

Gregleguenec Be great now you have 1.2 ver working,can you publish the full code so others can use. say on Git Hub
Si

Sure, but i don’t use LMIC nor TTN network, since all i need to monitor are my rooms temp/humidity, i have my own custom gateway pushing from lora >> udp >> influxdb (grafana) self hosted.

15

So you will need to adapt the code and change LoRa lib to LMIC if you wanna join TTN network :wink:

So there you go:

#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Arduino.h>
#include <rBase64.h>
#include <ArduinoJson.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "PinChangeInterrupt.h"

// BME280
#define SEALEVELPRESSURE_HPA (1013.25)
//LORA
#define NSS    10
#define RST     A0
#define DI0     2
#define SCK     13
#define MISO    12
#define MOSI    11
#define BAND  868E6
#define BLUE_LED 13
#define SF_FACTOR 7

const int drvPin = A2;
const int DONEPin = A1;
bool sendData = true;
Adafruit_BME280 bme;

void do_smth(void) {
  sendData = true;
}

void setup() {
  if (! bme.begin(0x76)) {
	while (1);
  }

  bme.setSampling(Adafruit_BME280::MODE_FORCED,
		    Adafruit_BME280::SAMPLING_X1, // temperature
		    Adafruit_BME280::SAMPLING_X1, // pressure
		    Adafruit_BME280::SAMPLING_X1, // humidity
		    Adafruit_BME280::FILTER_OFF   );
  delay(10);

  LoRa.setPins(SS,RST,DI0);
    if (!LoRa.begin(BAND)) {
      while (1);
    }
  LoRa.setSpreadingFactor(SF_FACTOR);
  // set pin to input with a pullup, DONE to output
  pinMode(drvPin, INPUT_PULLUP);
  pinMode(DONEPin, OUTPUT);

  attachPCINT(digitalPinToPCINT(drvPin), do_smth, FALLING); // DRV goes LOW, when TPL time has elapsed

  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep

}

void goToSleep()
{
  LoRa.sleep();

  ADCSRA &= ~(1 << 7);
  //BOD DISABLE - this must be called right before the __asm__ sleep instruction
  MCUCR |= (3 << 5); //set both BODS and BODSE at the same time
  MCUCR = (MCUCR & ~(1 << 5)) | (1 << 6); //then set the BODS bit and clear the BODSE bit at the same time
  __asm__  __volatile__("sleep");//in line assembler to go to sleep
}

void loop() {

  if (sendData)
  {
    bme.takeForcedMeasurement();

    const size_t capacity = JSON_OBJECT_SIZE(4);
    DynamicJsonDocument doc(capacity);
    String output;
    doc["t"] =  bme.readTemperature();
    doc["h"] = bme.readHumidity();
    doc["p"] = bme.readPressure() / 100.0F;

    serializeJson(doc, output);
    // For local test puropse, it's enough, to make it prod ready, use AES !!!
    rbase64.encode(output);
    LoRa.beginPacket();
    LoRa.print(rbase64.result());
    LoRa.endPacket();

    sendData = false; //so do_smth() can alter state;

    digitalWrite(DONEPin, HIGH); 
    delay(1); 
    digitalWrite(DONEPin, LOW); 

    goToSleep();
  }
}

For sure, you can make much prettier with some more love and patience.

Regarding battery consumption, i put a 2.5 ohm resistor in serie and tried to measure the voltage drop, thus deducing the current.

With BME280 and rfm95W WITHOUT sleep mode ~2.3 mA
With BME280 and rfm95W WITH sleep mode , i can’t measure as my UT61E give me 0.00 mV, thus, all i know is that i’m under 4 uA

PS: I would like to thanks all those helped me, but a special thanks to @jenertsA for helping on the TPL code !

1 Like

Lots of sad faces.

Yes i understand, but the fairplay policy is a mess, let’s agree, i would need sometimes to push every 5 min, sometimes in SF7, soon some other nodes on some mountain’s top (~70km away) in SF12, meaning crazy amount of air time per day.

And i don’t get the idea of publishing stuff on internet while all you need can be run in local env :wink: Anyway, just my opinion :wink:

The fair access plocy is not broken, it just sets a limit on what you get for free.

If the free access is not enough for you, contact the things network industries where you can pay for more.

If a node needs ‘crazy amount of air time per day’ then you would could well be breaking the legal duty cycle limit of 1%.

1 Like