Pin Change Interrupts (attiny88- MH-TINY) IM STUCK

Im working on a wireless keyboard that currently works just fine.

Transmitter is an Attiny88 MHTINY Version connected to an rf24 module.
Receiver is a Arduino Micro with RF24.

Its all gravy except for the part where I try to incorporate low power(sleep) into the transmitter
(its running off a 2032 Cell Battery and currently dies within a few hrs.
I have already made the Attiny88 run on 1mhz / making all the pins that arnt being used in InputPULLUP to save power and im assuming going to (SLEEP) is the only way
ill be able to have it run for at least a week.

Here is the working code for the transmitter (without Low Power)

#include "SPI.h"
#include "RF24.h"
#include <Button.h>


// Radio, choose any pins on your micro-controller to use as the CE and CSN pins.
#define CE_PIN 9
#define CSN_PIN 10

const uint8_t ADDRESS[6] = "5n1p3";


// Maximum number of keys a shortcut can be, or max number of keys to send through the NRF24L01 per button.
// This needs to match with the receiver side.
const int MAX_SHORTCUT_KEYS = 4;


const int BTN_SHORTCUT_SIZE = (3 * MAX_SHORTCUT_KEYS) + (MAX_SHORTCUT_KEYS - 1) + 1;

struct ButtonInfo {
    int btnPin;
    char btnShortcut[BTN_SHORTCUT_SIZE];
};


// All keys are represented using their ASCII decimal/int value. This can be found here: www.asciitable.com
// First specify the pin number, then seperate each key for that button in a string by a space.
const ButtonInfo BUTTONS_INFO[] = {{A0, "130 226"},
                                   {A1, "130 225"},
                                   {A2, "130 234"},
                                   {A3, "130 227"},
                                   
                                   
                                   };

                              

const int N_BUTTONS = sizeof(BUTTONS_INFO) / sizeof(BUTTONS_INFO[0]);

const uint16_t DEBOUNCE_MS = 10;


RF24 radio(CE_PIN, CSN_PIN);


Button *buttonObjs[N_BUTTONS];



void initRadio() {
    
    if (!radio.begin()) {
        while (1) {}
    }

    radio.setPALevel(RF24_PA_LOW);
    radio.setPayloadSize(BTN_SHORTCUT_SIZE);
    radio.openWritingPipe(ADDRESS);
    radio.setDataRate(RF24_1MBPS);
    
    radio.stopListening();
}

void initButtons() {
    for(int i = 0; i < N_BUTTONS; i++) {
        ButtonInfo btnInfo = BUTTONS_INFO[i];
        buttonObjs[i] = new Button(btnInfo.btnPin, DEBOUNCE_MS);
        buttonObjs[i] -> begin();
    }
}


void setup() {

    initRadio();
    initButtons();
    pinMode(A4, INPUT_PULLUP);
    pinMode(A5, INPUT_PULLUP);
    pinMode(A6, INPUT_PULLUP);
    pinMode(A7, INPUT_PULLUP);
    pinMode(0, OUTPUT);
    pinMode(1, INPUT_PULLUP);
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);
    pinMode(15, INPUT_PULLUP);
    pinMode(16, INPUT_PULLUP);
    
}

void loop() {

    // If any button pressed, then send the button keys through radio using the nRF24L01.
    for(int i = 0; i < N_BUTTONS; i++) {
        if(buttonObjs[i] -> pressed()) {
            ButtonInfo btnInfo = BUTTONS_INFO[i];
            
            radio.write(&btnInfo.btnShortcut, sizeof(btnInfo.btnShortcut));
            
            break;
        }
    }
}

My problem is im trying to make it LOW POWER so that it can stay asleep and wake up and send the right code when I click one of the keyboard buttons. I pulled up the data sheet and looked up some examples from Nick Gammon and honestly Ive spent the whole last week learning as much as I can on Pin Change Interrupts because I cant use INT0 or INT1 as I am hoping to use the same buttons that I have configured already and they are PCINT Which from reading the datasheet I should be able to wake up from sleep. here is the Code with what I thought would be working code but it doesnt wake from sleep? Im not looking for a handout im just looking for some guidance because my brain is mushy from all the research. Here is the "non working Low power code"

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include "SPI.h"
#include "RF24.h"
#include <Button.h>


