Snapshot from the ATmega328P datasheet:
According to the above section of the ATmega328P datasheet, only a Level or Pin change interrupt should wake up the CPU from Power Down Sleep Mode.
However, in the following code, a rising edge is being used to wake up the CPU from Power Down Mode.
#include <LowPower.h>
const byte led_pin = 8;
const byte interrupt_pin = 2;
volatile byte state = LOW;
void setup() {
Serial.begin(9600);
pinMode(led_pin,OUTPUT);
}
void loop() {
// the interrupt must be attached each loop
attachInterrupt(digitalPinToInterrupt(interrupt_pin), interrupt_routine, RISING);
LowPower.powerDown(SLEEP_FOREVER,ADC_OFF,BOD_OFF); // sleep until interrupt
detachInterrupt(digitalPinToInterrupt(interrupt_pin)); // remove interrupt
// the usual wake routine that turns on the LED
if (state == HIGH) {
digitalWrite(led_pin, HIGH);
delay(500);
}
if (state == HIGH){
state = LOW;
digitalWrite(led_pin,LOW);
}
}
void interrupt_routine() {
state = HIGH;
}
Code taken from "Arduino Interrupts with PIR Motion Sensor".
I don't understand how this code is working.
The Atmel datasheet make people believe that you can only use LOW interrupts to wake up the MCU when it is in sleep mode. It has however been long confirmed that you can use any type of interrupt (Rising edge / Falling edge / Low level / any logical change) to wake up the ATmega328P from sleep mode. There was a mistake on Atmel's datasheet. This was confirmed and documented by Nick Gammon on his post on Interrupts.
Related
I've been experimenting with the sleep options of both the ESP8266 wifi chip and the MPU9250 IMU. The ESP has a deep sleep command which essentially shuts the chip down apart from the real time clock until the RESET pin is pulled low, either by the ESPs GPIO16 or by an external interrupt.
The MPU9250 provides this interrupt in the form of its WOM (Wake On Motion) function, which brings the chip to bare minimum functionality until it detects motion on the built-in accelerometer, at which point its INT pin gets pulled high (I attached this pin to the gate of an NMOS transistor between the RESET pin of the ESP and GND to invert the interrupt).
When I set it up and use the following code, however, the setup enters a reset loop; both print statements execute, but I am unsure if the ESP actually has time to execute the DeepSleep command because it instantly resets as soon as the "Got here" prints and doesn't wait for motion.
However, if I disconnect and reconnect the INT connection from the NMOS while the program is running, it works temporarily and sleeps until it detects motion, at which point the reset loop begins again (even if the MPU9250 is kept completely still after moving). This means the WOM functionality is working, but something is causing the INT pin to ping high when it shouldn't be, and I can't figure out what the problem is. Does anyone know what the problem could be? Is it something I can fix with code alone?
Main code (loop() is empty):
#include <quaternionFilters.h>
#include <MPU9250.h>
#include <ESP8266WiFi.h>
extern "C" {
#include "gpio.h"
}
extern "C" {
#include "user_interface.h"
}
MPU9250 myIMU;
void setup()
{
Wire.begin(5, 14);
Serial.begin(74880);
printf("WAKE ME UP INSIDE");
delay(500);
sensorMpu9250WomEnable();
printf("Got Here");
ESP.deepSleep(0, WAKE_RF_DEFAULT);
}
In a separate file:
bool sensorMpu9250WomEnable(void)
{
uint8_t val;
// Clear registers
val = 0x80;
myIMU.writeByte(MPU9250_ADDRESS, PWR_MGMT_1, val);
delay(10);
// Enable accelerometer, disable gyro
val = 0x07;
myIMU.writeByte(MPU9250_ADDRESS, PWR_MGMT_2, val);
delay(10);
// Set Accel LPF setting to 184 Hz Bandwidth
val = 0x01;
myIMU.writeByte(MPU9250_ADDRESS, ACCEL_CONFIG2, val);
delay(10);
// Enable Motion Interrupt
val = 0x40;
myIMU.writeByte(MPU9250_ADDRESS, INT_ENABLE, val);
delay(10);
// Enable Accel Hardware Intelligence
val = 0xC0;
myIMU.writeByte(MPU9250_ADDRESS, MOT_DETECT_CTRL, val);
delay(10);
// Set Motion Threshold
val = 0x40;
myIMU.writeByte(MPU9250_ADDRESS, WOM_THR, val);
delay(10);
// Set Frequency of Wake-up
val = 6;
myIMU.writeByte(MPU9250_ADDRESS, LP_ACCEL_ODR, val);
delay(10);
// Enable Cycle Mode (Accel Low Power Mode)
val = 0x20;
myIMU.writeByte(MPU9250_ADDRESS, PWR_MGMT_1, val);
delay(10);
return true;
}
Thank you very much for this snippets, they helped me to get my project up and running.
I could fix the issue by adding a delay before start the deep sleep:
Wire.begin(I2C_SDA, I2C_SCL);
Serial.begin(115200);
Serial.println("WAKE ME UP INSIDE");
delay(500);
sensorMpu9250WomEnable();
Serial.println("Got Here");
Serial.flush();
delay(1000);
esp_sleep_enable_ext0_wakeup(GPIO_NUM_27, 1);
esp_deep_sleep_start();
Hope that helps you too.
I'm trying to turn the LED (pin 2) ON when I push the boot button on an esp32 ! Here is my code ! Any idea why this don't work ?
// constants won't change. They're used here to set pin numbers:
const int buttonPin = 0; // the number of the pushbutton pin
const int ledPin = 2; // the number of the LED pin
// variables will change:
int buttonState = 0; // variable for reading the pushbutton status
void setup() {
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);
}
void loop() {
// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);
// check if the pushbutton is pressed. If it is, the buttonState is HIGH:
if (buttonState == HIGH) {
// turn LED on:
digitalWrite(ledPin, HIGH);
} else {
// turn LED off:
digitalWrite(ledPin, LOW);
}
}
Your design is fatally flawed in multiple ways, primarily, while the chip is booting the i/o pins are not under your code's control, as soon as that signal is detected your program stops running. Rebooting restores their default power-up states. Plus that button input is surely serviced by an interrupt, your polling loop will never see it go high, ever.
If you want an LED that stays on while the chip is booting you'll need another component, like a JFET. Connect an i/o pin to the JFET's gate, and its source and drain pins in series with either the power or ground leg of the LED, then set that i/o pin high while your program is running, to bias the JFET, thus keeping an open circuit between source and drain. As soon as the gate goes low, the circuit closes and your LED turns on. When the bootstrap executes your program, it sets that pin high again, LED goes off. And as a bonus you won't need to waste any compute polling an i/o. Win:win!
I have a program that lets an LED pulse. I also connected the PC8574 GPIO expander with a push button. I want to evaluate the keypress. However, I can only read the status of the INT (interrupt) while the program is in the part between making the LED brighter and making it darker again (between the two for loops)
I know that the problem is the delays withing the for loops but I have no idea how to avoid that.
Would it be possible to evaluate the interrupt related code more often or like a real interrupt - always when the actual key is pressed? And if so, how?
I use this library: https://github.com/WereCatf/PCF8574_ESP
/*LED_Breathing.ino Arduining.com 20 AUG 2015
Using NodeMCU Development Kit V1.0
Going beyond Blink sketch to see the blue LED breathing.
A PWM modulation is made in software because GPIO16 can't
be used with analogWrite().
*/
#include <pcf8574_esp.h>
#include <Wire.h>
TwoWire testWire;
// Initialize a PCF8574 at I2C-address 0x20, using GPIO5, GPIO4 and testWire for the I2C-bus
PCF857x pcf8574(0x20, &testWire);
#define LED D1 // Led in NodeMCU at pin GPIO16 (D0).
#define BRIGHT 300 //max led intensity (1-500)
#define INHALE 1250 //Inhalation time in milliseconds.
#define PULSE INHALE*1000/BRIGHT
#define REST 1000 //Rest Between Inhalations.
#define PIN_INT D5
#define PIN_SDA D7
#define PIN_SCL D8
//----- Setup function. ------------------------
void setup() {
Serial.begin(115200);
Wire.pins(PIN_SDA, PIN_SCL);//SDA - D1, SCL - D2
Wire.begin();
pinMode(PIN_INT, INPUT_PULLUP);
pcf8574.begin( 0xFF);
pcf8574.resetInterruptPin();
pinMode(LED, OUTPUT); // LED pin as output.
}
bool CheckKey(byte key, byte num){ //0, 1, 2, 3
return key & (1 << num);
}
//----- Loop routine. --------------------------
void loop() {
//ramp increasing intensity, Inhalation:
for (int i=1;i<BRIGHT;i++){
digitalWrite(LED, LOW); // turn the LED on.
delayMicroseconds(i*10); // wait
digitalWrite(LED, HIGH); // turn the LED off.
delayMicroseconds(PULSE-i*10); // wait
delay(0); //to prevent watchdog firing.
}
if( digitalRead(PIN_INT)==LOW ){
delay(50);
byte b = pcf8574.read8();
Serial.println( "INT: " + String(b));
byte keys = ((~b)) & 0x0F;
if( CheckKey(keys, 8) ){
Serial.println( "KEY 7");
delay(2000);
}
}
//ramp decreasing intensity, Exhalation (half time):
for (int i=BRIGHT-1;i>0;i--){
digitalWrite(LED, LOW); // turn the LED on.
delayMicroseconds(i*10); // wait
digitalWrite(LED, HIGH); // turn the LED off.
delayMicroseconds(PULSE-i*10); // wait
i--;
delay(0); //to prevent watchdog firing.
}
delay(REST); //take a rest...
}
You could use the PCF8574 INT pin as an interrupt to the ESP8266 via Arduino's attachInterrupt() function, but you wouldn't gain much from that, since in order to detect which key was pressed you need to call pcf8574.read8(), and you can't do that from the interrupt handler.
The ESP8266 Arduino core is based on the Espressif NONOS SDK, so you can't have a separate thread to monitor key presses. I would suggest defining a helper function that checks if a key is currently being pressed, and then calling that function as often as you can in your main loop, e.g. at every iteration of each of your two for loops. The LED brightness ramps would be slightly disrupted when there is a key press, but I think it wouldn't be noticeable to the human eye.
So the helper function could be defined as:
byte GetKeyPress(void) {
if (digitalRead(PIN_INT) == LOW) {
return ~pcf8574.read8();
}
else {
return 0;
}
}
Then, at the beginning of the loop() function declare a static variable static byte keyPress;, and call the above function in each of the two for loops:
if (keyPress == 0) {
keyPress = GetKeyPress();
}
When you want to process a key press (e.g. between the two for loops like in your code), you can do like that:
if (keyPress) {
/* Do your stuff. */
keyPress = 0;
}
I've been trying to measure the time that the line is high on the Arduino. It goes high, then stays high for a couple of milliseconds. Then it gets pulled low for 1us and then floats back to high. My code doesn't seem to recognise the line getting pulled low. Is 1us too fast for the interrupt? How can I slow it down?
Thank you
EDIT
My thoughts are to use an RC filter in conjunction with a diode to slow the rise time enough for the Arduino to recognise the change, but only when the change occurs from the receiving line. Is this viable? Or can I use a pulse extender chip with a diode in the same way?
#define ESC 2 //the digital pin the esc signal line is attached to
int throttlePos = 0;
volatile unsigned long timer_start;
volatile int last_interrupt_time;
volatile int pulse_time;
void setup() {
// put your setup code here, to run once:
pinMode(ESC, OUTPUT); //originally set the ESC's line to output to keep line high ready for throttle armature
digitalWrite(ESC, HIGH); //keep the pulse high due to inverted throttle pulse
Serial.begin(115200); //opens the serial port for use when testing
timer_start = 0;
attachInterrupt(digitalPinToInterrupt(ESC), calcSignal, CHANGE);
for(throttlePos = 0; throttlePos <= 1000; throttlePos += 1) //these for loops arm the ESC by emulating an inverted PWM pulse, process takes two seconds
{
digitalWrite(ESC, LOW);
delayMicroseconds(1500);
digitalWrite(ESC, HIGH);
delayMicroseconds(100);
}
for(throttlePos = 1000; throttlePos <= 2000; throttlePos += 1)
{
digitalWrite(ESC, LOW);
delayMicroseconds(1000);
digitalWrite(ESC, HIGH);
delayMicroseconds(100);
}
}
void loop() {
digitalWrite(ESC, LOW);
delayMicroseconds(1200);
digitalWrite(ESC, HIGH);
delayMicroseconds(100);
delay(19);
Serial.println(pulse_time);
}
void calcSignal()
{
//record the interrupt time so that we can tell if the receiver has a signal from the transmitter
last_interrupt_time = micros();
//if the pin has gone HIGH, record the microseconds since the Arduino started up
if(digitalRead(ESC) == HIGH)
{
timer_start = micros();
}
//otherwise, the pin has gone LOW
else
{
//only worry about this if the timer has actually started
if(timer_start != 0)
{
//record the pulse time
pulse_time = ((volatile int)micros() - timer_start);
//restart the timer
timer_start = 0;
}
}
}
1/1us is 1MHz. Given that your Arduino executes instructions at 16MHz (at best, some instructions take longer), your interrupt handler could easily be missing things. However, the interrupt should still execute, even if the interrupt condition is cleared before the interrupt finishes.
Are you using the Arduino libraries for interrupts or configuring pin change interrupts directly at the register level?
Have you verified that the pulse into the Arduino is electrically sound? Without knowing more about your circuit, it's difficult to suggest a fix.
I have an ATTiny85 connected to an NRF24L01+ module using this wiring diagram: diagram. The ATTiny85 periodically goes in and out of sleep to send some value to a receiver, an Arduino Uno. If the ATTiny is running off the Arduino power supply (3.3v), everything works correctly. When I run the ATTiny off of a separate CR2032 coin cell that delivers around 3v, the Arduino never receives any data. I have a status LED hooked up to the ATTiny to ensure that the ATTiny is waking correctly, which it is. Here's the code for both:
EDIT:
Connecting it to an external 3.3v not from the Uno makes everything work - why wouldn't the coin cell's voltage work? I think everything is rated below 2.8v, the CR2032 minimum.
ATTiny Code
#include <avr/sleep.h>
#include <avr/interrupt.h>
// Routines to set and claer bits (used in the sleep code)
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
#define CE_PIN 3
#define CSN_PIN 3 //Since we are using 3 pin configuration we will use same pin for both CE and CSN
#include "RF24.h"
RF24 radio(CE_PIN, CSN_PIN);
byte address[11] = "SimpleNode";
unsigned long payload = 0;
void setup() {
radio.begin(); // Start up the radio
radio.setAutoAck(1); // Ensure autoACK is enabled
radio.setRetries(15,15); // Max delay between retries & number of retries
radio.openWritingPipe(address); // Write to device address 'SimpleNode'
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);
delay(500);
digitalWrite(4, LOW);
delay(500);
digitalWrite(4, HIGH);
delay(500);
digitalWrite(4, LOW);
delay(500);
digitalWrite(4, HIGH);
delay(500);
digitalWrite(4, LOW);
delay(1000);
setup_watchdog(6);
}
volatile int watchdog_counter = 0;
ISR(WDT_vect) {
watchdog_counter++;
}
void loop()
{
sleep_mode(); //Go to sleep!
if(watchdog_counter >= 5)
{
digitalWrite(4, HIGH);
watchdog_counter = 0;
payload = 123456;
radio.write( &payload, sizeof(unsigned long) ); //Send data to 'Receiver' ever second
delay(1000);
digitalWrite(4, LOW);
}
}
//Sleep ATTiny85
void system_sleep() {
cbi(ADCSRA,ADEN); // switch Analog to Digitalconverter OFF
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
sleep_enable();
sleep_mode(); // System actually sleeps here
sleep_disable(); // System continues execution here when watchdog timed out
sbi(ADCSRA,ADEN); // switch Analog to Digitalconverter ON
}
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(int ii) {
byte bb;
int ww;
if (ii > 9 ) ii=9;
bb=ii & 7;
if (ii > 7) bb|= (1<<5);
bb|= (1<<WDCE);
ww=bb;
MCUSR &= ~(1<<WDRF);
// start timed sequence
WDTCR |= (1<<WDCE) | (1<<WDE);
// set new watchdog timeout value
WDTCR = bb;
WDTCR |= _BV(WDIE);
}
Receiver Code
#define CE_PIN 7
#define CSN_PIN 8
#include <SPI.h>
#include "RF24.h"
RF24 radio(CE_PIN, CSN_PIN);
byte address[11] = "SimpleNode";
unsigned long payload = 0;
void setup() {
while (!Serial);
Serial.begin(115200);
radio.begin(); // Start up the radio
radio.setAutoAck(1); // Ensure autoACK is enabled
radio.setRetries(15,15); // Max delay between retries & number of retries
radio.openReadingPipe(1, address); // Write to device address 'SimpleNode'
radio.startListening();
Serial.println("Did Setup");
}
void loop(void){
if (radio.available()) {
radio.read( &payload, sizeof(unsigned long) );
if(payload != 0){
Serial.print("Got Payload ");
Serial.println(payload);
}
}
}
Is the problem here that the ATTiny and Uno need to be turned on at the same time to establish a connection, or is it something to do with the battery, or something else entirely? Any help would be appreciated.
I'm experiencing the same problem when running Arduino Nano from a battery.
Nano has a 3.3V pin that I'm using for powering the NRF24L01+ module.
When the voltage from my battery-pack drops under 3.3V, the 3.3V pin voltage also drops. After few minutes, the RF module is not sending any messages.
I fixed the problem temporarily by routing the battery through a 12V step-up regulator that I bought earlier for a different project. These 12V then go to the "UN" pin on Nano which accepts 6-20V. This setup works nicely but is definitely not optimal.
Therefore I'm planning to use a 3.3V step-up regulator such as Pololu 3.3V Step-Up Voltage Regulator U1V11F3 which, according to the supplier, can efficiently generate 3.3V from input voltages as low as 0.5V.
I think this might be helpful to your project as well.