Ruben Laguna's blog

Jan 16, 2014 - 4 minute read - microcontroller i2c teensy arduino atmel avr mcp23018

MCP23018 I2C communication advice

The past few pays I’ve been trying to get the MCP23018 working with the Teensy 2.0. I’m doing this to replace the firmware in my ErgoDox with my own since I can’t the the original ergodox-firmware to work. Finally I got the Teensy<->MCP23018 communication working so here is a summary of my experience.

Although my experience is in particular with Teensy 2.0 I guess it applies to any AVR / Atmel microcontroller (I guess including Arduino). First of all some advice with I2C in general and MCP23018 in particular:

  • NACKs : The last byte read from MCP23018 but be NACKed instead of ACKed. Otherwise the slave can continue to send another byte, miss the STOP condition and and weird behaviour will show up.
  • Speed bus: 100Khz or 400Khz. The speed you select has implication on the timing and delay that you have to insert in your code. 100Khz require higher delays so it’s more sentitive to timing issues in your code. 400Khz is a better choice, faster and less prone to this kind of errors.
  • Timing: There are some minimum time between certain operations that you must account for. I was naive enough to think that the TWI module would take care of that for me but it doesn’t.
    • tBUF: time to wait between a STOP condition and new START condition. You can’t just send those 2 in a row. You must wait at least 4.7us@100Khz or 1.3us@400Khz.
  • Don’t use REPEATED START condition, use STOP after a complete register read or register write

Troubleshooting I2C

When I started using I2C with the MCP23018 I just used repeated START. Since there was no other master on the bus, I thought I would be better to never release the bus. I thought communication was working fine since I was getting the correct TW_STATUS values. But the values that I was reading from the MCP23018 seemed wrong. So I decided to do two things:

  1. Every time I wrote a MCP23018 register and immediately read the register again to check the value
  2. Read and print all the MCP23018 registers

I quickly noticed that all the checks were showing incorrect results (although the TW_STATUS were ok) and that the reads were just returning the MCP23018 register address instead of the register data. In other words if I sent the 0x01 byte to select the IODIRB register then I was getting alwayx 0x01 as the register value.

So my first try was to use STOP conditions instead of the REPEATED START using the following:

void twi_stop(void) {
  TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); /* STOP condition */
  loop_until_bit_is_clear(TWCR, TWSTO);
  return;
}

and the program stuck usually at the second STOP, clearly the TWSTO flag was never cleared. That made me wonder about timing. So I re-read the MCP23018 datasheet and then I noticed the tBUF stuff and after googling a bit it seemed clear that I had to take into account this, I was assuming that the TWI module of the ATmel was taking care of these things for me but I was wrong. So then I went ahead and modified the code for the STOP condition like this:

#define TBUF   4.7   // 4.7us @ 100Khz // Bus free time between stop and start

void mcp23018_stopcond(void) {
  print("mcp23018 stopcond\n");
  twi_stop();
  _delay_us(TBUF);
}

Things got better, somehow. But still wasn’t enough, I was missing an important part: the NACK. If you read carefully the 20.8.2 Master Receiver Mode of ATMega32u4 datasheet it says that:

“After the last byte has been received, the MR should inform the ST by sending a NACK after the last received data byte.”

and it’s crucial since as explained in 3 :

“If the master ACKs the last byte and then attempts a STOP condition, the slave might put a 0 on the data line that blocks the STOP condition. If the master NACKs the last byte then the slave IC gives up and everybody exits cleanly.”

I fixed that and that’s it now I have a working communication with the MCP23018.

MCP23018 AVR Library

So at the end this is what I got, it contains some specific functions on using the MCP23018 on my specific project (using the MCP23018 of my ergodox) but mcp23018_read_register and mcp23018_write_register are usable on any project I believe

mpc23018.h

{% include_code lang:c mcp23018/mcp23018.h %}

mpc23018.c

{% include_code mcp23018/mcp23018.c %}