Host link: I2C¶
Not in the firmware yet
This page describes the I2C link ahead of the firmware, for review. UART and RS485 work now.
On I2C, the board is a target on the host's bus, like a sensor, at an address of its own (0x42 unless changed). It suits a host that already runs an I2C bus through the dome. It speaks native frames through a few registers, and pulls an interrupt line low when it has an event for the host.
This bus is separate from the board's own I2C bus, which serves its touch controller and IMU. Their addresses don't matter here.
Four wires¶
| Board | P2 pin | Host bus |
|---|---|---|
| GPIO21, SDA | 11 | SDA |
| GPIO33, SCL | 12 | SCL |
| GPIO15, IRQ | 7 | An input, with a pull-up (optional) |
| GND | 1 or 5 | GND |
Pull-ups¶
An I2C bus needs one pull-up resistor on SDA and one on SCL, to the bus voltage. The board has none on these pins, so the bus must have them somewhere: on the host, on another device, or added.
- 4.7 kΩ to 3.3 V for 100 kHz on short wires; 2.2 kΩ for longer ones, or 400 kHz.
- Only one set on the bus, or at least not so many in parallel that the total drops below about 1 kΩ.
3.3 V and 5 V¶
The board must never see 5 V on SDA or SCL. On a 5 V bus (an Arduino Uno, most MarcDuinos), put a bidirectional I2C level shifter between the bus and the board: a PCA9306, or a BSS138 module. Its 3.3 V side takes the board and 3.3 V pull-ups, and its 5 V side the host.
Arduino's Wire library pulls the bus up to 5 V
On an AVR, Wire.begin() turns on the chip's own pull-ups, to 5 V. They are weak, but enough
to put 5 V on a board wired straight to the bus. Always use a level shifter with a 5 V host.
Speed and length¶
- 100 kHz is the speed to use. 400 kHz should work on short wires, and will be confirmed when the I2C link is built.
- I2C is for short runs, a metre or so. Through a slip ring or across a whole droid, use RS485.
- Clock stretching. The board may hold SCL low briefly at the start of a read. Most hosts
wait for it. A Raspberry Pi's I2C hardware doesn't handle stretching reliably: on a Pi, use
the
i2c-gpiooverlay, a software bus that does.
The interrupt line¶
The board pulls IRQ low while it has events the host hasn't read, and lets it go when there are none. It's open drain: the host needs a pull-up on it, which a microcontroller's internal one usually covers. The IRQ lines of several boards can share one host input.
After an interrupt, the host reads each board's STATUS register, and reads EVENT from each
one with pending. A host that doesn't wire IRQ can read STATUS now and then instead.
Turning it on¶
link mode i2c -a 0x42
restart
Each board on a bus needs its own address, from 0x08 to 0x77, and one no other device on the bus uses.
Registers¶
| Register | Access | Holds |
|---|---|---|
0x00 STATUS |
read, 14 bytes | The protocol version, what the board is doing, and how many events are waiting |
0x10 COMMAND |
write | A command: apply a scene, set the LEDs, move the holo… |
0x11 RESULT |
read | The last command's result |
0x20 EVENT |
read, 8 bytes | The oldest event not yet read |
0x30 VERSION |
read, 32 bytes | The firmware version |
The protocol reference has their exact contents.
A first test, from an Arduino¶
Apply the scene in slot 3, and read the result:
#include <Wire.h>
const uint8_t HOLO = 0x42;
uint16_t crc16(const uint8_t *p, size_t n) { // CRC-16/CCITT-FALSE
uint16_t crc = 0xFFFF;
while (n--) {
crc ^= (uint16_t)*p++ << 8;
for (int i = 0; i < 8; i++) crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1;
}
return crc;
}
void setup() {
Serial.begin(115200);
Wire.begin();
uint8_t cmd[] = {0x10, /* seq */ 1, /* SCENE */ 0x10, /* slot */ 3, 0, 0};
uint16_t crc = crc16(cmd + 1, 3);
cmd[4] = crc & 0xFF;
cmd[5] = crc >> 8;
Wire.beginTransmission(HOLO);
Wire.write(cmd, sizeof(cmd));
Wire.endTransmission();
uint8_t status = 0xFE;
while (status == 0xFE) { // still working
delay(5);
Wire.beginTransmission(HOLO);
Wire.write(0x11); // RESULT
Wire.endTransmission(false); // repeated start
Wire.requestFrom(HOLO, (uint8_t)6);
Wire.read(); // seq
Wire.read(); // type + 0x80
status = Wire.read();
Wire.read(); // pending
Wire.read(); Wire.read(); // crc16
}
Serial.println(status == 0 ? "scene 3 applied" : "scene 3 failed");
}
void loop() {}
(The host above is 3.3 V, or behind a level shifter.)
From a Pi, i2cget -y 1 0x42 0x00 reads the first byte of STATUS, the protocol version, which
is 1. That's a quick way to check the wiring before writing any code.