even_rats
Member
I'm prototyping a MIDI controller with an ESP32 board as the main MCU, but also using a Teensy LC for its easy usbMIDI capabilities. And there's an MCP23017 i2c i/o expander that's used to control a bank of LEDs.
These 3 components talk to each other over an i2c bus, with the ESP32 board (SparkFun Thing Plus) acting as master, and the Teensy LC and MCP23017 in slave mode. The Teensy LC and MCP23017 are assigned different i2c addresses (0x33 and 0x20 respectively), and there are 3k pullup resistors to Vcc (3.3v) on each i2c line near each slave device.
For current communication-testing purposes:
These 2 things each work perfectly one at a time. But when I try them at the same time, they both stop working. With both communication tasks going, the LEDs blink correctly, and everything works when I push the MIDI test button once. But when I press it a second time, the LEDs stop blinking, and no further usbMIDI messages are sent.
Code running on the esp32 and teensyLC, respectively:
If I look at the log printed to Serial from the Teensy in this failure case, it appears to be incorrectly "receiving" the same 5-byte message over and over again, and the extra incorrect messages correspond temporally to each time the LEDs are supposed to blink (once per second). But with 2 extra null bytes appended each time. (It should be noted that the i2c messages to the MCP23017 are exactly 2 bytes long... hmm) So it seems like some wires are getting crossed somewhere, such that either the message is being incorrectly concatenated and retransmitted, or not being cleared from some queue on the receiving end. But I'm not sure of how to further debug.
Here's a log from when the MIDI button is working correctly, without the LED communication going. All looks good. (170 == 0xAA which is the magic start byte, the rest correspond to the MIDI values I'm sending as a test):
And here's a log from the failure mode when it crashes with the MCP23017:
Thanks for reading. Any hints or suggestions are appreciated!
These 3 components talk to each other over an i2c bus, with the ESP32 board (SparkFun Thing Plus) acting as master, and the Teensy LC and MCP23017 in slave mode. The Teensy LC and MCP23017 are assigned different i2c addresses (0x33 and 0x20 respectively), and there are 3k pullup resistors to Vcc (3.3v) on each i2c line near each slave device.
For current communication-testing purposes:
- once every second, the esp32 master sends commands via i2c to the MCP23017 to blink the LEDs to count in binary. (simplest way to test because it uses a register based control scheme)
- the esp32 master waits for a button press, and when it detects one, sends data via i2c to the Teensy LC representing a MIDI note on/note off. Each keypress is sent as a 5 byte message starting with a magic number byte (0xAA). The Teensy interprets and forwards these messages via usbMIDI.
These 2 things each work perfectly one at a time. But when I try them at the same time, they both stop working. With both communication tasks going, the LEDs blink correctly, and everything works when I push the MIDI test button once. But when I press it a second time, the LEDs stop blinking, and no further usbMIDI messages are sent.
Code running on the esp32 and teensyLC, respectively:
Code:
// esp32_i2c_test.ino
// i2c master - runs on esp32 board
#include <Bounce2.h>
#include <Wire.h>
#define TEENSYLC_ADDR 0x33
#define MCP23017_ADDR 0x20
#define MSG_SEPARATOR 0xAA
#define MIDI_TYPE_CC 0x20
#define MIDI_TYPE_PC 0x21
#define MIDI_TYPE_NOTEON 0x22
#define MIDI_TYPE_NOTEOFF 0x23
#define BUTTON_PIN 14
uint8_t counter = 0;
unsigned long start_time, current_time;
Bounce button;
void setup() {
Serial.begin(9600);
Wire.begin();
// setup MCP23017 as all outputs
Wire.beginTransmission(MCP23017_ADDR);
Wire.write(0x00); // IODIRA register
Wire.write(0x00); // set all of port A to outputs
Wire.endTransmission();
Wire.beginTransmission(MCP23017_ADDR);
Wire.write(0x01); // IODIRB register
Wire.write(0x00); // set all of port B to outputs
Wire.endTransmission();
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
button = Bounce(BUTTON_PIN, 10);
start_time = millis();
}
void loop() {
// communicate with 2 different i2c slaves
// both of the following work correctly one at a time.
// but when I attempt both, it locks up
// blink LEDs using MCP23017 via i2c
update_LEDs();
// check for button press and send resulting data to Teensy LC via i2c
check_midi_button();
}
void update_LEDs() {
// blink LEDs using MCP23017 via i2c
current_time = millis();
if(current_time - start_time >= 1000) { // update LEDs once per second (1000ms)
// a simple test that causes the connected LEDs connected to count in binary
Wire.beginTransmission(MCP23017_ADDR);
Wire.write(0x12); // address port A
Wire.write(counter++); // value to send
Wire.endTransmission();
start_time = current_time;
}
}
void check_midi_button() {
// handle button press and send resulting data to Teensy LC via i2c
if(button.update()) {
if(button.fallingEdge()) {
// to test, send a middle C with velocity 100 on channel 3
i2c_send_noteOn(3, 60, 100);
digitalWrite(LED_BUILTIN, HIGH);
} else if(button.risingEdge()) {
// .. and the corresponding note off message
i2c_send_noteOff(3, 60, 0);
digitalWrite(LED_BUILTIN, LOW);
}
}
}
void i2c_send_noteOn(unsigned int channel, unsigned int note, unsigned int val) {
Serial.print("Sending note on: ");
Serial.println(note);
Wire.beginTransmission(TEENSYLC_ADDR);
Wire.write(MSG_SEPARATOR);
Wire.write(MIDI_TYPE_NOTEON);
Wire.write(channel);
Wire.write(note);
Wire.write(val);
Wire.endTransmission();
}
void i2c_send_noteOff(unsigned int channel, unsigned int note, unsigned int val) {
Serial.print("Sending note off: ");
Serial.println(note);
Wire.beginTransmission(TEENSYLC_ADDR);
Wire.write(MSG_SEPARATOR);
Wire.write(MIDI_TYPE_NOTEOFF);
Wire.write(channel);
Wire.write(note);
Wire.write(val);
Wire.endTransmission();
}
Code:
// teensylc_i2c_test.ino
// i2c slave - runs on Teensy LC
#include <Wire.h>
#include <queue>
#define TEENSYLC_ADDR 0x33
#define MSG_SEPARATOR 0xAA
#define MIDI_TYPE_CC 0x20
#define MIDI_TYPE_PC 0x21
#define MIDI_TYPE_NOTEON 0x22
#define MIDI_TYPE_NOTEOFF 0x23
// This sketch receives messages over i2c representing MIDI data, and forwards them over usbMIDI.
// Message structure over i2c is 5 bytes starting with message separator byte (0xAA)
// order: SEPARATOR, MIDI_MSG_TYPE, CHANNEL, VAL1, VAL2
// for example, Note On for middle C (60) at velocity 100 on channel 3 would be:
// 0xAA, 0x22, 3, 60, 100
uint8_t counter;
std::queue<byte> i2c_buffer;
byte this_byte, msg_type, channel, val1, val2;
uint8_t msg_byte_counter; // keep track of how many bytes of the current message we've received, from 0 to 5
void setup() {
// set up i2c pins on 18 & 19
pinMode(18, INPUT);
pinMode(19, INPUT);
Wire.setSDA(18);
Wire.setSCL(19);
// start Wire library in slave mode
Wire.begin(TEENSYLC_ADDR);
Wire.onReceive(read_i2c); // register receive handler
Serial.begin(9600);
// we haven't received any bytes of the first message yet
msg_byte_counter = 0;
}
void read_i2c(int numBytes) {
int numBytesRead = 0;
byte tmp;
Serial.printf("onReceive handler called with %d bytes\n", numBytes);
// read as many bytes as available into queue
while(numBytesRead < numBytes) {
tmp = Wire.read();
Serial.printf("\tread byte value: %d\n", tmp);
i2c_buffer.push(tmp);
numBytesRead++;
}
if(numBytesRead>0) {
Serial.printf("Received %d bytes over i2c\n", numBytesRead);
}
// process received bytes
// this relies on int msg_byte_counter, which keeps track of which byte we're on in current message
while (!i2c_buffer.empty()) {
this_byte = i2c_buffer.front();
if(this_byte == MSG_SEPARATOR) { // start of new message
i2c_buffer.pop(); // discard separator
msg_byte_counter = 1;
} else if(msg_byte_counter == 0) { // no message in progress, and no separator
i2c_buffer.pop(); // discard
} else {
switch(msg_byte_counter) {
case 1: // already have separator
msg_type = this_byte;
msg_byte_counter++;
break;
case 2: // already have msg_type
channel = this_byte;
msg_byte_counter++;
break;
case 3: // already have channel
val1 = this_byte;
msg_byte_counter++;
break;
case 4: // already have val1
val2 = this_byte;
msg_byte_counter++;
break;
default: // shouldn't happen
msg_byte_counter = 0;
break;
}
i2c_buffer.pop(); // discard processed byte
}
}
if(msg_byte_counter >= 5) { // we have a full message
if(msg_type == MIDI_TYPE_CC) {
usbMIDI.sendControlChange((uint8_t) val1, (uint8_t) val2, (uint8_t) channel);
} else if(msg_type == MIDI_TYPE_PC) {
usbMIDI.sendProgramChange((uint8_t) val1, (uint8_t) channel);
} else if(msg_type == MIDI_TYPE_NOTEON) {
usbMIDI.sendNoteOn((uint8_t) val1, (uint8_t) val2, (uint8_t) channel);
} else if(msg_type == MIDI_TYPE_NOTEOFF) {
usbMIDI.sendNoteOff((uint8_t) val1, (uint8_t) val2, (uint8_t) channel);
}
msg_byte_counter = 0; // reset byte counter
}
}
void loop() {
;
}
If I look at the log printed to Serial from the Teensy in this failure case, it appears to be incorrectly "receiving" the same 5-byte message over and over again, and the extra incorrect messages correspond temporally to each time the LEDs are supposed to blink (once per second). But with 2 extra null bytes appended each time. (It should be noted that the i2c messages to the MCP23017 are exactly 2 bytes long... hmm) So it seems like some wires are getting crossed somewhere, such that either the message is being incorrectly concatenated and retransmitted, or not being cleared from some queue on the receiving end. But I'm not sure of how to further debug.
Here's a log from when the MIDI button is working correctly, without the LED communication going. All looks good. (170 == 0xAA which is the magic start byte, the rest correspond to the MIDI values I'm sending as a test):
Code:
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 34
read byte value: 3
read byte value: 60
read byte value: 100
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 34
read byte value: 3
read byte value: 60
read byte value: 100
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 34
read byte value: 3
read byte value: 60
read byte value: 100
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
Received 5 bytes over i2c
And here's a log from the failure mode when it crashes with the MCP23017:
Code:
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 34
read byte value: 3
read byte value: 60
read byte value: 100
Received 5 bytes over i2c
onReceive handler called with 5 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
Received 5 bytes over i2c
onReceive handler called with 7 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
Received 7 bytes over i2c
onReceive handler called with 9 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 9 bytes over i2c
onReceive handler called with 11 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 11 bytes over i2c
onReceive handler called with 13 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 13 bytes over i2c
onReceive handler called with 15 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 15 bytes over i2c
onReceive handler called with 17 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 17 bytes over i2c
onReceive handler called with 19 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 19 bytes over i2c
onReceive handler called with 21 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 21 bytes over i2c
onReceive handler called with 23 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 23 bytes over i2c
onReceive handler called with 25 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 25 bytes over i2c
onReceive handler called with 27 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 27 bytes over i2c
onReceive handler called with 29 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 29 bytes over i2c
onReceive handler called with 31 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 31 bytes over i2c
onReceive handler called with 32 bytes
read byte value: 170
read byte value: 35
read byte value: 3
read byte value: 60
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
read byte value: 0
Received 32 bytes over i2c
Thanks for reading. Any hints or suggestions are appreciated!
Last edited: