Arduino Nano I2C Button Matrix Interface not working

Dear Community,
I have a project which uses two Arduinos. The Arduino Mega as the main board and the Arduino Nano as Button Interface Controlller.

The Mega is set up as a I2C Master and the Nano as a Slave.
My Goal is to get all Events recognized by the Arduino Nano to the Arduino Mega.
The Problem is that sometimes not all Events get there, especially if i press alot of buttons at once.

The concept for my program is that the Nano scans the inputs and stores the input events in a buffer and the Mega is polling events from that buffer in an interval.

Arduino Mega -> I2C Master -> simplified Code

unsigned long pollLast;

void setup() {
  // put your setup code here, to run once:
  // Init I2C Communication as a Master
  Wire.begin();
  Wire.setClock(100000); // Standard Mode
}

void loop() {
  if ((micros() - this->_pollLast) > 2000) {
    // Send Command
    Wire.beginTransmission(SLAVE_ADDR);
    Wire.write(GET_EVENT);
    switch (Wire.endTransmission()) {
      case 0: // success
        Wire.requestFrom(SLAVE_ADDR, 2);
        // Read response from Slave
        while (Wire.available()) {
          this->inputEvent.from = Wire.read();
          this->inputEvent.type = Wire.read();
        }
        pollLast = micros();  
      break;
      case 1: 
        Serial.println("UCI::_poll(): data too long to fit in transmit buffer");
        break;
      case 2:
        Serial.println("UCI::_poll(): received NACK on transmit of address");
        break;
      case 3:
        Serial.println("UCI::_poll(): received NACK on transmit of data");
        break;
      case 4:
        Serial.println("UCI::_poll(): other error.");
        break;
    }
  }
}

The Nano runs a program which successfully scans for an inputEvent to happen ( i checked that it is succesful) and then it tries to store that event in a buffer so the mega can pick it through i2c.
I think my problem lies in the implementation of the buffer…
Here is the simplified code for the Nano, this is more kind of pseudo code to make you understand it more easily:

Arduino Nano -> I2C Slave -> rewritten and more simple Code

#include <Wire.h>
#include "userInterfaceGlobals.h"
#include "buttonMatrix.h"

// Event struct
/*
struct InputEvent {
  volatile int8_t from;
  volatile int8_t type;
};*/ 

// Event Buffer
#define EVENT_BUFFER_SIZE 64
InputEvent eventBuffer[EVENT_BUFFER_SIZE];
volatile uint8_t eventBufferHead;
volatile uint8_t eventBufferTail;
volatile bool updateEventBufferHeadFlag = false;

// Buttons
unsigned long buttonLastCheck;

// I2C 
#define SLAVE_ADDR 9
// EVENT_SOURCES - > will be stored in inputEvent.from
//enum { NONE = -1 };
// COMMANDS
//enum { GET_EVENT = 100 };
int8_t I2C_command;

// Init global inputEvent variable
InputEvent inputEvent = { NONE, NONE }; // I declared NONE=-1 as a global constant somewhere else

// Init Button Matrix
ButtonMatrix buttonMatrix;

void setup() {
  Serial.begin(115200);

  // Init I2C Communication as a Slave
  Wire.begin(SLAVE_ADDR);
  // Function to run when data received from master
  Wire.onReceive(onReceiveFromMaster);
  Wire.onRequest(onRequestFromMaster);

  // Init Event Buffer
  for (int i = 0; i < EVENT_BUFFER_SIZE; i++) {
    eventBuffer[i] = { NONE, NONE };
  }
  
  // Init Button Matrix
  buttonMatrix.begin();
}

void loop() {
  // clear global inputEvent
  inputEvent = { NONE, NONE };
  // check for new event
  checkInputs();
  // Serial.println(inputEvent); // uncommenting this line i see that all events -> thats working until here.
  addEventToBuffer();
  // Check if  onRequest Interrupt happened -> if an event has been picked from the buffer.
  //if (updateEventBufferHeadFlag) {
    updateEventBufferHead();
    //updateEventBufferHeadFlag = false;
  //}
  
}
/* Check Button Input
-------------------------------------------------------------- */
void checkInputs() {  
  if ((micros() - buttonLastCheck) > 2000) { // this one gets called more often
    inputEvent = buttonMatrix.scan(); // This method returns an InputEvent
    buttonLastCheck = micros();
  }
}

