Arduino pin interrupts

Hello,
This is great topic, I have learned a lot.
I want to make Arduino pro mini 3.3V/8MHZ + RFM95W sensor node using LMIC and OTAA.
I want after Join to TTN and first transmission the node to go to DEEP SLEEP, and to wake it up from button press with external interrupt. Is that possible with rocketscream low power library. Can someone share some code example?
Thanks a lot.

For those interested, i think i kinda made it work with LoRa library and TPL5110.
As with the TPL5110, I have no idea what it should look like with LMIC, if someone could show us, that’d be great :wink:

What works:

  • The TPL5110 with a ~56K resistor seems to work, but not sure if i can call that deep sleep regarding the crazy current consumption in “deep sleep”
  • works on battery with ANR26650 m1b (3.3V)

Issues:

  • If i include adcvcc.h library, the arduino loop constantly in the setup function and never reach the loop function, thus i’m not able to read the battery voltage
  • i removed led and voltage regulator on the arduino pro mini 3.3V, but the board still consume ~8 mA in sleep, i’m not the sure the TPL5110/code is doing what he is supposed to do
  • Once or twice a day, the bme will send 0 for all, pressure, temp and humidity :frowning:

#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
// #include “adcvcc.h”
#include <Arduino.h>
#include <rBase64.h>
#include <ArduinoJson.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#ifndef SERIAL_DEBUG
#define SERIAL_DEBUG 9600
#endif
// 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

Adafruit_BME280 bme;

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

uint32_t ms;

void setup() {
delay(1000);
Serial.begin(SERIAL_DEBUG);
Serial.println(F(“Starting TPL loop”));

if (! bme.begin(0x76)) {
Serial.println(“no bme”);
while (1);
}

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);

Serial.println(“init ok”);

void sendData(){
const size_t capacity = JSON_OBJECT_SIZE(4);
DynamicJsonDocument doc(capacity);
String output;
doc[“ta”] = “outside”;
doc[“t”] = bme.readTemperature();
doc[“h”] = bme.readHumidity();
doc[“p”] = bme.readPressure() / 100.0F;
// doc[“bat”] = (int)(readVcc() / 100);
serializeJson(doc, output);
Serial.println(output);
rbase64.encode(output);
LoRa.beginPacket();
LoRa.print(rbase64.result());
LoRa.endPacket();
}

void loop() {
bool drvn;
drvn = digitalRead(drvn_pin);
if (drvn == LOW) {
sendData();
digitalWrite(done_pin, HIGH);
delay(1);
digitalWrite(done_pin, LOW);
delay(1);
}
}

Can’t see any place in your code where you send Atmega to sleep. 8mA seem like normal working current if MCU is doing nothing.

Check out Atmega328P datasheet page 48. There is details about how to put it in different types of sleep modes. To wake it up you should use pinchange interrupt (as TPL5110 drv pin is not connected to hardware INT but A2)

Kevin Darrah has great video about deep sleep mode on Atmega

Thanks a lot for your answer, pardon my poor understanding of micro proc but its good to see that i was doing it the wrong way :slight_smile:

Did you meant something like this?

const uint8_t drvn_pin = PIN_A2;

void setup() {
  
  //Save Power by writing all Digital IO LOW - note that pins just need to be tied one way or another, do not damage devices!
  for (int i = 0; i < 20; i++) {
    if(i != 2)//just because the button is hooked up to digital pin 2
    pinMode(i, OUTPUT);
  }
  
  attachInterrupt (digitalPinToInterrupt (drvn_pin), pinChange, CHANGE);

  //Disable ADC - don't forget to flip back after waking up if using ADC in your application ADCSRA |= (1 << 7);
  ADCSRA &= ~(1 << 7);
  
  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
}

void loop() {
  //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 pinChange ()
{
    take_bme280MeasurementAndSendViaLora();  
}

Sounds like Voodoo to me, never done that or needed to on an ATmega328P.

There are some pins that may need to be high or low as outputs going into sleep and that depends on the circuit.

Assuming that for low power all pins need to be set to outputs and then low or high is just wrong.

Yes i agree, this part was additional and not really needed indeed, should remove it for the sake of keeping focus on what really bothers me as i’m more focus on how to make sleep this board and wake it up via TPL5110 :wink:

const uint8_t drvn_pin = PIN_A2;

void setup() {
    
  attachInterrupt (digitalPinToInterrupt (drvn_pin), pinChange, CHANGE);

  //Disable ADC
  ADCSRA &= ~(1 << 7);
  
  //ENABLE SLEEP - this enables the sleep mode
  SMCR |= (1 << 2); //power down mode
  SMCR |= 1;//enable sleep
}

void loop() {
  //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 pinChange ()
{
    take_bme280MeasurementAndSendViaLora();  
}

If anyone that made it running could just this little routine of sleeping and waking it up via pinchange interrupt as it seems this board is made to be woke up this way.

If you want to wake up a board (from sleep) use the TPL5010, its designed for that purpose.

Yes, that’s the whole point, this board (v1.2) use the TPL5110 , i soldered it along with the other components, i just dunno much how to actually make this board sleep and waking it up using it :wink:
@jenertsA Seems to got a point, just that i’m not quite sure whether my example make sense or not

This starts to look like a right direction. I would advise not to perform long routines as measurement taking in interrupt function. In there just “set flag” that you can use in further code.
I haven’t used pinchange interrupt and can’t comment on how it works.
Keep in mind that you have to write Done pin high at some point, so TPL knows that it can start next cycle.

1 Like

some links about the subject - however, I think this is a new topic on its own

2 Likes

So i tried several tutorials and the closest i went so far was

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

void wakeUp(){
   Serial.println("Interrrupt Fired");//Print message to serial monitor
   sleep_disable();//Disable sleep mode
   detachInterrupt(done_pin); //Removes the interrupt from pin 2;
   Serial.println("let’s do some process here");
   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");
}

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
}

But still doesn’t work, keep printing wake up every seconds, so i guess i will just buy a tpl5110 from adafruit and turn on and off the 3.3v to the board every X min, it should consume even less and do no need anything in the code to work :wink: thus much simpler :stuck_out_tongue:
I don’t use ttn network ( self hosted loraserver ), i also only use abp activation and i don’t mind that anyone hack my keys and send me their temp sensors :joy: , then i think that will be best choice :wink:

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: