Hello, I have a program in which I'm not doing anything yet, but I have a wdt running and waking up my IC every 8 sec. I also want to be able to wake it up with an external interrupt, but I'm can't make it work. Right now, my code works with a pin change interrupt, but any logic change activates the interrupt. That means I create 2 interrupts when I press the button and release it.
I /masked/ 3 lines of code in my Sleep() function that are supposed to make external interrupts on INT0 (pin 2) work, but I'm puzzled! I have been reading on every register in the datasheet but I can't make it! thanks a lot
/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/
#include <avr/sleep.h>
#include <avr/interrupt.h>
/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/
#define switchPin 2
#define ledPin 1
#define voltagePin 4
/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/
//WDT variables
volatile int watchDog_counter;
//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;
// values are set depending on resistor values. Common resistor is 10k
#define minNb 190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb 205
#define minSize 210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize 220
#define minReset 175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset 189
//Food Data variables
//amount of meals per day
byte meal_nb;
#define max_meal_nb 3
//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5
/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/
/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/
void setup() {
watchDog_counter = 0;
//pins setup
DDRB &= ~(1 << switchPin);
PORTB |= (1 << switchPin);
DDRB |= (1 << ledPin);
//Watchdog timer setup
//This order of commands is important and cannot be combined
MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
}
/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/
void loop() {
sleep();
PORTB |= (1 << ledPin);
//digitalWrite(ledPin, HIGH);
delay(100);
PORTB ^= (1 << ledPin);
//digitalWrite(ledPin, LOW);
if (buttonFlag) {
buttonFlag = 0;
}
}
/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/
/*Sleep function, activates external interrupts*/
void sleep() {
/*MCUCR |= 1<<ISC01; // Falling EDGE
GIMSK |= 1<<INT0; // Activate the INT0
sei(); //Active General Interrupt*/
GIMSK |= _BV(PCIE);
PCMSK |= _BV(PCINT2); // Use PB2 as interrupt pin (PCINT3 would be 3, etc.)
ADCSRA &= ~_BV(ADEN); // ADC off
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // replaces above statement
sleep_enable(); // Sets the Sleep Enable bit in the MCUCR Register (SE BIT)
sei(); // Enable interrupts
sleep_cpu(); // sleep
cli(); // Disable interrupts
PCMSK &= ~_BV(PCINT2); // Turn off PB2 as interrupt pin
sleep_disable(); // Clear SE bit
ADCSRA |= _BV(ADEN); // ADC on
sei(); // Enable interrupts
} // sleep
ISR(PCINT0_vect) {
unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
if (interrupt_time - last_interrupt_time > 200) //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
{
voltage = analogRead(voltagePin);
buttonFlag = 1; //tell the arduino a button was pressed
/**************************************************************************************************************************************************************/
//NB OF MEALS BUTTON IS PRESSED
if (minNb <= voltage && voltage <= maxNb) {
if (meal_nb < max_meal_nb) meal_nb++;
else meal_nb = 1;
}
/**************************************************************************************************************************************************************/
//SIZE OF MEALS BUTTON IS PRESSED
if (minSize <= voltage && voltage <= maxSize) {
if (meal_size < max_meal_size) meal_size++;
else meal_size = 1;
}
/**************************************************************************************************************************************************************/
//RESET BUTTON IS PRESSED
if (minReset <= voltage && voltage <= maxReset) {
}
//SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
last_interrupt_time = interrupt_time;
}
}
ISR(WDT_vect) {
watchDog_counter++;
}
OK I got the external interrupt working! So nevermind the rest of this post. I think it was late and my code had a couple mistakes. I decided to modify all my registers in the SETUP and not in a separate function; it seems to do the trick. I am sharing my new functioning code here.
I still have a problem I dont understand however: I can only trigger my INT0 with low lvl interrupts??? If I setup a rising or falling edge, the interrupt doesnt trigger at all (I comment that problem in the code as you see). Anyone knows what could be causing this? I tried changing isc01 and isc00 pins at the same time, but it doesnt change the results.
/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/
#include <avr/sleep.h>
#include <avr/interrupt.h>
/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/
#define switchPin 2
#define ledPin 1
#define voltagePin 4
/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/
//WDT variables
volatile int watchDog_counter;
//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;
// values are set depending on resistor values. Common resistor is 10k
#define minNb 190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb 205
#define minSize 210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize 220
#define minReset 175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset 189
//Food Data variables
//amount of meals per day
byte meal_nb;
#define max_meal_nb 3
//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5
/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/
/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/
void setup() {
watchDog_counter = 0;
//pins setup
DDRB &= ~(1 << switchPin);
PORTB |= (1 << switchPin);
DDRB |= (1 << ledPin);
//Watchdog timer setup
//This order of commands is important and cannot be combined
MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
//External interrupt setup
//Enable interrupts
GIMSK |= (1 << INT0);
sei();
//set low lvl trigger (keeps triggering while button not released)
MCUCR &= ~(1 << ISC01);
//TRYING TO PUT THIS INSTEAD!! (FOR FALLING EDGE): MCUCR |= (1 << ISC01);
MCUCR &= ~(1 << ISC00);
ADCSRA &= ~_BV(ADEN); //Turn ADC off, saves ~230uA
sleep_enable();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/
void loop() {
sleep_cpu();
//ADCSRA |= _BV(ADEN); //will ADC need to be turned on after sleep?
PORTB |= (1 << ledPin);
delay(100);
PORTB ^= (1 << ledPin);
delay(500);
if (buttonFlag) {
buttonFlag = 0;
}
}
/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/
ISR(INT0_vect) { //NOT PCINT0_vect!
unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
if (interrupt_time - last_interrupt_time > 200) //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
{
voltage = analogRead(voltagePin);
buttonFlag = 1; //tell the arduino a button was pressed
/**************************************************************************************************************************************************************/
//NB OF MEALS BUTTON IS PRESSED
if (minNb <= voltage && voltage <= maxNb) {
if (meal_nb < max_meal_nb) meal_nb++;
else meal_nb = 1;
}
/**************************************************************************************************************************************************************/
//SIZE OF MEALS BUTTON IS PRESSED
if (minSize <= voltage && voltage <= maxSize) {
if (meal_size < max_meal_size) meal_size++;
else meal_size = 1;
}
/**************************************************************************************************************************************************************/
//RESET BUTTON IS PRESSED
if (minReset <= voltage && voltage <= maxReset) {
}
//SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
last_interrupt_time = interrupt_time;
}
}
ISR(WDT_vect) {
watchDog_counter++;
}
JCSB:
I still have a problem I dont understand however: I can only trigger my INT0 with low lvl interrupts???
9.2
See “Code Examples” on page 6.
External Interrupts
The INT0 interrupts can be triggered by a falling or rising edge or a low level. This is set up as indicated in the
specification for the MCU Control Register – MCUCR. When the INT0 interrupt is enabled and is configured as
level triggered, the interrupt will trigger as long as the pin is held low. Note that recognition of falling or rising edge interrupts on INT0 requires the presence of an I/O clock, described in “Clock Systems and their Distribution” on page 23.
7.1.3 Power-down Mode
When the SM[1:0] bits are written to 10, the SLEEP instruction makes the MCU enter Power-down mode. In this
mode, the Oscillator is stopped, while the external interrupts, the USI start condition detection and the Watchdog
continue operating (if enabled). Only an External Reset, a Watchdog Reset, a Brown-out Reset, USI start condition
interupt, an external level interrupt on INT0 or a pin change interrupt can wake up the MCU. This sleep mode halts
all generated clocks, allowing operation of asynchronous modules only.
When the SM[1:0] bits are written to 10, the SLEEP instruction makes the MCU enter Power-down mode. In this
mode, the Oscillator is stopped, while the external interrupts, the USI start condition detection and the Watchdog
continue operating (if enabled). Only an External Reset, a Watchdog Reset, a Brown-out Reset, USI start condition
interupt, an external level interrupt on INT0 or a pin change interrupt can wake up the MCU. This sleep mode halts
all generated clocks, allowing operation of asynchronous modules only.
Thanks for the info! So if I want to keep power_down, I will have to find a way around multiple interrupts per button press.. I was thinking maybe going with a pin change interrupt instead, and then setting a flag so that always 1/2 of interrupts (so 1 per button press) are actually triggering what the button is supposed to do. Does that sound like something you would do?
Note that recognition of falling or rising edge interrupts on INT0 requires the presence of an I/O clock, described in "Clock Systems and their Distribution" on page 23.
That has been determined to be incorrect and confirmed by Atmel. I believe Jack Christensen (@JChristensen) discovered the discrepancy and was able to get confirmation from Atmel.
What I cannot remember is if the discrepancy applies to the t85 processor. Knowing Jack's interests I suspect it does.
Got it, thanks for the clarification. Will be useful for my next atmega328 project. For this tiny85 project, however, I need to find an efficient way for the user to trigger the button's effect only once per pressing. How would you guys do it? I had this idea:
So if I want to keep power_down, I will have to find a way around multiple interrupts per button press.. I was thinking maybe going with a pin change interrupt instead, and then setting a flag so that always 1/2 of interrupts (so 1 per button press) are actually triggering what the button is supposed to do. Does that sound like something you would do?
What about setting the flag 'buttonFlag' in your ISR only if, after reading the port, that you determine the button has actually been pressed and not simply released. Anyway, move most of that logic (apart from the flag setting) out of the ISR and put it in the loop() where you can also reject unwanted button presses based on time or whatever criterion.
Or simply extend the timer that you are using to debounce the button to cover the whole period between pressing and a releasing the button.
last_interrupt_time should incidentally be declared a volatile.
6v6gt:
What about setting the flag 'buttonFlag' in your ISR only...
Why do anything in the pin change interrupt service routine?
If the processor is awake there are three possibilities: 8 seconds elapsed, button down, button up. Just check for the second two in loop when control returns from sleeping.
Or simply extend the timer that you are using to debounce the button to cover the whole period between pressing and a releasing the button.
I thought about this but I want the user to be able to press the button multiple times in a row, quite fast. So your 1st idea seems very good:
What about setting the flag 'buttonFlag' in your ISR only if, after reading the port, that you determine the button has actually been pressed and not simply released.
Here's what I've done. Looks pretty cool and works so far (I switched to PCINT, moved everything from ISR and made sure I read PINB2 to check its low before triggering buttons effects, so nothing happens when I release, even though my "fake" button action happens when I first press (quickly flashes 5 times)
/**************************************************************************************************************************************************************/
////LIBRARIES
/**************************************************************************************************************************************************************/
#include <avr/sleep.h>
#include <avr/interrupt.h>
/**************************************************************************************************************************************************************/
////PINS
/**************************************************************************************************************************************************************/
#define switchPin 2
#define ledPin 1
#define voltagePin 4
/**************************************************************************************************************************************************************/
////VARIABLES
/**************************************************************************************************************************************************************/
//WDT variables
volatile int watchDog_counter;
//External interrupt variables
volatile int buttonFlag;
unsigned long last_interrupt_time;
int voltage;
// values are set depending on resistor values. Common resistor is 10k
#define minNb 190 //175 to 185 based on a 4.7k resistor value for UP arrow button
#define maxNb 205
#define minSize 210 //189 to 220 based on a 1k resistor value for DOWN arrow button
#define maxSize 220
#define minReset 175 //120 to 166 based on a 10k resistor value for SELECT button
#define maxReset 189
//Food Data variables
//amount of meals per day
byte meal_nb;
#define max_meal_nb 3
//amount of cylinder spins (meal size)
byte meal_size;
#define max_meal_size 5
/**************************************************************************************************************************************************************/
////OBJECTS
/**************************************************************************************************************************************************************/
/**************************************************************************************************************************************************************/
////SETUP
/**************************************************************************************************************************************************************/
void setup() {
watchDog_counter = 0;
//pins setup
DDRB &= ~(1 << switchPin);
PORTB |= (1 << switchPin);
DDRB |= (1 << ledPin);
//Watchdog timer setup
//This order of commands is important and cannot be combined
MCUSR &= ~(1 << WDRF); //Clear the watch dog reset
WDTCR |= (1 << WDCE) | (1 << WDE); //Set WD_change enable, set WD enable
WDTCR = 0B100001; //Set prescaler to 8 sec (see p.46 of datasheet to change prescaler), and OVERWRITE WDTCR value ( dont just use |= )
WDTCR |= _BV(WDIE); //Set the interrupt enable, this will keep unit from resetting after each int
//Pin Change interrupt setup
//Enable interrupts
GIMSK |= (1 << PCIE);
PCMSK |= _BV(PCINT2);
sei();
//EXT INTERRUPT STUFF
//IMSK |= (1 << PCIE);
//set low lvl trigger (keeps triggering while button not released)
//MCUCR &= ~(1 << ISC01);
//TRYING TO PUT THIS INSTEAD!! (FOR FALLING EDGE): MCUCR |= (1 << ISC01);
//MCUCR |= (1 << ISC00);
ADCSRA &= ~_BV(ADEN); //Turn ADC off, saves ~230uA
sleep_enable();
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}
/**************************************************************************************************************************************************************/
////LOOP
/**************************************************************************************************************************************************************/
void loop() {
sleep_cpu();
//ADCSRA |= _BV(ADEN); //will ADC need to be turned on after sleep?
PORTB |= (1 << ledPin);
delay(100);
PORTB ^= (1 << ledPin);
// delay(500);
if (buttonFlag) {
buttonsAction();
buttonFlag = 0;
}
}
/**************************************************************************************************************************************************************/
////FUNCTIONS
/**************************************************************************************************************************************************************/
void buttonsAction() {
unsigned long interrupt_time = millis(); //saves time at which this interrupt starts
if (interrupt_time - last_interrupt_time > 200 && !(PINB & (1 << PB2))) //compares last time interrupt was finished to now. We give 200 millis to ignore rebounds
{
voltage = analogRead(voltagePin);
for (int i = 0; i < 10; i++) {
PORTB ^= (1 << ledPin);
delay(100);
}
/**************************************************************************************************************************************************************/
//NB OF MEALS BUTTON IS PRESSED
if (minNb <= voltage && voltage <= maxNb) {
if (meal_nb < max_meal_nb) meal_nb++;
else meal_nb = 1;
}
/**************************************************************************************************************************************************************/
//SIZE OF MEALS BUTTON IS PRESSED
if (minSize <= voltage && voltage <= maxSize) {
if (meal_size < max_meal_size) meal_size++;
else meal_size = 1;
}
/**************************************************************************************************************************************************************/
//RESET BUTTON IS PRESSED
if (minReset <= voltage && voltage <= maxReset) {
}
//SAVE WHEN THE INTERRUPT OCCURED TO PREVENT BOUNCES FROM TRIGGERING MULTIPLE INTERRUPTS
last_interrupt_time = interrupt_time;
}
}
ISR(PCINT0_vect) { //NOT PCINT0_vect!
buttonFlag = 1; //tell the arduino a button was pressed
}
ISR(WDT_vect) {
watchDog_counter++;
}
Really?? Can you explain please? It does work for now. Do you think it is bound to fail on the long run? And how can I keep interrupts down while sleeping and the wdt or the interrupt are supposed to wake it up? Thanks!
if (buttonFlag) {
buttonsAction();
buttonFlag = 0;
}
...has a race condition. The correct sequence is...
• Disable interrupts
• Save the value of buttonFlag to a local variable
• Clear buttonFlag
• Restore interrupts
• Use the local variable for the if-statement
Hi, sorry for the late responce. Why is this a race condition? And what does that imply? Does it mean it will work most but all the time?? And this means all my modified registers lines must be placed in loop instead of setup, because I will constantly turn them on and off? Thanks!
I can't think of a way to make that race condition produce bad behavior here...
The concern is that you test buttonFlag... but between then and when you set it back to zero, the interrupt could fire and change that variable. I don't see how that can break the simple case of a button, though.
But it's something you need to be keenly aware of when you are working with interrupts. Always think about "Would my code break if an interrupt fired between two points at which I interact with this variable and changed it" - if the answer is yes, you need to disable interrupts while you copy it to a local variable.
Note that at least in AVR-land, interrupts are disabled during an ISR unless you explicitly enable them.