Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 4 of 4

Thread: strange i2c issues with Teensy LC in single-master, multi-slave setup

Threaded View

  1. #1
    Junior Member even_rats's Avatar
    Join Date
    Jul 2020
    Location
    The Road
    Posts
    12

    strange i2c issues with Teensy LC in single-master, multi-slave setup

    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.

    Click image for larger version. 

Name:	i2c_q2.jpg 
Views:	31 
Size:	148.1 KB 
ID:	24242

    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 by even_rats; 03-30-2021 at 02:35 AM. Reason: clarification

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •