2

I have this project of a "phone". It consists of:

  • Arduino DUE,
  • 4x4 Keypad,
  • SD Data shield,
  • a phone receiver detect (on/off)
  • an Amp and a Speaker.

In the beginning, after the phone receiver is being picked, I am playing a .wav file of an intro on how to choose a language. And this .wav should be interrupted when the user presses the 4th row of a keypad (Buttons A/B/C/D). I only need one pin as far as I know, in this setup - pin 38 (Column 4 pin). As I understand, there is no proper way to declare a keypad input pin as an interrupt on DUE. Or is it?

So my question - how to wire a keypad input pin as an interrupt? I have read about wiring with a diode. But most of the threads lack any specifics.

Can someone push me in the right direction? Cheers!

Code:

#include <DAC.h>
#include <SD.h>
#include <SPI.h>
#include <Audio.h>
#include <Key.h>
#include <Keypad.h>

const byte ROW_NUM = 4; const byte COLUMN_NUM = 4;

int code1 = 12345; //The code I used, you can change it int code2 = 45678; //The code I used, you can change it int code3 = 78900; //The code I used, you can change it

int tot, i1, i2, i3, i4, i5; char c1, c2, c3, c4;

char hexaKeys[ROW_NUM][COLUMN_NUM] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} };

byte hookPinA = 23; // your interrupt pin byte interruptPin = 22; // your interrupt pin

byte pin_rows[ROW_NUM] = {52, 50, 48, 46}; //connect to the row pinouts of the keypad byte pin_column[COLUMN_NUM] = {44, 42, 40, 38}; //connect to the column pinouts of the keypad

int keyCounter = 0;

Keypad myKeypad = Keypad(makeKeymap(hexaKeys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM);

char keypresses[5]; char language; char key;

int goIntoKeys = 0;

volatile uint8_t interruptState = 0; volatile byte hookOffState = 0;

void setup() { // debug output at 9600 baud Serial.begin(9600); SD.begin(10); analogWriteResolution(12);

//for( int i=0; i< ROW_NUM; ++i) pinMode(pin_rows[i], INPUT_PULLUP); //for( int i=0; i< COLUMN_NUM; ++i) pinMode(pin_column[i], INPUT_PULLUP);

pinMode(hookPinA, INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(hookPinA), hookOffToggle, CHANGE);

// EIFR |= bit(INTF0); // clear INT0 interrupt flag

attachInterrupt(digitalPinToInterrupt(interruptPin), interruptToggle, RISING); //test interrupt with a regular button

pinMode(38, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(38), interruptToggle, RISING);

DACC->DACC_CHDR = DACC_CHDR_CH1; //disable DAC1

}

int state = 0; const int STATE_OFF = 0; const int STATE_IDLE = 1; const int STATE_LISTEN_KEY = 2; const int STATE_PLAYER = 3;

bool isLanguageKey(char customKey) { return ('A' == customKey || 'B' == customKey || 'C' == customKey || 'D' == customKey); }

void loop() { switch (state) { case STATE_OFF: //listen for a telephone receiver to be removed if (hookOffState == 1) { Serial.print("HOOK OFF "); Serial.println(hookOffState); interruptState = 0; state = STATE_IDLE; } else if (hookOffState == 0) { Serial.print("HOOK ON "); Serial.println(hookOffState); } break; case STATE_IDLE: //listen for a language key. Cannot dial a number before Serial.print("interrupt IDLE "); Serial.println(interruptState); if (interruptState == 1) { buttonBeep(); processLanguage(); state = STATE_LISTEN_KEY; } else if (hookOffState == 0){ state = STATE_OFF; } else { playSound("intro.wav"); } break; case STATE_LISTEN_KEY: //listen for any key Serial.print("interrupt LISTEN KEY "); Serial.println(interruptState); if (isKeyPressed()) { interruptState = 0; if (isLanguageKey(key)) { buttonBeep(); processLanguage(); } else { Serial.print("interruptStateF "); Serial.println(interruptState); buttonBeep(); keypresses[keyCounter] = key; keyDial(); keyCounter++; if (keyCounter > 4) { state = STATE_PLAYER; //when 5 numbers are pressed - try to open the .wav file } } } else if (hookOffState == 0){ state = STATE_OFF; } break; case STATE_PLAYER: //Play the audio and go back to state LISTEN calculateAndPlay(); keyCounter = 0; interruptState = 0; state = STATE_LISTEN_KEY; break; default: break; } }

void hookOffToggle(){ if (digitalRead(hookPinA) == 1) { hookOffState = 1; } else { hookOffState = 0; } }

void interruptToggle() { interruptState = 1; }

void kalba() { Serial.println("Kalba? Language? Valoda? Yazik?"); }

bool isKeyPressed() { key = myKeypad.getKey(); return key != NO_KEY; }

bool langKeyPressed() { key = myKeypad.getKey(); return key != NO_KEY && isLanguageKey(key); }

void processLanguage() { //press A/B/C/D keys - select 1 out of 4 languages //goIntoKeys = 1; keyCounter = 0; if (key == 'A') { Serial.println(" LT "); } if (key == 'B') { Serial.println(" EN "); } if (key == 'C') { Serial.println(" LV "); } if (key == 'D') { Serial.println(" RU "); } language = key; }

void keyDial() { // LCD displays the number dialed in 3 slots. keypresses[keyCounter] = 0x00;//null-byte keypresses[keyCounter] = key;

if (key != NO_KEY) { Serial.println(keypresses[keyCounter]); } }

void buttonBeep(){ for (int j = 0; j < 200; j++) { for (int i = 0; i < 200; i++) analogWrite(DAC0, i); for (int i = 200; i >= 0; i--) analogWrite(DAC0, i); } }

void backgroundHumm() { for (int j = 0; j < 256; j++) { for (int i = 0; i < 256; i++) analogWrite(DAC0, j); for (int j = 256; j >= 0; j--) { for (int i = 256; i >= 0; i--) analogWrite(DAC0, j); } } }

void calculateAndPlay() { //if we have 4+1 numbers - proceed //the keys pressed are stored into chars I convert them to int then i did some multiplication to get the code as an int of xxxx i1 = (keypresses[0] - 48) * 10000; i2 = (keypresses[1] - 48) * 1000; i3 = (keypresses[2] - 48) * 100; i4 = (keypresses[3] - 48) * 10; i5 = (keypresses[4] - 48) * 1;

tot = i1 + i2 + i3 + i4 + i5; if (language == 'A') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiLT.wav"); } else if (tot == code2) { playSound("zuvisLT.wav"); } else if (tot == code3) { playSound("draugLT.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasLT.wav"); } } if (language == 'B') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiEN.wav"); } else if (tot == code2) { playSound("zuvisEN.wav"); } else if (tot == code3) { playSound("draugEN.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasEN.wav"); } } if (language == 'C') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiLV.wav"); } else if (tot == code2) { playSound("zuvisLV.wav"); } else if (tot == code3) { playSound("draugLV.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasLV.wav"); } } if (language == 'D') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiRU.wav"); } else if (tot == code2) { playSound("zuvisRU.wav"); } else if (tot == code3) { playSound("draugRU.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasRU.wav"); } } }

// Playing assigned filename void playSound(const char* cName) {
File myFile = SD.open(cName); const int S = 1024; // Number of samples to read in block short buffer[S]; // until the file is not finished Serial.println("playing "); Serial.println(cName);

Audio.begin(44100, 200); delay(200); while (myFile.available()) { myFile.read(buffer, sizeof(buffer)); // Prepare samples int volume = 512; //max volume 1024 Audio.prepare(buffer, S, volume); Audio.write(buffer, S); if ( interruptState ) break; else if ( hookOffState == 0 ) break; } Audio.end(); myFile.close(); delay(50); DACC->DACC_CHDR = DACC_CHDR_CH1; //disable DAC1 }

The schematics (if you can call it that) below: enter image description here

EDIT: @Damago solution works! I just wanted to share here the solution for future searches. What you need to do is to get the 4th column of 4x4 keypad to work as an interrupt properly: Set all row pins as output and column pins as input with pullup.

for( int i=0; i< ROW_NUM; ++i) pinMode(pin_rows[i], OUTPUT);
for( int i=0; i< COLUMN_NUM; ++i) pinMode(pin_column[i], INPUT_PULLUP);

Set row pins to low:

for( int i=0; i< ROW_NUM; ++i) digitalWrite(pin_rows[i], LOW);

Attach the 4th column pin as interrupt:

attachInterrupt(digitalPinToInterrupt(pin_column[3]), interruptToggle, FALLING);

And of course, fix the syntax overall (it was a bit messy :) )

Full sketch below:

#include <DAC.h>
#include <SD.h>
#include <SPI.h>
#include <Audio.h>
#include <Keypad.h>

const byte ROW_NUM = 4; const byte COLUMN_NUM = 4;

int code1 = 12345; //The code I used, you can change it int code2 = 45678; //The code I used, you can change it int code3 = 78900; //The code I used, you can change it

int tot, i1, i2, i3, i4, i5; char c1, c2, c3, c4;

char hexaKeys[ROW_NUM][COLUMN_NUM] = { {'1', '2', '3', 'A'}, {'4', '5', '6', 'B'}, {'7', '8', '9', 'C'}, {'*', '0', '#', 'D'} };

byte hookPinA = 23; // your interrupt pin byte pin_rows[ROW_NUM] = {52, 50, 48, 46}; //connect to the row pinouts of the keypad byte pin_column[COLUMN_NUM] = {44, 42, 40, 38}; //connect to the column pinouts of the keypad

Keypad myKeypad = Keypad(makeKeymap(hexaKeys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM);

char keypresses[5]; char language; char key;

volatile uint8_t interruptState = 0; volatile byte hookOffState = 0; int keyCounter = 0;

void setup() { // debug output at 9600 baud Serial.begin(9600); SD.begin(10); analogWriteResolution(12);

for( int i=0; i< ROW_NUM; ++i) pinMode(pin_rows[i], OUTPUT); for( int i=0; i< COLUMN_NUM; ++i) pinMode(pin_column[i], INPUT_PULLUP);

for( int i=0; i< ROW_NUM; ++i) digitalWrite(pin_rows[i], LOW); pinMode(hookPinA, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(hookPinA), hookOffToggle, CHANGE); attachInterrupt(digitalPinToInterrupt(pin_column[3]), interruptToggle, FALLING);

DACC->DACC_CHDR = DACC_CHDR_CH1; //disable DAC1 }

int state = 0; const int STATE_OFF = 0; const int STATE_IDLE = 1; const int STATE_LISTEN_KEY = 2; const int STATE_PLAYER = 3;

bool isLanguageKey(char customKey) { return ('A' == customKey || 'B' == customKey || 'C' == customKey || 'D' == customKey); }

void loop() { switch (state) { case STATE_OFF: //listen for a telephone receiver to be removed if (hookOffState == 1) { interruptState = 0; state = STATE_IDLE; } else if (hookOffState == 0) { } break; case STATE_IDLE: //listen for a language key. Cannot dial a number before if (interruptState == 1) { isKeyPressed(); //get the key input buttonBeep(); processLanguage(); state = STATE_LISTEN_KEY; } else if (hookOffState == 0){ state = STATE_OFF; } else { playSound("intro.wav"); } break; case STATE_LISTEN_KEY: //listen for any key if (isKeyPressed()) { interruptState = 0; if (isLanguageKey(key)) { buttonBeep(); processLanguage(); } else { buttonBeep(); keypresses[keyCounter] = key; keyCounter++; if (keyCounter > 4) { state = STATE_PLAYER; //when 5 numbers are pressed - try to open the .wav file } } } else if (hookOffState == 0){ state = STATE_OFF; } break; case STATE_PLAYER: //Play the audio and go back to state LISTEN calculateAndPlay(); keyCounter = 0; interruptState = 0; state = STATE_LISTEN_KEY; break; default: break; } }

void hookOffToggle() { //detect a telephone receiver to be removed hookOffState = digitalRead(hookPinA); }

void interruptToggle() { interruptState = 1; }

bool isKeyPressed() { key = myKeypad.getKey(); return key != NO_KEY; }

bool langKeyPressed() { key = myKeypad.getKey(); return key != NO_KEY && isLanguageKey(key); }

void processLanguage() { //press A/B/C/D keys - select 1 out of 4 languages keyCounter = 0; if (key == 'A') { Serial.println(" LT "); } if (key == 'B') { Serial.println(" EN "); } if (key == 'C') { Serial.println(" LV "); } if (key == 'D') { Serial.println(" RU "); } language = key; }

void buttonBeep(){ for (int j = 0; j < 200; j++) { for (int i = 0; i < 200; i++) analogWrite(DAC0, i); for (int i = 200; i >= 0; i--) analogWrite(DAC0, i); } }

void backgroundHumm() { for (int j = 0; j < 256; j++) { for (int i = 0; i < 256; i++) analogWrite(DAC0, j); for (int j = 256; j >= 0; j--) { for (int i = 256; i >= 0; i--) analogWrite(DAC0, j); } } }

void calculateAndPlay() { //if we have 4+1 numbers - proceed //the keys pressed are stored into chars I convert them to int then i did some multiplication to get the code as an int of xxxx i1 = (keypresses[0] - 48) * 10000; i2 = (keypresses[1] - 48) * 1000; i3 = (keypresses[2] - 48) * 100; i4 = (keypresses[3] - 48) * 10; i5 = (keypresses[4] - 48) * 1;

tot = i1 + i2 + i3 + i4 + i5; if (language == 'A') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiLT.wav"); } else if (tot == code2) { playSound("zuvisLT.wav"); } else if (tot == code3) { playSound("draugLT.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasLT.wav"); } } if (language == 'B') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiEN.wav"); } else if (tot == code2) { playSound("zuvisEN.wav"); } else if (tot == code3) { playSound("draugEN.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasEN.wav"); } } if (language == 'C') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiLV.wav"); } else if (tot == code2) { playSound("zuvisLV.wav"); } else if (tot == code3) { playSound("draugLV.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasLV.wav"); } } if (language == 'D') { if (tot == code1) //if the code is correct you trigger to play .wav { playSound("ledaiRU.wav"); } else if (tot == code2) { playSound("zuvisRU.wav"); } else if (tot == code3) { playSound("draugRU.wav"); } else //if the code is wrong you get a beep and audio message { playSound("blogasRU.wav"); } } }

// Playing assigned filename void playSound(const char* cName) {
File myFile = SD.open(cName); const int S = 1024; // Number of samples to read in block short buffer[S]; // until the file is not finished Serial.println("playing "); Serial.println(cName);

Audio.begin(44100, 200); delay(200); while (myFile.available()) { myFile.read(buffer, sizeof(buffer)); // Prepare samples int volume = 512; //max volume 1024 Audio.prepare(buffer, S, volume); Audio.write(buffer, S); if ( interruptState ) break; else if ( hookOffState == 0 ) break; } Audio.end(); myFile.close(); delay(50); DACC->DACC_CHDR = DACC_CHDR_CH1; //disable DAC1 }

2 Answers2

3

There is a bit of a problem here. You cannot simply use one diode. There voltage is present on the keyboard pins only during getKey() function evaluation.

The keyboard works this way: when you call a myKeypad.getKey(); then arduino does the following:

  • changes the first column from input (high impedance) to output
  • outputs 0V voltage to first column,
  • scans all rows if they are 0V (normally they are pulled up - you know what pullup is).
  • if any key in this column is pressed then the column voltage of 0V is pulling down the corresponding row voltage. That is there is a connection between row and column.

Here is the exact code:

    pin_mode(columnPins[c],OUTPUT);
    pin_write(columnPins[c], LOW);  // Begin column pulse output.
    for (byte r=0; r<sizeKpd.rows; r++) {
        bitWrite(bitMap[r], c, !pin_read(rowPins[r]));  // keypress is active low so invert to high.
    }
    // Set pin to high impedance input. Effectively ends column pulse.
    pin_write(columnPins[c],HIGH);
    pin_mode(columnPins[c],INPUT);

It is not impossible to do this this way but it would require either modifying keyboard or creating a bit more complicated circuit. However it really does not make real sense.

I would do this different way: I would completely skip interrupt here. You are checking for a flag set inside interrupt:

if ( interruptState ) break;

I would directly check here for the voltage on a specific pin. You have to (simply):

  • set row pin (46 as far as I understand from your code) to output, and set it to low,
  • set column pins (all of them) to input-pullup - as I understand {44, 42, 40, 38}
  • check if any of those pins is 'LOW', if yes - then break;

As far as I understand the library you don't even have to revert any pins back, the library re-initializes the pins correctly on every GetKey()

If for any reason the operation above takes too long you can shorten it using direct port manipulation as in https://forum.arduino.cc/t/arduino-due-how-to-direrct-port-access/251656 but note, that it is not as simple on Arduino Due as on AVR based ones.

Damago
  • 66
  • 1
1

If you want to use interrupts, it requires a separate pin to detect the falling edge on column 4 (pin 38). In the following example, I used pin 28 for the column 4 interrupt. These can be connected together via a 1K resistor to protect against pin 28 inadvertently outputting low while the other is outputting high.

Pin 38 is pulled low during the call of myKeypad.getKey() thus triggering the ISR.

Working copies are made of the variables that are updated in the ISRs. This will require changes to the state machine to process column4Pressed obtained from the ISR.

const byte hookPinA = 23;  // your interrupt pin
const byte interruptPin = 22;  // your interrupt pin

const byte column4InterruptPin = 28; // Column 4 interrupt pin connected to column4Pin via a wire link. Could add a 1K series resistor to protect against short circuit to ground if it were output low when pin 38 is output high. const byte column4Pin = 38; // Column 4 pin connected to column4InterruptPin via a wire link.

// States updated by ISR. volatile byte isr_hookOffState = 0; volatile byte isr_interruptState = 0; volatile bool isr_column4Pressed = false;

// Working copies of ISR data. byte hookOffState = 0; byte interruptState = 0; bool column4Pressed = false;

// Use a single byte for the state variable and initialise it to OFF. enum class State : byte { OFF = 0, IDLE = 1, LISTEN_KEY = 2, PLAYER = 3 } state = State::OFF;

void setup() { pinMode(hookPinA, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(hookPinA), hookOffToggle, CHANGE);

pinMode(interruptPin, INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), interruptToggle, RISING); // test interrupt with a regular button

pinMode(column4InterruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(column4InterruptPin), interruptColumn4, FALLING);  // Triggered by pulling column 4 low, e.g. digitalWrite(column4Pin, LOW) or within myKeypad.getKey().

}

void loop() { // Get a fresh copy of the ISR data and reset the interrupt state flag atomically // by temporarily disabling interrupts. cli(); hookOffState = isr_hookOffState; interruptState = isr_interruptState; column4Pressed = isr_column4Pressed; isr_interruptState = 0; // Reset to detect next interrupt. isr_column4Pressed = false; // Reset to detect next interrupt. sei();

// Alternative method to test whether any button in column 4 is pressed.
// This is how to do it without using interrupts.
/*
pinMode(column4Pin, OUTPUT);
digitalWrite(column4Pin, LOW);
column4Pressed = !digitalRead(52) || !digitalRead(50) || !digitalRead(48) || !digitalRead(46);
digitalWrite(column4Pin, HIGH);
pinMode(column4Pin, INPUT);
*/

// Process the working copies of the ISR data.
// You will need to change the state machine logic to process column4Pressed obtained from the ISR.

switch (state)
{
case State::OFF:
    break;
case State::IDLE:
    break;
case State::LISTEN_KEY:
    break;
case State::PLAYER:
    break;
}

}

void hookOffToggle() { isr_hookOffState = digitalRead(hookPinA); }

void interruptToggle() { isr_interruptState = 1; }

void interruptColumn4() { // Test whether any button in column 4 is pressed. isr_column4Pressed = !digitalRead(52) || !digitalRead(50) || !digitalRead(48) || !digitalRead(46); }

Here is the Wokwi simulation which uses an Arduino Mega and pin 2 for the column 4 interrupt. Note that there is no protection resistor between pins 2 and 38. The Wokwi simulator will create a Value Change Dump (VCD) file for viewing in your favourite trace analyser:

Screenshot of Wokwi simulation of keypad interrupt

Alternatively, if you don't want to use interrupts, you could pull column 4 (pin 38) low and test the row pins for a logic low. See the commented code in loop().

tim
  • 699
  • 6
  • 15