// Radio, choose any pins on your micro-controller to use as the CE and CSN pins.
#define CE_PIN 9
#define CSN_PIN 10

const uint8_t ADDRESS[6] = "5n1p3";


// Maximum number of keys a shortcut can be, or max number of keys to send through the NRF24L01 per button.
// This needs to match with the receiver side.
const int MAX_SHORTCUT_KEYS = 4;


const int BTN_SHORTCUT_SIZE = (3 * MAX_SHORTCUT_KEYS) + (MAX_SHORTCUT_KEYS - 1) + 1;

struct ButtonInfo {
    int btnPin;
    char btnShortcut[BTN_SHORTCUT_SIZE];
};


// All keys are represented using their ASCII decimal/int value. This can be found here: www.asciitable.com
// First specify the pin number, then seperate each key for that button in a string by a space.
const ButtonInfo BUTTONS_INFO[] = {{A0, "130 226"},
                                   {A1, "130 225"},
                                   {A2, "130 234"},
                                   {A3, "130 227"},
                                   
                                   
                                   };

                              

const int N_BUTTONS = sizeof(BUTTONS_INFO) / sizeof(BUTTONS_INFO[0]);

const uint16_t DEBOUNCE_MS = 10;


RF24 radio(CE_PIN, CSN_PIN);


Button *buttonObjs[N_BUTTONS];



void initRadio() {
    
    if (!radio.begin()) {
        while (1) {}
    }

    radio.setPALevel(RF24_PA_LOW);
    radio.setPayloadSize(BTN_SHORTCUT_SIZE);
    radio.openWritingPipe(ADDRESS);
    radio.setDataRate(RF24_1MBPS);
    
    radio.stopListening();
}

void initButtons() {
    for(int i = 0; i < N_BUTTONS; i++) {
        ButtonInfo btnInfo = BUTTONS_INFO[i];
        buttonObjs[i] = new Button(btnInfo.btnPin, DEBOUNCE_MS);
        buttonObjs[i] -> begin();
    }
}


void setup() {

    initRadio();
    initButtons();
    pinMode(A0, INPUT);
    digitalWrite(A0, HIGH);
    pinMode(A1, INPUT);
    digitalWrite(A1, HIGH);
    pinMode(A2, INPUT);
    digitalWrite(A2, HIGH);
    pinMode(A3, INPUT);
    digitalWrite(A3, HIGH);
    pinMode(A4, INPUT_PULLUP);
    pinMode(A5, INPUT_PULLUP);
    pinMode(A6, INPUT_PULLUP);
    pinMode(A7, INPUT_PULLUP);
    pinMode(0, OUTPUT);
    pinMode(1, INPUT_PULLUP);
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);
    pinMode(15, INPUT_PULLUP);
    pinMode(16, INPUT_PULLUP);
    
}

void sleep() {

    PCICR |= _BV(PCIE0);
    PCICR |= _BV(PCIE1);
    PCICR |= _BV(PCIE2);
    PCICR |= _BV(PCIE3);// Enable Pin Change Interrupts
    PCMSK1 |= _BV(PCINT8);                   // Use PC0 as interrupt pin
    PCMSK1 |= _BV(PCINT9);                   // Use PC1 as interrupt pin
    PCMSK1 |= _BV(PCINT10);                   // Use PC2 as interrupt pin
    PCMSK1 |= _BV(PCINT11);                   // Use PC3 as interrupt pin
    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
    PCMSK1 &= ~_BV(PCINT8);                  // Turn off PC0 as interrupt pin
    PCMSK1 &= ~_BV(PCINT9);                  // Turn off PC1 as interrupt pin
    PCMSK1 &= ~_BV(PCINT10);                  // Turn off PC2 as interrupt pin
    PCMSK1 &= ~_BV(PCINT11);                  // Turn off PC3 as interrupt pin
    sleep_disable();                        // Clear SE bit
    ADCSRA |= _BV(ADEN);                    // ADC on

    sei();                                  // Enable interrupts
    } // sleep

ISR(PCINT0_vect) {
    // This is called when the interrupt occurs, but I don't need to do anything in it
    }

void loop() {

    sleep();
    // If any button pressed, then send the button keys through radio using the nRF24L01.
    for(int i = 0; i < N_BUTTONS; i++) {
        if(buttonObjs[i] -> pressed()) {
            ButtonInfo btnInfo = BUTTONS_INFO[i];
            
            radio.write(&btnInfo.btnShortcut, sizeof(btnInfo.btnShortcut));
            
            break;
        }
    }
}

Thank you for taking the time to read this post and possibly help out.

You need to explain what "non-working" means. Does it go to sleep? Does it wake up when you press a key? What doesn't work?

I built an IR remote control for 328P Arduinos that seems to be similar to what you are doing, but it used keypad.h instead of button.h. I set the column pins low and the row pins input_pullup, and turned on the interrupt mask for the row pins. Then in the ISR I clear the mask enables and clear any flags. Then back in the loop I set the column pins back to input_pullup. Well, you may want to look at it and see if it's relevant. All the rows and the columns are in the same port, which simplifies things. Anyway, the code is here:

https://github.com/gbhug5a/Roku-Sling-IR-Channel-Number-Remote-for-Arduino

Most of the IR and Sling stuff will be irrelevant to you, but the sleep/wake, interrupts, and keypad input might be useful. But keep in mind they are for a different processor. I don't know about the ATTiny88.

By it doesnt work I mean it doesnt send data at all. Im assuming my code is all wrong and anything could be off. I will take a look at your project and see what I can figure out. Thank you!

Well I would suggest breaking it down into smaller pieces,, and get them working one at a time. Get it to go to sleep. Then get it to wake up with the pins in the right state to be read. You already know it will do the rest.

Ok I will have a go at that. I just dont know how i will integrate once i have the sleep working.

Do you have a schematic you could post? Hand drawn is fine.

How many buttons are there? Are they wired in a grid of rows and columns?


This is going to be the final schematic with 8 buttons but right now on my breadboard version I only have 4 buttons. The spot with the 3 through hole spots is where a cap and a mcp1700 are to make sure the rf24 gets 5v

Ok, so there's no matrix. It's just pushing on a button grounds the I/O pin. Well that's simpler than my project. I suspect it's just going to be getting the registers set correctly. I'll take a look at the 88 datasheet.

Ok thank you so much =]

I don't see anything wrong. You might try moving this code into the ISR:

    PCMSK1 &= ~_BV(PCINT8);                  // Turn off PC0 as interrupt pin
    PCMSK1 &= ~_BV(PCINT9);                  // Turn off PC1 as interrupt pin
    PCMSK1 &= ~_BV(PCINT10);                  // Turn off PC2 as interrupt pin
    PCMSK1 &= ~_BV(PCINT11);                  // Turn off PC3 as interrupt pin
    sleep_disable();                        // Clear SE bit

but that really shouldn't be necessary for pin change interrupts. It seems it ought to work. You're going to have to figure out where it's going wrong. Is it going to sleep? Is it waking up? Sorry I don't have a fix for you.

Oh.

I think this is wrong:

ISR(PCINT0_vect)

Shouldn't that be

ISR(PCINT1_vect)

OK PROGRESS! It works after switching the ISR to PCINT1_vect because now its sending commands! However I can only send every command once and then it stops working until I reset it. So this is good I think we are making progress! I also tried moving the Code into the ISR and same outcome. So its allowing me to press every single button once and send the command and then im assuming that it stays in sleep. hmmm. im excited that were getting closer

UPDATE:

void loop() {

    
    // If any button pressed, then send the button keys through radio using the nRF24L01.
    for(int i = 0; i < N_BUTTONS; i++) {
        if(buttonObjs[i] -> pressed()) {
            ButtonInfo btnInfo = BUTTONS_INFO[i];
            
            radio.write(&btnInfo.btnShortcut, sizeof(btnInfo.btnShortcut));
            
            break;
            sleep();
        }
    }
}

I switched the sleep to the end of the loop and its working!
IDK if its going to sleep though! I dont know how to test how many mah its using with a multimeter. Right now im just leaving it on and testing in a few hours to see if it still works

After a button is pressed, and you send the radio message, are you waiting until the button has been released before re-enabling the interrupts? To deal with any switch bouncing that may occur, I would wait until the button (or all buttons) have been high continuously for 250ms before assuming they have actually been released.