/* Buffer
-------------------------------------------------------------- */
void addEventToBuffer() {
  if (inputEvent.from != NONE) {
    Serial.println("event");
    // Get the next Index 
    increaseBufferHeadIndex();  
    // Report Buffer overflow
    if (eventBuffer[eventBufferHead].from != NONE) {
      Serial.println("Buffer Overflow!");
      Serial.print("eventBufferHead = "); Serial.println(eventBufferHead);
    }
    // Set event to buffer    
    eventBuffer[eventBufferHead] = inputEvent;
    // Clear inputEvent 
    inputEvent = { NONE, NONE };
  }
}
void increaseBufferHeadIndex() {
  // Update Buffer Head
  if (eventBufferHead < (EVENT_BUFFER_SIZE-1)) {
    eventBufferHead++;
  } else {
    eventBufferHead = 0;
  }
}
void updateEventBufferHead() {
  //Serial.println("updateEventBufferHead");
  // Clear that event in buffer
  eventBuffer[eventBufferTail] = { NONE, NONE };
  increaseBufferHeadIndex();  
}

/* I2C Services
-------------------------------------------------------------- */
void onReceiveFromMaster(int numBytes) { 
  if (0 < Wire.available()) {
    I2C_command = Wire.read();
  }
}
void onRequestFromMaster() {
  switch(I2C_command) {
    case GET_EVENT:
      byte answer[2];
      // Set tail index one before eventBufferHead
      eventBufferTail = (eventBufferHead > 0) ? eventBufferHead -1 : (EVENT_BUFFER_SIZE - 1);
      Wire.write(eventBuffer[eventBufferTail].from);
      Wire.write(eventBuffer[eventBufferTail].type);
      eventBuffer[eventBufferTail] = { NONE, NONE };
      //updateEventBufferHeadFlag = true;
      increaseBufferHeadIndex();
      I2C_command = NONE;
      break;
  }
}

Can anyone help me understand why sometimes I don’t receive all Events I get in the line
Serial.println(inputEvent); in the Nano’s loop() code on the Arduino Mega?
I tested alot of different things already. At the moment i also have 220 ohm in in series with the SCL, and SDA line to stop reflections because I thought that it is signal quality problem. But actually i think my problem is the buffer. My real code gets kind of big at the moment and I dont have progress with it for some days now. I would be very grateful for any help.

I think that the onRequest Interrupt somehow crashes the writing or reading to the eventBuffer.

EDIT: I updated the Nanos code here, I am getting Buffer Overflow messages when i hit a lot of buttons. So the problems seems to be in the Nano's code

-Flub

I think i somehow found a solution. Instead of sending single entries of the buffer I just sent back the whole buffer without the item at the buffer Write pos.
This is the code...

#include <Wire.h>
#include "userInterfaceGlobals.h"
#include "buttonMatrix.h"

// Event struct

// Event Buffer
#define I2C_EVENT_BUFFER_SIZE 10
InputEvent eventBuffer[I2C_EVENT_BUFFER_SIZE];
volatile uint8_t eventBufferWrite;

// Buttons
unsigned long buttonLastCheck;

// I2C 
#define SLAVE_ADDR 9
// EVENT_SOURCES - > will be stored in inputEvent.from
//enum { NONE = -1 };
// COMMANDS
//enum { GET_EVENT = 100 };
int8_t I2C_command;

// Init global inputEvent variable
InputEvent inputEvent = { NONE, NONE }; // I declared NONE=-1 as a global constant somewhere else

// Init Button Matrix
ButtonMatrix buttonMatrix;

void setup() {
  Serial.begin(115200);

  // Init I2C Communication as a Slave
  Wire.begin(SLAVE_ADDR);
  // Function to run when data received from master
  Wire.onReceive(onReceiveFromMaster);
  Wire.onRequest(onRequestFromMaster);

  // Init Event Buffer
  for (int i = 0; i < I2C_EVENT_BUFFER_SIZE; i++) {
    eventBuffer[i] = { NONE, NONE };
  }
  
  // Init Button Matrix
  buttonMatrix.begin();
}

void loop() {
  inputEvent = { NONE, NONE };
  checkInputs();
  addEventToBuffer();
}
/* Check Button Input
-------------------------------------------------------------- */
void checkInputs() {  
  if ((micros() - buttonLastCheck) > 2000) { // this one gets called more often
    inputEvent = buttonMatrix.scan(); // This method returns an InputEvent
    buttonLastCheck = micros();
  }
}

/* Buffer
-------------------------------------------------------------- */
void addEventToBuffer() {
  if (inputEvent.from != NONE) {
    // Report Buffer overflow
    if (getNextEventBufferWrite()) {
       // Set event to buffer    
      eventBuffer[eventBufferWrite] = inputEvent;
      // Clear inputEvent 
      inputEvent = { NONE, NONE };
      // Increase the Buffer Write
      eventBufferWrite = (eventBufferWrite < (I2C_EVENT_BUFFER_SIZE - 1)) ? eventBufferWrite+1 : 0;
    } else {    
      Serial.println("Buffer Overflow!");
      // Print count of events 
      uint8_t count = 0;
      for (int i = 0; i < I2C_EVENT_BUFFER_SIZE; i++) {
        if (eventBuffer[i].from != NONE) {
          count++;
        }
      }
      Serial.print("Event count: "); Serial.println(count);
    }
  }
}
bool getNextEventBufferWrite() {
  volatile uint8_t count = 0;  
  while (eventBuffer[eventBufferWrite].from != NONE) {
    eventBufferWrite = (eventBufferWrite < (I2C_EVENT_BUFFER_SIZE - 1)) ? eventBufferWrite+1 : 0;
    count++;
    if (count == (I2C_EVENT_BUFFER_SIZE-1)) return false;
  }
  return true;
}

/* I2C Services
-------------------------------------------------------------- */
void onReceiveFromMaster(int numBytes) { 
  if (0 < Wire.available()) {
    I2C_command = Wire.read();
  }
}
void onRequestFromMaster() {
  switch(I2C_command) {
    case GET_EVENTS:
      volatile uint8_t count = 0;
      // Send I2C_EVENT_BUFFER_SEND_COUNT events
      for (volatile uint8_t i = 0; i < I2C_EVENT_BUFFER_SIZE; i++) {
        if (i != eventBufferWrite && eventBuffer[i].from != NONE) {
          Wire.write(eventBuffer[i].from);
          Wire.write(eventBuffer[i].type);
          eventBuffer[i] = { NONE, NONE };
          count++;
          if (count == I2C_EVENT_BUFFER_SEND_COUNT) break;
        }
      }
      // Send the rest of the events
      for (volatile uint8_t i = count; i < I2C_EVENT_BUFFER_SEND_COUNT; i++) {
        Wire.write(NONE);
        Wire.write(NONE);
      }
      // Send the number of buttonsPressed
      Wire.write(buttonMatrix.buttonsPressed);
      I2C_command = NONE;
      break;
  }
}


How about using an I²C or a SPI 16 channel portexpander like MCP23017 I²C MCP23S17 SPI?

If I have to do communication between two microcontrollers I would always prefer a serial connection. Except a special version of a non-blocking I2C-library I2C is blocking. If a single signal-change does not happen I²C can hang waiting for this signal-change to come until you reset / restart new.

If you need hand-shaking you could implement this on the software-level.

best regards Stefan

If the Controller (Master) does this: Wire.requestFrom(SLAVE_ADDR, 2);
then it will receive 2 bytes, regardless how many or how little the Target (Slave) will send.
So it is better that the Target sends just two bytes.

A button has bounce. Do you have code to debounce it ? How many button-samples can the debounce output per second ? The I2C bus is a slow bus.

Can you make a Serial/UART communication between the Arduino boards instead ? That will be easier.

I'm using "Controller" and "Target" now, see: New I2C standard document by NXP : "Controller" and "Target"

Thanks for your answers, I will think about this if I will have more problems with it. But now it seems to work. Yes I have button debouncing. For me it is somehow difficult to imagine all scenarios which can happen when the slave is interrupted while scanning the inputs.
My core code is now:

// I2C Interrupts
static void SeqInterfaceController::_onReceiveFromMaster(int numBytes) {
  classToUse->_I2C_busy = true;
  if (0 < Wire.available()) {
    classToUse->_I2C_command = Wire.read();
  }
}
static void SeqInterfaceController::_onRequestFromMaster() {
  switch(classToUse->_I2C_command) {
    case SAY_HELLO:
      Wire.write(SAY_HELLO);
      classToUse->_I2C_command = NONE;
      break;
    case GET_EVENTS:
      volatile uint8_t count = 0;
      // Send I2C_EVENT_BUFFER_SEND_COUNT events
      for (volatile uint8_t i = 0; i < I2C_EVENT_BUFFER_SIZE; i++) {
        if (i != classToUse->_eventBufferWrite && classToUse->_eventBuffer[i].from != NONE) {
          Wire.write(classToUse->_eventBuffer[i].from);
          Wire.write(classToUse->_eventBuffer[i].type);
          classToUse->_eventBuffer[i] = { NONE, NONE };
          count++;
          if (count == I2C_EVENT_BUFFER_SEND_COUNT) break;
        }
      }
      // Send the rest of the events
      for (volatile uint8_t i = count; i < I2C_EVENT_BUFFER_SEND_COUNT; i++) {
        Wire.write(NONE);
        Wire.write(NONE);
      }
      // Send the number of buttonsPressed
      Wire.write(classToUse->_buttonMatrix.buttonsPressed);
      classToUse->_I2C_command = NONE;
      break;
  }
  classToUse->_I2C_busy = false;
}
// Event Buffer
void SeqInterfaceController::_addEventToBuffer() {
  if (this->_inputEvent.from != NONE) {
    // Report Buffer overflow
    if (getNextEventBufferWrite()) {
       // Set event to buffer    
      this->_eventBuffer[this->_eventBufferWrite] = this->_inputEvent;
      // Clear inputEvent 
      this->_inputEvent = { NONE, NONE };
      // Increase the Buffer Write Position
      this->_eventBufferWrite = (this->_eventBufferWrite < (I2C_EVENT_BUFFER_SIZE - 1)) ? this->_eventBufferWrite+1 : 0;
    } else {    
      Serial.println("Buffer Overflow!");
      // Print count of events 
      uint8_t count = 0;
      for (int i = 0; i < I2C_EVENT_BUFFER_SIZE; i++) {
        if (this->_eventBuffer[i].from != NONE) {
          count++;
        }
      }
      Serial.print("Event count: "); Serial.println(count);
    }
  }
}
bool SeqInterfaceController::getNextEventBufferWrite() {
  volatile uint8_t count = 0;  
  while (this->_eventBuffer[this->_eventBufferWrite].from != NONE) {
    this->_eventBufferWrite = (this->_eventBufferWrite < (I2C_EVENT_BUFFER_SIZE - 1)) ? this->_eventBufferWrite+1 : 0;
    count++;
    if (count == (I2C_EVENT_BUFFER_SIZE-1)) return false;
  }
  return true;
}
// Input scanning
void SeqInterfaceController::_checkInputs() {
  this->_clearInputEvent();
  this->_checkForEncoderEvents();
  this->_checkForButtonEvents();
  this->_checkForSwitchEvent();
}
void SeqInterfaceController::_checkForButtonEvents(bool init = false) {
  if (this->_inputEvent.from != NONE) return; // Only one Event at a time is possible
  
  if ((micros() - this->_buttonLastInputCheck) > this->_buttonDebounceTime || init == true) { // this one gets called more often
    this->_inputEvent = this->_translateEvent(this->_buttonMatrix.scan());
    this->_buttonLastInputCheck = micros();
  }
  if (this->_debug) {
    this->_debugInput(this->_inputEvent);
  }
  this->_updateStates();
  this->_addEventToBuffer();
}
void SeqInterfaceController::_checkForEncoderEvents() {
  if (this->_inputEvent.from != NONE) return; // Only one Event at a time is possible
  if ((micros() - this->_encoderLastInputCheck) > this->_encoderDebounceTime) { // this one gets called more often
    if (this->_encPos != this->_encPosLast) {
      this->_inputEvent.from = ENCODER;
      if (this->_encPos > 0) {
        this->_inputEvent.type = ENC_UP;
      } else {
        this->_inputEvent.type = ENC_DOWN;
      }
      this->_encPosLast = 0;
      this->_encPos = 0;
    }
    this->_encoderLastInputCheck = micros();
  }
  if (this->_debug) {
    this->_debugInput(this->_inputEvent);
  }
  this->_addEventToBuffer();

}
void SeqInterfaceController::_checkForSwitchEvent(bool init = false) {
  if (this->_inputEvent.from != NONE) return; // Only one Event at a time is possible
  if ((micros() - this->_switchLastInputCheck) > this->_switchDebounceTime) { // this one gets called more often
    if (this->_lastSwitchState != ((PINC & B00000100) >> 2) || init == true) {
      this->_lastSwitchState = ((PINC & B00000100) >> 2);
      this->_inputEvent.from = SWITCH;
      if (this->_lastSwitchState) {
        this->_inputEvent.type = SWITCH_TOGGLE_ON;
      } else {
        this->_inputEvent.type = SWITCH_TOGGLE_OFF;
      }
    }
    this->_switchLastInputCheck = micros();
  }
  if (this->_debug) {
    this->_debugInput(this->_inputEvent);
  }
  this->_updateStates();
  this->_addEventToBuffer();
}

If the Controller does this: Wire.requestFrom(SLAVE_ADDR, 2);
and the Target writes 10 bytes with Wire.write in the onRequest handler, then only the first 2 bytes are accepted and the remaining 8 bytes are thrown away.

I think that filling the buffer in a for-loop (in the onRequest handler) is not useful, if only 2 bytes are requested.