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

Thread: I2C nightmare (Teensy 3 + MCP23017 + SSD1306 + tca9548)

  1. #1

    I2C nightmare (Teensy 3 + MCP23017 + SSD1306 + tca9548)

    Hi guys,

    I am really getting hopeless here… The project in a nutsheel:

    - 8 rotary encoders via a MCP2307 -> using interrups pins
    - 8 SSD1306 OLED screens (with all the same adresses) via TCA9548A I2C mux

    Click image for larger version. 

Name:	Screen Shot 2016-03-02 at 10.50.13.png 
Views:	1651 
Size:	47.4 KB 
ID:	6527

    The teensy + MCP2307 + TCA9548A are placed on a central “brain” board and the encoder + screens are on “module” board connected to the brain via a ribbon cable. Each encoder has 1 pin connected to the A port and the other pin to the B port. I am using interrupts for both ports. This gives me great performance for the encoders and everything is reading fine.

    UNTIL.... I am sending data to the SSD1306 screens, then everything is going wrong. It seems that data coming from the MCP2307 is then no longer accurate and is sometimes corrupted. As in : I am turning a certain encoder but the MCP is telling me that it got an interrupt on another inputs (than the encoder I am turning now). The result is that the code is not registering the pulses from the encoders proprely.

    I added a time delay to experiment and tried to send data to the screens ONLY 5/10/15 millisecs AFTER the last MCP2307 interrupt. This helps when rotating the encoder really fast but not when I turn it slowly. Switching between the different screens is easy as sending the right bit to a registers in the TCA9548A. Even when I don’t try to switch between and stick with 1 screen, it doesn’t get better.

    So far I tried:

    - Different values of pull up resistor on SDA and SCL close to the teensy: 10K, 4.7K, 470ohm. Making no real differences, without any resistors it was not working at all
    - Adding pull up resistors to each screen, as specified in the datasheet of the TCA9548 multiplexer : http://www.ti.com/lit/ds/symlink/tca9548a.pdf
    - Looking at the SCL signal on the scope, no real difference in the shape of the signal when I start to communicate with the SSD1306 screens
    - I tried the standard Wire library as well as the i2c_t3
    - I tried different i2c_t3 modes: DMA, interrupts, and the third one
    - Different speeds 100hz, 400hz
    - I tried shorter jumper wire/cables and it made no difference
    - I swapped the Adafruit SSD1306 library for another code but no improvements (http://robotcantalk.blogspot.nl/2015...06-driven.html)

    I am really stuck here, I am looking to purchase at a logic analyzer to debug the I2C better, but maybe someome has some pointers ? Anybody got experience with relatively cheap SSD1306 screens screwing things up ?

    By the way, altough the screens are screwing it up, the MCP23017 encoders code might be interesting.

    Thanks in advance!
    Nicolas.

    The code:

    Code:
    #include <Adafruit_GFX.h>
    #include <gfxfont.h>
    #include <Adafruit_SSD1306.h>
    #include <i2c_t3.h>
    
    //-----------------------//
    //VARTIABLES FOR OLEDs---//
    //-----------------------//
    #define TCAADDR1 0x70
    #define OLED_RESET -1
    
    Adafruit_SSD1306 display(OLED_RESET);
    
    int check_i2c = 0;
    
    int channel_sel = 0;
    
    unsigned long t = 0;
    int type, note, velocity, channel, d1, d2;
    
    int OLED_index[16] = {4, 5, 6, 7, 0, 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15};
    bool tca1 = 0;
    
    //-----------------------//
    //VARIABLES FOR MCP23017-//
    //-----------------------//
    // MCP23017 registers (everything except direction defaults to 0)
    #define IODIRA   0x00   // IO direction  (0 = output, 1 = input (Default))
    #define IODIRB   0x01
    #define IOPOLA   0x02   // IO polarity   (0 = normal, 1 = inverse)
    #define IOPOLB   0x03
    #define GPINTENA 0x04   // Interrupt on change (0 = disable, 1 = enable)
    #define GPINTENB 0x05
    #define DEFVALA  0x06   // Default comparison for interrupt on change (interrupts on opposite)
    #define DEFVALB  0x07
    #define INTCONA  0x08   // Interrupt control (0 = interrupt on change from previous, 1 = interrupt on change from DEFVAL)
    #define INTCONB  0x09
    #define IOCON    0x0A   // IO Configuration: bank/mirror/seqop/disslw/haen/odr/intpol/notimp
    //#define IOCON 0x0B  // same as 0x0A
    #define GPPUA    0x0C   // Pull-up resistor (0 = disabled, 1 = enabled)
    #define GPPUB    0x0D
    #define INFTFA   0x0E   // Interrupt flag (read only) : (0 = no interrupt, 1 = pin caused interrupt)
    #define INFTFB   0x0F
    #define INTCAPA  0x10   // Interrupt capture (read only) : value of GPIO at time of last interrupt
    #define INTCAPB  0x11
    #define GPIOA    0x12   // Port value. Write to change, read to obtain value
    #define GPIOB    0x13
    #define OLLATA   0x14   // Output latch. Write to latch output.
    #define OLLATB   0x15
    
    #define port 0x20  // MCP23017 is on I2C port 0x20
    
    #define INT_PINA 3    // pin 13
    #define INT_PINB 2    // pin 13
    
    volatile bool INTPortA = 0;
    volatile bool INTPortB = 0;
    
    int after_int = 0;
    int colors = 0;
    
    byte PORTA_flags = 0;
    byte PORTA_capture = 0;
    byte PORTA_pin_number = 0;
    bool PORTA_pin_state = 0;
    
    byte PORTB_flags = 0;
    byte PORTB_capture = 0;
    byte PORTB_pin_number = 0;
    bool PORTB_pin_state = 0;
    
    byte GP_NUM = 0;
    byte Enc_NUM = 0;
    byte screen_Enc_NUM = 0;
    
    byte GPA_INDEX[8] = {5, 4, 7, 6, 0, 1, 2, 3};
    byte GPB_INDEX[8] = {4, 5, 6, 7, 1, 0, 3, 2};
    
    byte GPA_NUM_INDEX[8] = {6, 7, 4, 5, 2, 3, 0, 1};
    byte GPB_NUM_INDEX[8] = {2, 3, 0, 1, 7, 6, 5, 4};
    
    bool EncA_READ[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    bool EncB_READ[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    bool EncA_PREV[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    bool EncB_PREV[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    
    long Enc_COUNT[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    long previous_Enc_COUNT[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    int Enc_DIR[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    
    elapsedMillis timer = 0;
    byte screen_refresh = 100;
    
    void setup()   {
    
      Serial.begin(9600);
    
      //Wire.begin();
      Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_400, I2C_OP_MODE_DMA);
    
      //------------------//
      //SETUP FOR OLEDs---//
      //------------------//
      pinMode(OLED_RESET, OUTPUT);
    
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3D (for the 128x64)
    
      display.setTextSize(3);
      display.setTextColor(WHITE);
      for (int i = 0; i < 8; i++) {
        OLEDselect(OLED_index[i]);
        display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x64)
        display.clearDisplay();
        display.setCursor(0, 0);
        display.println(i);
        display.display();
        delay(200);
        display.clearDisplay();
        display.display();
      }
    
      //----------------------------//
      //SETUP FOR MCP23017 ENCODERS-//
      //----------------------------//
      pinMode (INT_PINA, INPUT_PULLUP);  // for onboard LED
      pinMode (INT_PINB, INPUT_PULLUP);  // for onboard LED
    
      delay(100);
    
      attachInterrupt(INT_PINA, INTA, FALLING);
      attachInterrupt(INT_PINB, INTB, FALLING);
    
      // expander configuration register
      expanderWriteSingle (IOCON, 0b00100000); // disable sequential mode
    
      // enable pull-up on switches
      expanderWriteBoth (GPPUA, 0xFF);   // pull-up resistor for switch - both ports
    
      // invert polarity
      expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity of signal - both ports
    
      // set all GPIOS to inputs
      //expanderWriteBoth (IODIRA, 0xFF);
    
      //reset states
      expanderWriteBoth (IOPOLA, 0xFF);  // invert polarity of signal - both ports
    
      // enable all interrupts
      expanderWriteSingle (GPINTENA, 0xFF);
      expanderWriteSingle (GPINTENB, 0xFF);
    
      // confirm ints compared to previous values
      expanderWriteSingle (INTCONA, 0x00);
      expanderWriteSingle (INTCONB, 0x00);
    
      // read from interrupt capture ports to clear them
      expanderRead (INTCAPA);
      expanderRead (INTCAPB);
    }
    
    //----------------------------//
    //------MAIN LOOP-------------//
    //----------------------------//
    void loop() {
    
      //If interrupts on port A then find on which IO pin, read states of pin 1&2 of the specific encoder
      // then decode and count the steps
      if (INTPortA) {
        PORTA_flags = (expanderRead (INFTFA)); // Registers that tells you which input triggered the interrupt
        PORTA_capture = (expanderRead (INTCAPA)); //Registers that tells you the state of every input when interrupt occured (reading this clears all interrupts)
        for (int i = 0; i < 8; i++) {
          if (bitRead(PORTA_flags, i)) {
            GP_NUM = i;
            Enc_NUM = GPA_NUM_INDEX[GP_NUM];
            break;
          }
        }
    
        EncA_READ[Enc_NUM] = bitRead(PORTA_capture, GP_NUM);
        EncB_READ[Enc_NUM] = bitRead(expanderRead(GPIOB), GPA_INDEX[GP_NUM]);
    
        Enc_DIR[Enc_NUM] = decodingA(Enc_NUM);
        Enc_COUNT[Enc_NUM] = Enc_COUNT[Enc_NUM] + Enc_DIR[Enc_NUM];
    
        EncA_PREV[Enc_NUM] = EncA_READ[Enc_NUM];
        EncB_PREV[Enc_NUM] = EncB_READ[Enc_NUM];
    
        INTPortA = 0;
        after_int = millis();
      }
    
      if (INTPortB) {
        PORTB_flags = (expanderRead (INFTFB));
        PORTB_capture = (expanderRead (INTCAPB));
        for (int i = 0; i < 8; i++) {
          if (bitRead(PORTB_flags, i)) {
            GP_NUM = i;
            Enc_NUM = GPB_NUM_INDEX[GP_NUM];
            break;
          }
        }
    
        EncB_READ[Enc_NUM] = bitRead(PORTB_capture, GP_NUM);
        EncA_READ[Enc_NUM] = bitRead(expanderRead(GPIOA), GPB_INDEX[GP_NUM]);
    
        Enc_DIR[Enc_NUM] = decodingB(Enc_NUM);
        Enc_COUNT[Enc_NUM] = Enc_COUNT[Enc_NUM] + Enc_DIR[Enc_NUM];
    
        EncA_PREV[Enc_NUM] = EncA_READ[Enc_NUM];
        EncB_PREV[Enc_NUM] = EncB_READ[Enc_NUM];
    
        INTPortB = 0;
        after_int = millis();
      }
    
      //!!!When I put this in the code, encoders stop working proprely!!!!!
      
      if ((!INTPortA) && (!INTPortB)) { //This is skipping this if MCP2307 interrupt just happenede
        if ((millis() - after_int) > 10) { //this is waiting 10ms after the last MCP2307 interrupt
          for (int i = 0; i < 8; i++) {
            if (previous_Enc_COUNT[i] != Enc_COUNT[i]) {
              OLEDselect(OLED_index[i]);
              OLEDwrite(Enc_COUNT[i]);
              previous_Enc_COUNT[i] = Enc_COUNT[i];
            }
          }
        }
      }
    
    }
    
    
    //----------------------------//
    //------MCP FUNCTIONS --------//
    //----------------------------//
    // set register "reg" on expander to "data"
    // for example, IO direction
    void expanderWriteBoth (const byte reg, const byte data )
    {
      Wire.beginTransmission (port);
      Wire.write (reg);
      Wire.write (data);  // port A
      Wire.write (data);  // port B
      Wire.endTransmission ();
    }
    
    void expanderWriteSingle (const byte reg, const byte data )
    {
      Wire.beginTransmission (port);
      Wire.write (reg);
      Wire.write (data);  // port A
      Wire.endTransmission ();
    }
    
    // read a byte from the expander
    unsigned int expanderRead (const byte reg)
    {
      Wire.beginTransmission (port);
      Wire.write (reg);
      Wire.endTransmission ();
      Wire.requestFrom (port, 1);
      return Wire.read();
    }
    
    // interrupt service routine,
    void INTA () {
      INTPortA = 1;   // set flag so main loop knows
    }
    
    void INTB () {
      INTPortB = 1;   // set flag so main loop knows
    }
    
    int decodingA(uint8_t num) {
      if (EncA_PREV[num] && EncB_PREV[num]) {
        if (EncA_READ[num] && !EncB_READ[num]) return -1;
        if (!EncA_READ[num] && EncB_READ[num]) return 1;
        if (!EncA_READ[num] && !EncB_READ[num]) return 2;
      }
    
      if (!EncA_PREV[num] && EncB_PREV[num]) {
        if (!EncA_READ[num] && !EncB_READ[num]) return 1;
        if (EncA_READ[num] && EncB_READ[num]) return -1;
        if (EncA_READ[num] && !EncB_READ[num]) return -2;
      }
    
      if (!EncA_PREV[num] && !EncB_PREV[num]) {
        if (EncA_READ[num] && !EncB_READ[num]) return 1;
        if (!EncA_READ[num] && EncB_READ[num]) return -1;
        if (EncA_READ[num] && EncB_READ[num]) return 2;
      }
    
      if (EncA_PREV[num] && !EncB_PREV[num]) {
        if (EncA_READ[num] && EncB_READ[num]) return 1;
        if (!EncA_READ[num] && !EncB_READ[num]) return -1;
        if (!EncA_READ[num] && EncB_READ[num]) return -2;
      }
      return 0;
    }
    
    int decodingB(uint8_t num) {
      if (EncA_PREV[num] && EncB_PREV[num]) {
        if (EncA_READ[num] && !EncB_READ[num]) return -1;
        if (!EncA_READ[num] && EncB_READ[num]) return 1;
      }
    
      if (!EncA_PREV[num] && EncB_PREV[num]) {
        if (!EncA_READ[num] && !EncB_READ[num]) return 1;
        if (EncA_READ[num] && EncB_READ[num]) return -1;
      }
    
      if (!EncA_PREV[num] && !EncB_PREV[num]) {
        if (EncA_READ[num] && !EncB_READ[num]) return 1;
        if (!EncA_READ[num] && EncB_READ[num]) return -1;
      }
    
      if (EncA_PREV[num] && !EncB_PREV[num]) {
        if (EncA_READ[num] && EncB_READ[num]) return 1;
        if (!EncA_READ[num] && !EncB_READ[num]) return -1;
      }
      return 0;
    }
    
    //----------------------------//
    //------OLED FUNCTIONS -------//
    //----------------------------//
    void OLEDselect(uint8_t i) {
    
      Wire.beginTransmission(TCAADDR1);
      Wire.write(1 << i);
      Wire.endTransmission();
    }
    
    void OLEDwrite(String input) {
      display.clearDisplay();
      display.setCursor(0, 0);
      display.println(input);
      display.display();
    }

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,298
    Quote Originally Posted by nicolas_soundforce View Post
    UNTIL.... I am sending data to the SSD1306 screens, then everything is going wrong. It seems that data coming from the MCP2307 is then no longer accurate and is sometimes corrupted. As in : I am turning a certain encoder but the MCP is telling me that it got an interrupt on another inputs (than the encoder I am turning now). The result is that the code is not registering the pulses from the encoders proprely.
    As a first step to investigate, I'd try measuring the length of time the SSD1306 takes. Use an elapsedMicros variable, or call micros() before and after.

    Then I'd try replacing the screen update with a simple delay. The idea is to learn whether the SSD1306's communication is somehow interfering, or if the extra time spent not responding to the MCP23017 is causing the trouble.

  3. #3
    Quote Originally Posted by PaulStoffregen View Post
    As a first step to investigate, I'd try measuring the length of time the SSD1306 takes. Use an elapsedMicros variable, or call micros() before and after.

    Then I'd try replacing the screen update with a simple delay. The idea is to learn whether the SSD1306's communication is somehow interfering, or if the extra time spent not responding to the MCP23017 is causing the trouble.
    Thank you Paul, I'll try this right away and report my findings!

  4. #4
    Quote Originally Posted by PaulStoffregen View Post
    As a first step to investigate, I'd try measuring the length of time the SSD1306 takes. Use an elapsedMicros variable, or call micros() before and after.

    Then I'd try replacing the screen update with a simple delay. The idea is to learn whether the SSD1306's communication is somehow interfering, or if the extra time spent not responding to the MCP23017 is causing the trouble.
    The timing is : 109200 microseconds! This is what I get on the serial monitor with this code

    Code:
      elapsedMicros since_screen = 0;
      display.clearDisplay();
      display.setCursor(0, 0);
      display.println("test");
      display.display();
      Serial.println(since_screen);
    When I comment this line :
    Code:
    display.display();
    it goes down to 350microseconds

    And when I replace the SSD1306 code by a 109200 micro seconds delay, the encoder are badly and the behaviour is quite similar as when sending data to the SSD1306. Like this:

    Code:
    void loop() {
    
      //If interrupts on port A then find on which IO pin, read states of pin 1&2 of the specific encoder
      // then decode and count the steps
      if (INTPortA) {
        PORTA_flags = (expanderRead (INFTFA)); // Registers that tells you which input triggered the interrupt
        PORTA_capture = (expanderRead (INTCAPA)); //Registers that tells you the state of every input when interrupt occured (reading this clears all interrupts)
        for (int i = 0; i < 8; i++) {
          if (bitRead(PORTA_flags, i)) {
            GP_NUM = i;
            Enc_NUM = GPA_NUM_INDEX[GP_NUM];
            break;
          }
        }
    
        EncA_READ[Enc_NUM] = bitRead(PORTA_capture, GP_NUM);
        EncB_READ[Enc_NUM] = bitRead(expanderRead(GPIOB), GPA_INDEX[GP_NUM]);
    
        Enc_DIR[Enc_NUM] = decodingA(Enc_NUM);
        Enc_COUNT[Enc_NUM] = Enc_COUNT[Enc_NUM] + Enc_DIR[Enc_NUM];
    
        Serial.println(Enc_COUNT[Enc_NUM]);
    
        EncA_PREV[Enc_NUM] = EncA_READ[Enc_NUM];
        EncB_PREV[Enc_NUM] = EncB_READ[Enc_NUM];
    
        INTPortA = 0;
        after_int = millis();
      }
    
      if (INTPortB) {
        PORTB_flags = (expanderRead (INFTFB));
        PORTB_capture = (expanderRead (INTCAPB));
        for (int i = 0; i < 8; i++) {
          if (bitRead(PORTB_flags, i)) {
            GP_NUM = i;
            Enc_NUM = GPB_NUM_INDEX[GP_NUM];
            break;
          }
        }
    
        EncB_READ[Enc_NUM] = bitRead(PORTB_capture, GP_NUM);
        EncA_READ[Enc_NUM] = bitRead(expanderRead(GPIOA), GPB_INDEX[GP_NUM]);
    
        Enc_DIR[Enc_NUM] = decodingB(Enc_NUM);
        Enc_COUNT[Enc_NUM] = Enc_COUNT[Enc_NUM] + Enc_DIR[Enc_NUM];
    
        Serial.println(Enc_COUNT[Enc_NUM]);
    
        EncA_PREV[Enc_NUM] = EncA_READ[Enc_NUM];
        EncB_PREV[Enc_NUM] = EncB_READ[Enc_NUM];
    
        INTPortB = 0;
        after_int = millis();
      }
    
    
      //!!!When I put this in the code, encoders stop working proprely!!!!!
    
      if ((!INTPortA) && (!INTPortB)) { //This is skipping this if MCP2307 interrupt just happenede
        if ((millis() - after_int) > 5) { //this is waiting 10ms after the last MCP2307 interrupt
          elapsedMicros since_screen = 0;
          while (since_screen < 109200) {
          }
          //Serial.println(since_screen);
        }
      }
    }
    109206 seems like an exagerated amount of time to me, although I am not very experienced with I2C. What do you think ?

    EDIT: switching channels on the TCA9548A takes 200 micros. writing a register in the MCP23017 takes 388 micros.
    EDIT2: I tried in other sketches and also with another SSD1306 library and I am also getting resultat as 107200 micros.
    It seems related to the SSD1306 hardware then ?
    Last edited by nicolas_soundforce; 03-02-2016 at 12:06 PM.

  5. #5
    other interesting thing, according to this thread from 2012 : https://forums.adafruit.com/viewtopic.php?f=47&t=29918
    Updating a SSSD1306 via I2C should take 54ms (so 54000micros) on Leonardo at 8Mhz. Altough the I2c timing is still 400hz I could still expect shorter timings due to the Teensy running @ 96Mhz , right ?

  6. #6
    Making some progress! When I tried to use the Adafruit SSD1306 with the i2c_t3 library I commented the TWBR = 12; definition, in order to be able to compile it. I was using as start up code : Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_400, I2C_OP_MODE_DMA);

    Now I switched back to the Wire.h library and I am getting a new low timing of about 31400micros, to send 3 numbers. I got much better even though it's still a bit too long.

    Am I using the i2c_t3 library not correctly ? Do I have to add "Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_400, I2C_OP_MODE_DMA);" in the Adafruit SSD1306 .cpp file ?

    What are the effects of TWBR and TWSR when using Teensys and not AVR?

    Thanks again!

  7. #7
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,298
    Delete all those TWBR and TWSR.

    With the normal Wire library, you can use Wire.setClock(400000) or Wire.setClock(1000000).

  8. #8
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    21,298
    While faster SCL clock speeds will help, I believe you're probably going to have to restructure your main loop code.

    After detecting any change, you're updating all 8 displays. Even with fast clock speed, pushing so many pixels out to 8 displays is going to take a long time. During that time, you're not able to check for new changes on the encoders.

    The first step would involve 8 flags, one for each display, and you only update 1 display each time through loop. If all 8 need updates, you'll get the other 7 on the next passes through the loop.

    But even that may not be good enough, if the encoders are able to turn quickly. You might need to dig into the SSD1306 library code and divide the screen update into several pieces. Then add a variable that tracks where you are in updating the current display, so you do only a small piece on each pass through the loop.

    The key to all this is measuring the time taken. At some point, you'll get the time low enough that you're responding to the encoders fast enough.

    Another crazy approach would replace the MCP23017 with another processor, like Teensy-LC. It could run the regular encoder library, and transmit the data on a single serial pin at regular (reasonable) intervals. If you keep the message size small enough, like under 60 bytes, it will all be able to fit into the serial receive buffer if it arrives when you're busy updating those 8 displays. Then you can just use Serial1.available() and Serail1.read() to get the already decoded positions. That'd relieve the time-sensitive pressure to keep checking that MCP23017 so quickly.

  9. #9
    Quote Originally Posted by PaulStoffregen View Post
    Delete all those TWBR and TWSR.

    With the normal Wire library, you can use Wire.setClock(400000) or Wire.setClock(1000000).
    Fantastic. I did that, so removed TWBR definition from the Adafruit_SSD1306.cpp file. And added Wire.setClock(1000000);, AT THE VERY END of the setup (). When I placed it just after Wire.begin() at the beginning of my setup it didn't helped. I guess some definition somewhere in the setup were messing it up.

    At 400hz I get 30ms, slightly better than TWBR = 12; And with 1Mhz i get as low as 15ms!
    Even though the SSD1306 should be rated at max 400hz, it still works and it's great!

    Paul, thank you so much for the pointers and the guidance. You saved me.

  10. #10
    Quote Originally Posted by PaulStoffregen View Post
    While faster SCL clock speeds will help, I believe you're probably going to have to restructure your main loop code.

    After detecting any change, you're updating all 8 displays. Even with fast clock speed, pushing so many pixels out to 8 displays is going to take a long time. During that time, you're not able to check for new changes on the encoders.

    The first step would involve 8 flags, one for each display, and you only update 1 display each time through loop. If all 8 need updates, you'll get the other 7 on the next passes through the loop.

    But even that may not be good enough, if the encoders are able to turn quickly. You might need to dig into the SSD1306 library code and divide the screen update into several pieces. Then add a variable that tracks where you are in updating the current display, so you do only a small piece on each pass through the loop.

    The key to all this is measuring the time taken. At some point, you'll get the time low enough that you're responding to the encoders fast enough.

    Another crazy approach would replace the MCP23017 with another processor, like Teensy-LC. It could run the regular encoder library, and transmit the data on a single serial pin at regular (reasonable) intervals. If you keep the message size small enough, like under 60 bytes, it will all be able to fit into the serial receive buffer if it arrives when you're busy updating those 8 displays. Then you can just use Serial1.available() and Serail1.read() to get the already decoded positions. That'd relieve the time-sensitive pressure to keep checking that MCP23017 so quickly.
    Thanks for the new input. I modified the code to update the screen only when something changed. And things are going great, almost perfect.

  11. #11
    by updating only 1/4th of the screen at the same time, I got the timing down to 4ms. I added a another variable to the .display() function, a line variable, so I can choose where I want to start displaying, I also divided by 4 the amount of data being sent.

    changes in the library .ccp file

    Code:
    void Adafruit_SSD1306::displayline(uint16_t line_start) {
        ssd1306_command(SSD1306_COLUMNADDR);
        ssd1306_command(0);   // Column start address (0 = reset)
        ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)
        
        ssd1306_command(SSD1306_PAGEADDR);
        //this is changed!!!
        ssd1306_command(line_start); // Page start address (0 = reset)
    #if SSD1306_LCDHEIGHT == 64
        ssd1306_command(line_start+1); // Page end address
    #endif
    #if SSD1306_LCDHEIGHT == 32
        ssd1306_command(3); // Page end address
    #endif
    #if SSD1306_LCDHEIGHT == 16
        ssd1306_command(1); // Page end address
    #endif
        
        if (sid != -1)
        {
            // SPI
            *csport |= cspinmask;
            *dcport |= dcpinmask;
            *csport &= ~cspinmask;
            
            for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
                fastSPIwrite(buffer[i]);
                //ssd1306_data(buffer[i]);
            }
            *csport |= cspinmask;
        }
        else
        {
            // save I2C bitrate
    #ifndef __SAM3X8E__
            //uint8_t twbrbackup = TWBR;
            //TWBR = 12; // upgrade to 400KHz!
    #endif
            //Serial.println(TWBR, DEC);
            //Serial.println(TWSR & 0x3, DEC);
            
            // I2C
            // This is changed!!!
            for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/32); i++) {
                // send a bunch of data in one xmission
                Wire.beginTransmission(_i2caddr);
                WIRE_WRITE(0x40);
                for (uint8_t x=0; x<16; x++) {
                    WIRE_WRITE(buffer[i]);
                    i++;
                }
                i--;
                Wire.endTransmission();
            }
    #ifndef __SAM3X8E__
            // TWBR = twbrbackup;
    #endif
        }
    }
    my function to write to the screen:

    Code:
    void OLEDwrite(int line, String input) {
      display.clearDisplay();
      display.println(input);
      display.setCursor(0, 0);
      display.displayline(2 * line);
    }
    Last edited by nicolas_soundforce; 03-02-2016 at 09:17 PM.

Posting Permissions

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