Two wires, several chips

The MPU6050, the DS3231 and the TM1637 all live on two wires. Two of them speak I2C and one of them does not.

That difference is worth an afternoon, and so is the difference between a bus and a register map.

I2C is the bus a beginner meets second, after "a pin is high or low". It is two wires for any number of devices, each with an address, and it is what almost every interesting module in a kit uses. What follows is what the three modules on this bench actually do with it, and the four or five things that go wrong.

The shape of a transfer

Both lines idle high, held there by pull-up resistors, and every breakout board in the parts bin carries its own pair, which is why an OLED, a clock or an IMU works wired straight to two pins with nothing else on the breadboard.

A master talks by pulling them down:

  START      SDA falls while SCL is high
  address    seven bits and a read/write bit, most significant first
  ACK        the addressed device pulls SDA down for the ninth clock
  bytes      eight bits and an acknowledge, over and over
  STOP       SDA rises while SCL is high

That is the whole protocol. Everything above it (registers, pointers, the order bytes go in) is the chip's business, not the bus's.

Wire.h

A tutorial's Wire calls work as they are. Wire.begin() starts the bus on the board's own I2C pins (A4 and A5 on an Uno, the others in each board's help article), Wire.begin(sda, scl) on any two, and a register read is the familiar four lines:

#include <Wire.h>

void setup() {
  Serial.begin(9600);
  Wire.begin();
  Wire.beginTransmission(0x68);
  Wire.write(0x75);                 // WHO_AM_I
  Wire.endTransmission(false);      // no STOP: a repeated START follows
  Wire.requestFrom(0x68, 1);
  Serial.println(Wire.read(), HEX); // 68
}

void loop() {}

Wire.h is Mokxi's own header on mokxi_i2c.h, so it is a bit-banged master like everything else here, with the stock library's 32-byte buffers and its endTransmission codes: 0 for success, 2 for an address nobody answered, 3 for a data byte refused. An I2C scanner finds every module on the bus. What it does not do is be a slave: Wire.begin(address) stops the build with a message, because nothing in Mokxi can be the master of a board.

Reading a register file

Both of the I2C parts here are register files, and they are read the same way:

  S  addr+W  A  reg  A  Sr  addr+R  A  data A  data A ... data N  P

Write the pointer, then a repeated START (a second START without a STOP in between, which keeps the bus and stops another master butting in), then read. The pointer steps on after every byte, so a whole block comes out in one transfer. firmware/lib/mokxi_i2c.h has this as readRegisters().

That is not a nicety. Read a clock's seven time registers one at a time and it can tick between the minutes and the seconds, and 10:00:59 comes out as 10:01:59, once an hour, for ever.

The MPU6050, and the two things everybody hits

A three-axis accelerometer and a three-axis gyroscope in one chip, on the little purple GY-521 board. The bus part is easy; the register map is the work.

It powers up asleep. PWR_MGMT_1 (0x6B) reads 0x40 after reset (the SLEEP bit), and until it is cleared every measurement register reads zero. No error, no complaint: a perfectly steady nothing, which is worse than a crash. Every driver's begin() writes 0 there first.

WHO_AM_I is 0x68 whatever the address is. The register holds bits 6:1 of the address, and AD0 is bit 0. So a board strapped to 0x69 still identifies as 0x68, and a driver that compares WHO_AM_I against the address it is using fails on exactly the board somebody had to strap.

After that it is arithmetic. ACCEL_CONFIG bits 4:3 and GYRO_CONFIG bits 4:3 pick the ranges, and the counts per unit are on the datasheet:

bits accelerometer counts per g bits gyroscope counts per deg/s
0 +/-2 g 16384 0 +/-250 deg/s 131
1 +/-4 g 8192 1 +/-500 deg/s 65.5
2 +/-8 g 4096 2 +/-1000 deg/s 32.8
3 +/-16 g 2048 3 +/-2000 deg/s 16.4

Every reading is a signed 16-bit number, high byte first, and it saturates at the end of the scale rather than wrapping, which is the whole argument for the wider ranges.

Gravity is the trick

An accelerometer at rest measures the reaction to gravity and nothing else. Flat on the bench that is (0, 0, +1 g); tip it and gravity moves onto the other two axes, and the tilt is the arctangent of two of the three. That is why a part that measures acceleration is what tells you which way up something is while it is standing perfectly still.

Drag the board on the canvas and it tips; the readings follow. The gyroscope is the other half and behaves quite differently: it reads how fast the board is moving, so it sits at zero until you drag and falls back the moment you stop.

What this model does not do

No DMP and no FIFO: the motion processor's firmware is not modeled, so a library that uses it gets nothing rather than something wrong. The auxiliary I2C master on XDA and XCL is two pins that do nothing. GYRO_ZOUT is always zero, because there is nothing on the canvas that would turn the board about the axis pointing at you. And this chip is perfectly calibrated, where a real one needs its zero-rate offsets measured and subtracted before it is any use, so a sketch written here needs that work adding before it meets hardware.

The DS3231, and BCD

A temperature-compensated crystal oscillator, which is why it is the clock everybody ends up buying: a DS1307 drifts minutes a month and this one drifts about that much a year. Address 0x68, fixed. There is no address pin.

Everything in it is binary coded decimal. Each byte holds two decimal digits, one per nibble, so twenty-five seconds is 0x25 and not 25:

  value = (byte >> 4) * 10 + (byte & 0x0f)

Forget it and 37 minutes past shows as 55. It is the one thing everybody gets wrong once.

The other trap is the hours register. Bit 6 is the 12-hour flag, and with it set bits 4:0 are 1 to 12 and bit 5 is PM, so 3 pm is 0x63, and a driver that masks with 0x3f instead of 0x1f reads it as 23.

The part keeps a real calendar with leap years, both alarms with the whole of the datasheet's mask table, and SQW as either the alarm output or a square wave at 1 Hz, 1.024 kHz, 4.096 kHz or 8.192 kHz. OSF (bit 7 of the status register) comes up set and means "this time cannot be trusted", which on a real bench is how you find out somebody's coin cell is flat.

Two things are not modeled and are said plainly here: the 32 kHz output, because generating it would be 65,536 edges a simulated second for something nothing in the parts bin can use; and the battery, so a part that loses VCC loses the time where a real module keeps it.

Both of them are 0x68

The MPU6050 and the DS3231 answer to the same address. Two of them on one bus is one of the few genuine address clashes a kit can produce, and the fix is the AD0 pin: strap it high and the IMU moves to 0x69. It is worth doing once on purpose.

The TM1637, which is not I2C

The four-digit display looks exactly like an I2C device (the same START, the same STOP, an acknowledge on a ninth clock), and it is not, in two ways that break every attempt to drive it with a Wire library:

  • There is no address. The first byte after a START is a command, so the chip answers everything on the wire. Two of them share a pair of pins and show the same thing.
  • Bytes go least significant bit first. I2C is most significant first. Get it the wrong way around and the display lights a plausible-looking set of wrong segments, which is a hard bug to see.

The three transfers a driver sends are 0x40 (write display data, auto-incrementing), 0xC0 followed by four segment bytes, and 0x80 | 0x08 | brightness. One byte a digit, bit 0 is segment A through bit 6 for G, and bit 7 is the colon, on the second digit only, which is the module's wiring rather than the chip's, and is why every library has a special case for it.

The eight brightness steps are pulse widths out of sixteen: 1, 2, 4, 10, 11, 12, 13, 14. They are not evenly spaced, which is why level 3 is such a jump.

Circuits to open

  • Tilt meter: an MPU6050 and three lamps. Drag the board and watch the registers follow gravity.
  • Desk clock: a DS3231 and a TM1637 side by side, which is the whole point of the pairing: two two-wire devices on two different two-wire buses.
  • Thermometer on an I2C LCD: the third kind of I2C device, a port expander with no register file at all.

What is not modeled on the bus itself

Clock stretching, which none of these three chips does anyway. Arbitration between two masters. Ten-bit addressing. And the rate: the parts decode the bus from its edges and impose no timing of their own, so a driver that is too fast works here where a real device would not answer.

Arduino I2C LCD not working uses a scanner and Wire.endTransmission() to tell a wiring fault from a wrong address.