To measure current, you would have to find a way to insert your meter (in current mode) into the 5V power line. Or you could insert a very low value resistor, like 1 ohm, and measure the voltage across it. But if you still have your radio module running, you may still get significant current when the processor is sleeping.

ahh thats a great idea. Would that go in the Void loop section?
if else statement with counter for 250ms maybe?

im not using 5v power im just using a 3.7 coin cell I heard somewhere that it helps with power consumption to go lower. What do you mean by inserting it into the Power line?

also how did you come up with the pcint1_vect vs pcint0_vect

The only four Pin Change Interrupts that are enabled are on PORTC/PCIE1/PCMSK1.

Note: There is no need to enable PCIE0, PCIE2, or PCIE3.

Oh gotcha I had them enabled because eventually I will have 8 or more buttons once I have the code working.

Ok so I moved sleep back above on the top of the loop because my battery was dying very fast. here is my updated code with a few things changed like whats inside the ISR and also added sleep_disable(); at the end of it.

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include "SPI.h"
#include "RF24.h"
#include <Button.h>



// Radio, choose any pins on your micro-controller to use as the CE and CSN pins.
#define CE_PIN 9
#define CSN_PIN 10

const uint8_t ADDRESS[6] = "5n1p3";


// Maximum number of keys a shortcut can be, or max number of keys to send through the NRF24L01 per button.
// This needs to match with the receiver side.
const int MAX_SHORTCUT_KEYS = 4;


const int BTN_SHORTCUT_SIZE = (3 * MAX_SHORTCUT_KEYS) + (MAX_SHORTCUT_KEYS - 1) + 1;

struct ButtonInfo {
    int btnPin;
    char btnShortcut[BTN_SHORTCUT_SIZE];
};


// All keys are represented using their ASCII decimal/int value. This can be found here: www.asciitable.com
// First specify the pin number, then seperate each key for that button in a string by a space.
const ButtonInfo BUTTONS_INFO[] = {{A0, "130 226"},
                                   {A1, "130 225"},
                                   {A2, "130 234"},
                                   {A3, "130 227"},
                                   
                                   
                                   };

                              

const int N_BUTTONS = sizeof(BUTTONS_INFO) / sizeof(BUTTONS_INFO[0]);

const uint16_t DEBOUNCE_MS = 10;


RF24 radio(CE_PIN, CSN_PIN);


Button *buttonObjs[N_BUTTONS];



void initRadio() {
    
    if (!radio.begin()) {
        while (1) {}
    }

    radio.setPALevel(RF24_PA_LOW);
    radio.setPayloadSize(BTN_SHORTCUT_SIZE);
    radio.openWritingPipe(ADDRESS);
    radio.setDataRate(RF24_1MBPS);
    
    radio.stopListening();
}

void initButtons() {
    for(int i = 0; i < N_BUTTONS; i++) {
        ButtonInfo btnInfo = BUTTONS_INFO[i];
        buttonObjs[i] = new Button(btnInfo.btnPin, DEBOUNCE_MS);
        buttonObjs[i] -> begin();
    }
}


void setup() {

    initRadio();
    initButtons();
    pinMode(A0, INPUT);
    digitalWrite(A0, HIGH);
    pinMode(A1, INPUT);
    digitalWrite(A1, HIGH);
    pinMode(A2, INPUT);
    digitalWrite(A2, HIGH);
    pinMode(A3, INPUT);
    digitalWrite(A3, HIGH);
    pinMode(A4, INPUT_PULLUP);
    pinMode(A5, INPUT_PULLUP);
    pinMode(A6, INPUT_PULLUP);
    pinMode(A7, INPUT_PULLUP);
    pinMode(0, OUTPUT);
    pinMode(1, INPUT_PULLUP);
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);
    pinMode(4, INPUT_PULLUP);
    pinMode(5, INPUT_PULLUP);
    pinMode(6, INPUT_PULLUP);
    pinMode(7, INPUT_PULLUP);
    pinMode(8, INPUT_PULLUP);
    pinMode(14, INPUT_PULLUP);
    pinMode(15, INPUT_PULLUP);
    pinMode(16, INPUT_PULLUP);
    
}

