I am struggling to get my foot switches working properly on my MIDI foot controller. I find that I am changing the debouncing time all the time, but the debouncing is not working steadily when the loop takes slightly too long. I would like somebody to look at my code and see if I am missing something, or if I can simplify things.
Here is my setup:
I have four display boards with three displays and four switches connected to one MCP23017 on each board. The boards are connected over i2c with 5k pull-up resistors. I also have a shared INTA_PIN connected from each MCP23017 through a diode to pin 2 on the Teensy. For this pin I use an internal pullup resistor of the Teensy. The displays are working perfectly, but the switches give me trouble all the time.
I am struggling to keep the loop time down as my project has 13 displays controlled over i2c, 12 neopixel LEDs, 3 serial midi ports, usbmidi and advanced support for many midi devices. Therefore a loop can take up to 100 ms, which then gives me trouble in debouncing the switches.
Here is how the code is working. I am reading two registers from the MCP23017: INTCAPA and GPIOA. The first will give the state at the time of interrupt, the second will give the current state. I have to read both, otherwise I am really missing switch presses or releases. I want to keep the number of i2c reads to a minimum, as they take up a lot of time.
The order of events is:
* Check INTA_PIN is low (MCP23017 is triggered by a button state change)
* Read INTCAPA of the current board
* Check if it clears the interrupt or select the next board. This board will be checked on the next loop cycle.
* If it clears the interrupt set switch_triggered or switch_released and start debouncing
* When debounce timer expires and INTA_PIN is still high check GPIOA to check if we have not missed a release. Update switch_pressed and switch_released accordingly
Here is the part of my code that does the debouncing of displays:
And here is my lcd_lib.h library. It is based upon F.Mathilda's LiquidCrystal library.:
Here is my setup:
I have four display boards with three displays and four switches connected to one MCP23017 on each board. The boards are connected over i2c with 5k pull-up resistors. I also have a shared INTA_PIN connected from each MCP23017 through a diode to pin 2 on the Teensy. For this pin I use an internal pullup resistor of the Teensy. The displays are working perfectly, but the switches give me trouble all the time.
I am struggling to keep the loop time down as my project has 13 displays controlled over i2c, 12 neopixel LEDs, 3 serial midi ports, usbmidi and advanced support for many midi devices. Therefore a loop can take up to 100 ms, which then gives me trouble in debouncing the switches.
Here is how the code is working. I am reading two registers from the MCP23017: INTCAPA and GPIOA. The first will give the state at the time of interrupt, the second will give the current state. I have to read both, otherwise I am really missing switch presses or releases. I want to keep the number of i2c reads to a minimum, as they take up a lot of time.
The order of events is:
* Check INTA_PIN is low (MCP23017 is triggered by a button state change)
* Read INTCAPA of the current board
* Check if it clears the interrupt or select the next board. This board will be checked on the next loop cycle.
* If it clears the interrupt set switch_triggered or switch_released and start debouncing
* When debounce timer expires and INTA_PIN is still high check GPIOA to check if we have not missed a release. Update switch_pressed and switch_released accordingly
Here is the part of my code that does the debouncing of displays:
Code:
#include <i2c_t3.h>
#include <LiquidCrystal.h>
#include "lcd_lib.h"
#define INTA_PIN 2 // Digital Pin 2 of the Teensy is connected to INTA of the MCP23017 on the display boards
#define NUMBER_OF_DISPLAY_BOARDS 4
LiquidCrystal_MCP23017 lcd[NUMBER_OF_DISPLAY_BOARDS] = {
LiquidCrystal_MCP23017 (0x20, DISPLAY1),
LiquidCrystal_MCP23017 (0x21, DISPLAY1),
LiquidCrystal_MCP23017 (0x22, DISPLAY1),
LiquidCrystal_MCP23017 (0x23, DISPLAY1),
};
uint8_t switch_pressed = 0; //Variable set when switch is pressed
uint8_t switch_released = 0; //Variable set when switch is released
uint8_t previous_switch_pressed = 255;
#define DEBOUNCE_TIME 50 // Debounce time for display boards
uint32_t Debounce_timer = 0;
bool Debounce_timer_active = false;
uint8_t Current_board = 0; // The current display board that is read for switches pressed
uint32_t time_switch_pressed;
void inta_pin_interrupt() {
time_switch_pressed = micros(); // Store the time switch was pressed. It will be used for tap tempo if the switch is programmed that way.
}
#define SERIAL_STARTUP_TIME 3000 // Will wait max three seconds max before serial starts
uint32_t serial_timer;
void setup() {
// put your setup code here, to run once:
pinMode(INTA_PIN, INPUT_PULLUP);
attachInterrupt(INTA_PIN, inta_pin_interrupt, FALLING);
Serial.begin(115200);
serial_timer = millis();
while ((!Serial) && (serial_timer - millis() < SERIAL_STARTUP_TIME)) {}; // Wait while the serial communication is not ready or while the SERIAL_START_UP time has not elapsed.
Serial.println("Debugging started...");
for (uint8_t i = 0; i < NUMBER_OF_DISPLAY_BOARDS; i++) {
lcd[i].begin (16, 2);
}
}
void loop() {
// Reset switch variables
switch_pressed = 0;
switch_released = 0;
if (!Debounce_timer_active) {
if (digitalRead(INTA_PIN) == LOW) {
check_switches_on_current_board(true); // Read the state as it was when the interrupt was triggered
if (digitalRead(INTA_PIN) == LOW) { // Check if INT_A pin is still LOW
// we did not find the source of the interrupt and we will need to read the next board
Select_next_board();
}
else {
// Start timer
Debounce_timer = millis();
Debounce_timer_active = true;
}
}
}
else if (millis() - Debounce_timer > DEBOUNCE_TIME) { // Check if debounce timer expires
check_switches_on_current_board(false); //read the current state
Debounce_timer_active = false;
}
if (switch_released > 0) {
Serial.println("Switch released: " + String(switch_released));
}
// Now check for Long pressing a button
if (switch_pressed > 0) {
Serial.println("Switch pressed: " + String(switch_pressed));
}
// Now simulate the rest of the loop
delay(100);
}
void check_switches_on_current_board(bool check_interrupt_state) {
uint8_t new_switch_pressed = 0;
bool updated;
// Read the buttons on this board
if (check_interrupt_state) {
updated = lcd[Current_board].updateButtonInterruptState() && (digitalRead(INTA_PIN) == HIGH);
Serial.println("*** interrupt button_state board" + String(Current_board) + ": " + String(lcd[Current_board].readButtonState()));
}
else {
updated = lcd[Current_board].updateButtonCurrentState();
Serial.println("*** current button_state board" + String(Current_board) + ": " + String(lcd[Current_board].readButtonState()));
}
if (updated) {
uint8_t button_state = lcd[Current_board].readButtonState();
if (button_state & 1) new_switch_pressed = Current_board + 1; // Switch is in bottom row (1-4)
if (button_state & 2) new_switch_pressed = Current_board + NUMBER_OF_DISPLAY_BOARDS + 1; // Switch is in second row (5-8)
if (button_state & 4) new_switch_pressed = Current_board + (NUMBER_OF_DISPLAY_BOARDS * 2) + 1; // Switch is in third row (9-12)
if (button_state & 8) new_switch_pressed = Current_board + (NUMBER_OF_DISPLAY_BOARDS * 3) + 1; //Switch is in top row (13 - 16)
if (new_switch_pressed != previous_switch_pressed) { // Check for state change
switch_released = previous_switch_pressed;
previous_switch_pressed = new_switch_pressed; // Need to store the previous version, because switch_pressed can only be active for one cycle!
switch_pressed = new_switch_pressed; // switch_pressed must be set here, so accidental presses of switches below will not result in a command being executed.
}
}
Serial.println("******* Switches read on board " + String(Current_board) + "!!! ********");
}
void Select_next_board() {
Current_board++;
if (Current_board >= NUMBER_OF_DISPLAY_BOARDS) Current_board = 0;
}
And here is my lcd_lib.h library. It is based upon F.Mathilda's LiquidCrystal library.:
Code:
// Please read VController_v2.ino for information about the license and authors
#ifndef LCDLIB_H
#define LCDLIB_H
// Start of my MCP23017 library - a derived class from FMathilda's LiquidCrystal library.
// Connections to MCP23017
// GPIO B to 8 data bits of three displays
// GPIO A pin 1-4 to switch 1-4
// GPIO A pin 5 to Rs pin of three displays
// GPIO A pin 6,7 and 8 to the E pin of display 1,2 and 3
#include <Print.h>
#include <i2c_t3.h>
#include <LCD.h>
//#include <I2CIO.h>
// Define pins on MCP23017 for the displays
#define GPIO_RS_PIN B00010000 // Pin 5 of GPIO A is the Rs pin
#define DISPLAY1 B10000000 // Pin 8 of GPIO A is the E pin for display 1
#define DISPLAY2 B01000000 // Pin 7 of GPIO A is the E pin for display 2
#define DISPLAY3 B00100000 // Pin 6 of GPIO A is the E pin for display 3
#define DISPLAY_ALL B11100000 // Pin 6-8 for all displays
#define SWITCH_PINS B00001111 // Pin 1-4 are switch pins
// MCP23017 registers
#define MCP23017_IODIRA 0x00
#define MCP23017_IPOLA 0x02
#define MCP23017_GPINTENA 0x04
#define MCP23017_DEFVALA 0x06
#define MCP23017_INTCONA 0x08
#define MCP23017_IOCONA 0x0A
#define MCP23017_GPPUA 0x0C
#define MCP23017_INTFA 0x0E
#define MCP23017_INTCAPA 0x10
#define MCP23017_GPIOA 0x12
#define MCP23017_OLATA 0x14
#define MCP23017_IODIRB 0x01
#define MCP23017_IPOLB 0x03
#define MCP23017_GPINTENB 0x05
#define MCP23017_DEFVALB 0x07
#define MCP23017_INTCONB 0x09
#define MCP23017_IOCONB 0x0B
#define MCP23017_GPPUB 0x0D
#define MCP23017_INTFB 0x0F
#define MCP23017_INTCAPB 0x11
#define MCP23017_GPIOB 0x13
#define MCP23017_OLATB 0x15
class LiquidCrystal_MCP23017 : public LCD
{
public:
// Class constructor
// lcd_Addr is the i2C address of the MCP23017 on the display board
// lcd_Number is the number of the display (1, 2 or 3)
LiquidCrystal_MCP23017 (uint8_t lcd_Addr, uint8_t lcd_Number);
virtual void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS);
virtual void send(uint8_t value, uint8_t mode);
bool updateButtonInterruptState (); // Updates state of buttons, returns true when updated
uint8_t readButtonState (); // Returns state of buttons
bool updateButtonCurrentState (); // Reads the current state regardless of the interrupt - needed, because we are missing switch off messages
private:
void expanderWrite (const byte reg, const byte data );
void expanderWriteBoth (const byte reg, const byte dataA, uint8_t dataB );
uint8_t expanderRead (const byte reg);
int init(); // Initialize the LCD class and the MCP23017
uint8_t _Addr; // I2C Address of the IO expander
uint8_t _Number;
uint8_t _En; // LCD expander word for enable pin
uint8_t _Rs; // LCD expander word for Register Select pin
uint8_t _Rw; // LCD expander word for R/W pin (not implemented)
uint8_t _ButtonState; // State of buttons
uint8_t _NewButtonState; // Current state of buttons
};
LiquidCrystal_MCP23017::LiquidCrystal_MCP23017 (uint8_t lcd_Addr, uint8_t lcd_Number) {
_Addr = lcd_Addr;
_Number = lcd_Number; // LCD number is 1, 2 or 3 for display 1,2 or 3
_Rs = GPIO_RS_PIN; // Pin 4 is the default Rs pin
_En = lcd_Number; // Pin 7 for display 1, pin 6 for display 2 and pin 5 for display 3
_Rw = 0; // RW pin is not implemented
}
// Initialization of i2c, MCP23017 ports and display
int LiquidCrystal_MCP23017::init()
{
int status = 0;
// initialize the MCP23017 expander
// and display functions.
// ------------------------------------------------------------------------
Wire.begin();
Wire.setClock(I2C_RATE_1200); // Set i2c clock to 1.2 MHz - the maximum speed of the MCP23017 is 1.7 MHz
if ( Wire.requestFrom ( _Addr, (uint8_t)1 ) == 1 )
{
// MCP23017 has four switches connected to pin 1-4 of Port A
// Port 5-8 of Port A and all pins of Port B are connected to the displays
// MCP PortA pin 1-4 input and pin 5-8 output
// MCP PortB all output
expanderWriteBoth(MCP23017_IODIRA, SWITCH_PINS, 0x00);
// Setup for INTA port to be triggered by switch change
expanderWriteBoth (MCP23017_IOCONA, 0b01100000, 0b01100000); // mirror interrupts, disable sequential mode
expanderWriteBoth (MCP23017_GPPUA, SWITCH_PINS, 0x00); // pull-up resistor for switch pins
expanderWriteBoth (MCP23017_IPOLA, SWITCH_PINS, 0x00); // invert polarity of signal for switch pins
expanderWriteBoth (MCP23017_GPINTENA, SWITCH_PINS, 0x00); // Enable interrupts for switch pins
// read from interrupt capture ports to clear them
expanderRead (MCP23017_INTCAPA);
//expanderRead (MCP23017_INTCAPB);
// setup port 1 D7 = E; D6 = RS
expanderWrite(MCP23017_GPIOA, _Rw);
_displayfunction = LCD_8BITMODE | LCD_1LINE | LCD_5x8DOTS;
status = 1;
}
return ( status );
}
void LiquidCrystal_MCP23017::begin(uint8_t cols, uint8_t lines, uint8_t dotsize)
{
init(); // Initialise the I2C expander interface
LCD::begin ( cols, lines, dotsize );
}
// Display low-level stuff
// send is called from the base LCD library
void LiquidCrystal_MCP23017::send(uint8_t value, uint8_t mode)
{
// Is it a command or data
// -----------------------
if ( mode == DATA )
{
mode = _Rs;
}
// Now we toggle the enable bit(s) high and vrite the data in the first write cycle
expanderWriteBoth(MCP23017_GPIOA, (mode | _En), value); // Enable bit high and value
expanderWrite(MCP23017_GPIOA, (mode)); // Enable bit low
}
// update button state
bool LiquidCrystal_MCP23017::updateButtonInterruptState () {
bool updated = false;
_NewButtonState = expanderRead(MCP23017_INTCAPA) & SWITCH_PINS; // INTCAPA remembers the state at the time of the interrupt
//_NewButtonState = expanderRead(MCP23017_GPIOA) & SWITCH_PINS; // GPIO gives the current state
if (_NewButtonState != _ButtonState) {
_ButtonState = _NewButtonState;
updated = true;
}
return updated;
}
// Read button state (unbounced)
uint8_t LiquidCrystal_MCP23017::readButtonState () {
return _ButtonState;
}
bool LiquidCrystal_MCP23017::updateButtonCurrentState () {
bool updated = false;
//_NewButtonState = expanderRead(MCP23017_INTCAPA) & SWITCH_PINS; // INTCAPA remembers the state at the time of the interrupt
_NewButtonState = expanderRead(MCP23017_GPIOA) & SWITCH_PINS; // GPIO gives the current state
if (_NewButtonState != _ButtonState) {
_ButtonState = _NewButtonState;
updated = true;
}
return updated;
}
// **** MCP23017 expander writing and reading
// write a byte to a ports of the MCP23017 expander
void LiquidCrystal_MCP23017::expanderWrite (uint8_t reg, uint8_t data )
{
Wire.beginTransmission (_Addr);
Wire.send (reg);
Wire.send (data);
Wire.endTransmission ();
}
// write two bytes to both ports of the MCP23017 expander
void LiquidCrystal_MCP23017::expanderWriteBoth (uint8_t reg, uint8_t dataA, uint8_t dataB )
{
Wire.beginTransmission (_Addr);
Wire.send (reg);
Wire.send (dataA); // port A
Wire.send (dataB); // port B
Wire.endTransmission ();
}
// read a byte from the MCP23017 expander
uint8_t LiquidCrystal_MCP23017::expanderRead (uint8_t reg)
{
Wire.beginTransmission (_Addr);
Wire.send (reg);
Wire.endTransmission ();
Wire.requestFrom (_Addr, (uint8_t) 1);
return Wire.read();
}
#endif
Last edited: