i2c betwee two 4.0s: Do I need pull up resistors?

DrGarbisk

Member
I am making a split keyboard with a pair of 4.0s and I am unable to get a basic master read sample working. Do I need pull up resistors for this scenario? I'm using a common ground and pins 18 and 19. Any tips would be appreciated.

slave code:
Code:
#include <Arduino.h>
#include <i2c_driver.h>
#include <i2c_driver_wire.h>
#include <i2c_register_slave.h>
void requestEvent();

int led = LED_BUILTIN;

void setup()
{
  Serial.begin(9600);
  pinMode(led, OUTPUT);
  Wire.begin(8);        // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
}

void loop(){
  Serial.println("hey");

  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  digitalWrite(led, HIGH);      // briefly flash the LED
  Wire.write("hello ");     // respond with message of 6 bytes
                                // as expected by master
  digitalWrite(led, LOW);
}

master code:
Code:
#include <i2c_driver.h>
#include <i2c_driver_wire.h>
#include <i2c_device.h>
void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop() {
  Wire.requestFrom(8, 6);    // request 6 bytes from peripheral device #8

  while (Wire.available()) { // peripheral may send less than requested
    char c = Wire.read(); // receive a byte as character
    Serial.print(c);         // print the character
  }
Serial.println("hey");
  delay(500);
}
 
Yes, the i2c interface cant work without Pull-Up's. It's an Open-Drain bus structure
It's due to the fact, no output will ever pull the given wire (SCL/SDA) HIGH, only LOW, so to be able to ever go HIGH a pull up is required.
@3.3V 100-400Khz and short wires 4K7 resistors is fine, but if You want to go to 1MHz, you will need to lower the resistors to 1K8 - 2K2 Ohm

https://en.wikipedia.org/wiki/I²C
 
You really should use real pullup resistors, between 1K to 4.7K. But if you omit them, if using the Wire library on Teensy 4.0, the weak internal pullup resistors are activated. They result in a weak / slow pullup with is far from ideal, but usually works if the wires aren't long.

Not every Teensy model turns on its internal pullups when using I2C. Teensy 3.2, 3.5, 3.6 in particular do not. With those you must have a real pullup resistor. You can pretty easily check with a voltmeter. If the SDA or SCL signal isn't 3.3V when idle, that's a sure sign you need to add a real resistor.

Any tips would be appreciated.

First check which software version you have installed. If older than 1.57, update. Old versions only supported I2C master mode. In Arduino 1.8.x, click Help > About. In Arduino 2.0.x, click the Boards Manager and search for "Teensy".

Your code is including i2c_driver.h and i2c_driver_wire.h. Maybe this library really supports Teensy, or maybe not, or maybe only in master mode? I don't know, as I'm not familiar with that particular library. But I do know for sure that Wire works. The master_reader and slave_sender examples definite do work. Maybe open those from File > Examples > Wire. If you really need that other library, just for the sake of testing you could run these known-good examples to make sure everything is really working first.
 
I think I have a capacitance problem (or something similar). I got my i2c bus working between that two keyboard halves without pull up resistors. However, whenever I press a key in the 5th or 6th column to be read i get the timeout most of the time. Adding 2K pull up resistors has no effect. The interesting part is that it is independent of the column. If I reorder the column pins in the array same result. The last two columns in the array of IO pins are the only ones to have this problem.

Here is the code I'm using to read the key pins

Code:
//################### keyboard #########################


byte rows[] = { 23, 22, 21, 20, 15, 14, 9 };
const int rowCount = sizeof(rows) / sizeof(rows[0]);

byte cols[] = { 5, 4, 3, 2, 1, 0 };
const int colCount = sizeof(cols) / sizeof(cols[0]);

byte keyStates[colCount][rowCount];


void setupKeys() {

  initKeyStates();

  Keyboard.begin();
  for (int x = 0; x < rowCount; x++) {
    pinMode(rows[x], INPUT_DISABLE);
  }
  for (int x = 0; x < colCount; x++) {
    pinMode(cols[x], INPUT_DISABLE);
    //digitalWrite(cols[x], LOW);
  }
}

bool readKeys(int keyBufSize, byte* keysBuffer) {
  int keyIdx = 0;
  bool keyRead = false;
  for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
    byte row = rows[rowIdx];
    pinMode(row, INPUT_PULLDOWN);
    for (int colIdx = 0; colIdx < colCount; colIdx++) {
      byte col = cols[colIdx];
      pinMode(col, OUTPUT);
      digitalWrite(col, HIGH);
      delayMicroseconds(5);
      byte btnState = digitalRead(row);
      digitalWrite(col, LOW);
      pinMode(col, INPUT_DISABLE);

      if (keyStates[colIdx][rowIdx] != btnState) {  // check if button state changed
        keyRead = true;
        if (keyIdx >= keyBufSize) {  // Only so many button presses can be tracked and sent at one time via i2c
          println("Key buffer full");
          return true;  // buffer full
        }
        // col row nonce state
        // 111 111 1     1
        byte state = colIdx;
        state = state << 3;
        state += rowIdx;
        state = state << 2;
        state += 2;  // flip the empty bit as a nonce so we can distinguish between no value and 0,0 released
        state += btnState;
        keysBuffer[keyIdx++] = state;
      }

      keyStates[colIdx][rowIdx] = btnState;
    }
    pinMode(row, INPUT_DISABLE);
  }

  return keyRead;
}


void initKeyStates() {
  for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
    for (int colIdx = 0; colIdx < colCount; colIdx++) {
      keyStates[colIdx][rowIdx] = 0;
    }
  }
}
 
Last edited:
Here is the code I'm using to read the key pins

I don't understand why it seems to read only 1 row after driving 1 column high? Also don't understand why you're changing the pinMode on rows?

A more traditional approach would be the leave the rows always configured as inputs with weak pull resistors. Then configure 1 column as active output drive and all the other columns as tri-state, wait the signal settling time (ideally longer than only 5 microseconds) and then read the result on all the rows. Repeat for driving each column with all the others high impedance. You can allow longer settling time and still achieve better overall performance if you read all the rows.
 
I give that a try. This code does work reliably to read the keys on both halves when they are the primary. This issue only occurs when reading from the secondary via i2c. Which is the odd part.

Wha does "tri-state" mean for a digital pin?

Thanks for the help so far.
 
So I get the pattern you recommended a try and it had no effect. I did work around the problem by not scanning the keys during the event handler. Not sure I like that though. I may switch to a pattern where the slave writes to the master.
 
I did work around the problem by not scanning the keys during the event handler.

Glad you found a solution.

Sounds like there may be some sort of unanticipated interaction with the rest of your program (which we can't see) if running the same function from different context causes it to work differently.
 
I think I'd need a scope hooked up to the i2c circuit to really figure this out.

Now I'm on to a new problem around Keyboard.press() notworking for keys like backspace and ctrl. But I'll start a new post for that. Thanks for the input on this one!
 
Back
Top