void sleep() {

    PCICR |= _BV(PCIE1);                      // Enable Pin Change Interrupts
    PCMSK1 |= _BV(PCINT8);                   // Use PC0 as interrupt pin
    PCMSK1 |= _BV(PCINT9);                   // Use PC1 as interrupt pin
    PCMSK1 |= _BV(PCINT10);                   // Use PC2 as interrupt pin
    PCMSK1 |= _BV(PCINT11);                   // Use PC3 as interrupt pin
    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

    
    } // sleep

ISR(PCINT1_vect) {
    // This is called when the interrupt occurs, but I don't need to do anything in it
    cli();                                  // Disable interrupts
    PCMSK1 &= ~_BV(PCINT8);                  // Turn off PC0 as interrupt pin
    PCMSK1 &= ~_BV(PCINT9);                  // Turn off PC1 as interrupt pin
    PCMSK1 &= ~_BV(PCINT10);                  // Turn off PC2 as interrupt pin
    PCMSK1 &= ~_BV(PCINT11);                  // Turn off PC3 as interrupt pin
    sleep_disable();                        // Clear SE bit
    ADCSRA |= _BV(ADEN);                    // ADC on
    sleep_disable();
    }

void loop() {

    sleep();
    // If any button pressed, then send the button keys through radio using the nRF24L01.
    for(int i = 0; i < N_BUTTONS; i++) {
        if(buttonObjs[i] -> pressed()) {
            ButtonInfo btnInfo = BUTTONS_INFO[i];
            
            radio.write(&btnInfo.btnShortcut, sizeof(btnInfo.btnShortcut));
            
            break;
            
        }
    }
}

Im super confused its sending data but only 1 button at a time and I have to go in order for it to work. so If I press button 1 (A0) it sends a command if i press it again nothing happens until i press button 2 (A1) and also there is a lag sometimes and the buttons dont work. im assuming its going to sleep then waking up. im just confused.

I'm not a good enough programmer to figure out what's going on. I don't know how Button.h works. And I don't have any idea what's happening in this statement:

if(buttonObjs[i] -> pressed()) {

And I don't know what a break inside an IF statement inside a FOR loop actually does.

After the For loop, there needs to be something to test whether the key is still pressed, and wait until it is released. Maybe Button.h does that. There is a variable defining debounce_ms at 10. Maybe that is used somehow. Without that test, the next pin change may be when the button is released. But it will not be detected as pressed, because it isn't. I don't know what happens then.

A few things that probably don't have anything to do with your issues:

You don't need cli inside the ISR. Interrupts are automatically disabled inside an ISR, and re-enabled on exit.

You have sleep_disable() twice.

This stuff:

pinMode(A0, INPUT);
digitalWrite(A0, HIGH);

can just be replaced by pinMode(A0, INPUT_PULLUP);

Maybe somebody who knows structs and such can help.

You need to read Section 7 of the ATTiny88 datasheet. I think you have more to do to reduce sleep power consumption. But in the best case, you still have the radio running.

Hi @Wesley5n1p35, I came here because of ATTINY88 and RF24, I didn't find what I was looking for, but I got interested in your issue and I spotted the problem.. sorry if it's too late and you already figured this out, or gave up, or even workaround it thanks to @ShermanP, who actually gave the right hint, despite not knowing Button.h

The problem is in the assumption that Button.h does about pressed() being called repeatedly inside loop(): as a matter of facts, pressed() returns true only once after pressing the button (what the author calls "rising edge") and then will return false until the button is released and pressed again (outside the debounce interval). However, the only way it has to notice the release (and update its internal state) is by calling pressed() (or read()) again and again, which is usually done at each loop, unless you're putting the device to sleep before this had the occasion to occurr.
You can check the (simple) implementation here Button/Button.cpp at master · madleech/Button · GitHub

You could either do what @ShermanP was suggesting, which is to wait before sleeping again until the button is released, or insert a custom timeout before sleeping, or even do your own button implementation suited for your purpose.

BTW a break inside a FOR loop exits the loop immediately, regardless of being inside an IF or not, so, in your working example, you were never sleeping and you were calling pressed() repeatedly as expected