nicolas_soundforce
Active member
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
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/03/interfacing-arduino-with-ssd1306-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:
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
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/03/interfacing-arduino-with-ssd1306-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();
}