(Solved) I2C Display missing sent letters

I'm working on a project that's going to have quite a few inputs, so I've been attempting to use an I2C LCD display and I2C port expander to help reduce the pin count. Datasheets for both devices are attached. I am using an Arduino UNO.

The wiring is fairly simple. A5 and A4 are connected to SCL and SDA of the port expander, which is then daisy-chained to the LCD display's SCL and SDA. All devices are powered by the UNO's 5V pin. A0-A2 are grounded on the port expander, RESET is tied high. INT goes to pin 2. The display has a jumper wire soldered onto R1, as required in the datahseet.

I have my sketch set up with two buttons attached to the port expander; one button clears the screen and prints a configuration menu, the other clears the screen and prints a different menu.

The problem I am having is that when I print the menus, letters are missing, and not always the same ones. This becomes particularly obvious when I press the same button repeatedly, causing the same menu to be printed. I can see the missing letters change.

I have two 10k resistors on each line as a pullup, giving 5k total.

I don't seem to have any problems at all reading the port expander, just printing to the display. I've tried adding delays after the endTransmission(), but it doesn't seem to help.

Frequency_Generator.ino

#include <SPI.h>
#include <Wire.h>
#include <avr/eeprom.h>
#include "NHD_I2C_Display.h"
#include "MCP23008.h"
#include "structs.h"

config_t configuration = { 4, 40, 2048 };
config_t old_config = configuration;

// ***************************************************
// EEPROM indexes and pointers
// ***************************************************
// Ring counter method from Atmel App Note 101
// High Endurance EEPROM Storage
#define EEPROM_START_ADDRESS 10
const uint8_t max_EEPROM_items = 150;
uint8_t ring_index = 0;
uint8_t ring_counter;

uint8_t* status_buffer = (uint8_t*)EEPROM_START_ADDRESS;
config_t* parameter_buffer = (config_t*) (status_buffer + (sizeof(status_buffer[0])*max_EEPROM_items));

// ***************************************************
// SPI stuff
// ***************************************************
#define DAC_SLAVE_SELECT 9

// ***************************************************
// Port Expander Buttons
// ***************************************************
gpio_button on_button = {0, HIGH, 0};
gpio_button off_button = {1, HIGH, 0};

const uint8_t INT_pin = 2;

// ***************************************************
// State Machine variables
// ***************************************************
typedef enum
{
  tr_ANALOG, ANALOG_MENU, tr_DIGITAL_CLOCK, DIGITAL_CLOCK_MENU, tr_DIGITAL_PWM, DIGITAL_PWM_MENU, tr_CONFIG, CONFIG_MENU 
} state_t;
state_t system_state = tr_CONFIG;

// ***************************************************
// Setup function
// ***************************************************
void setup()
{
  Serial.begin(19200);
  SPI.begin();
  SPI.setClockDivider( SPI_CLOCK_DIV2 );
  Wire.begin();
  
  load_EEPROM_config();
  
  // Set Port Expander configuration
  pinMode( INT_pin, INPUT );
  gpio_set_inputs( _BV(on_button.pin) | _BV(off_button.pin) );
  gpio_set_pullups( _BV(on_button.pin) | _BV(off_button.pin) );
  gpio_set_interrupt_on_change( _BV(on_button.pin) | _BV(off_button.pin) );
  gpio_set_defualt_value( 0xFF );
  gpio_set_interrupt_control( 0x00 );
  gpio_set_configuration( 0x00 );
  
  lcd_clear_screen();
  lcd_set_backlight( configuration.backlight );
  lcd_set_contrast( configuration.contrast );
}

// ***************************************************
// Loop function
// ***************************************************
void loop()
{
  update_port_expander_buttons();
  
  if( on_button.edge && on_button.state==HIGH )
    system_state = tr_CONFIG;
    
  if( off_button.edge && off_button.state==HIGH )
    system_state = tr_ANALOG;
  
  switch( system_state )
  {
    case tr_CONFIG:
      init_CONFIG_menu();
      break;
      
    case tr_ANALOG:
      init_ANALOG_menu();
      break;
  }
}

void load_EEPROM_config()
{
    // Search for the right EEPROM pointer in the status buffer.
  uint8_t match_found = 0;
  while( ring_index<max_EEPROM_items && !match_found )
  {
    ring_index++;
    match_found = (uint8_t)(eeprom_read_byte(&status_buffer[ring_index])-eeprom_read_byte(&status_buffer[ring_index-1])) != 1;
  }
  if( ring_index==max_EEPROM_items )
  {
    ring_index = 0;
    ring_counter = eeprom_read_byte(&status_buffer[max_EEPROM_items-1]);
  }
  else
  {
    ring_counter = eeprom_read_byte(&status_buffer[ring_index-1]);
  }
  
  // Read config from EEPROM
  eeprom_read_block( &configuration, &parameter_buffer[ring_index], sizeof(config_t) );
  
  // if the EEPROM starts out blank, load the default configuration.
  if( configuration.backlight==0xFF && configuration.contrast==0xFF && configuration.dc_zero_offset==0xFFFF )
  {
    configuration.backlight = old_config.backlight;
    configuration.contrast = old_config.contrast;
    configuration.dc_zero_offset = old_config.dc_zero_offset;
  }
  else
  {
    old_config = configuration;
  }
}

// ***************************************************
// update_EEPROM_config function
// ***************************************************
void update_EEPROM_config()
{
  // If the new configuration is different from the old one...
  // update the config present in the EEPROM.
  if( configuration.backlight!=old_config.backlight || configuration.contrast!=old_config.contrast || configuration.dc_zero_offset!=old_config.dc_zero_offset )
  {
    ring_counter++;
    eeprom_update_byte( &status_buffer[ring_index], ring_counter );
    Serial.println( configuration.dc_zero_offset );
    eeprom_update_block( &configuration, &parameter_buffer[ring_index], sizeof(config_t) );
    Serial.println( configuration.dc_zero_offset );
    
    ring_index++;
    if( ring_index==max_EEPROM_items )
    {
      ring_index = 0;
    }
    
    old_config = configuration;
  }
}

void update_port_expander_buttons()
{
  clear_port_expander_edge();
  
  if( digitalRead(INT_pin) == LOW )
  {
    uint8_t buttons = gpio_read_port();
    update_gpio_button( &on_button, buttons );
    update_gpio_button( &off_button, buttons );
  }
}

void clear_port_expander_edge()
{
  on_button.edge = 0;
  off_button.edge = 0;
}

void update_gpio_button( gpio_button* button, uint8_t port_state )
{
  
  uint8_t current_state = (port_state&_BV(button->pin)) ? HIGH : LOW;
  
  button->edge = current_state != button->state;
  
  button->state = current_state;
}

void init_CONFIG_menu()
{
  lcd_clear_screen();
  lcd_set_cursor( 0, 0 );
  
  lcd_print_string( "Configuration Menu" );
  lcd_set_cursor( 0, 1 );
  lcd_print_string( ("Backlight:") );
  lcd_set_cursor( 0, 2 );
  lcd_print_string( ("Contrast:") );
  lcd_set_cursor( 0, 3 );
  lcd_print_string( ("Zero Offset:") );
  
  system_state = CONFIG_MENU;
}

void init_ANALOG_menu()
{
  lcd_clear_screen();
  lcd_set_cursor( 0, 0 );
  
  lcd_print_string( "Analog:" );
  lcd_set_cursor( 0, 1 );
  lcd_print_string( "Freq:" );
  lcd_set_cursor( 0, 2 );
  lcd_print_string( "Amplitude:" );
  lcd_set_cursor( 0, 3) ;
  lcd_print_string( "DC Offset:" );
  
  system_state = ANALOG_MENU;
}

I can post the code for the port expander too, but that doesn't seem to be causing any problems.

NHD-0420D3Z-FL-GBW-V3-29857.pdf (576 KB)

MCP23008 - 8-bit IO Expander with Serial Interface.pdf (295 KB)

NHD_I2C_Display.cpp

#include <Arduino.h>
#include <Wire.h>
#include "NHD_I2C_Display.h"

void lcd_send_command(uint8_t command)
{
  Wire.beginTransmission(LCD_I2C_ADDRESS);
  Wire.write( 0xFE );
  Wire.write( command );
  Wire.endTransmission();
  
  delayMicroseconds( 500 );
}
void lcd_send_command(uint8_t command, uint8_t data)
{
  Wire.beginTransmission(LCD_I2C_ADDRESS);
  Wire.write( 0xFE );
  Wire.write( command );
  Wire.write( data );
  Wire.endTransmission();
  
  delayMicroseconds( 500 );
}
void lcd_send_command(uint8_t command, uint8_t* data, uint8_t size)
{
  Wire.beginTransmission(LCD_I2C_ADDRESS);
  Wire.write( 0xFE );
  Wire.write( command );
  for( uint8_t i=0; i<size; i++ )
  {
    Wire.write( data[i] );
  }
  Wire.endTransmission();
  
  delayMicroseconds( 500 );
}

void lcd_write_char(char character)
{
  Wire.beginTransmission(LCD_I2C_ADDRESS);
  Wire.write( character );
  Wire.endTransmission();
  
  delayMicroseconds( 200 );
}


void lcd_display_on()
{
  lcd_send_command( LCD_DISPLAY_ON );
}

void lcd_display_off()
{
  lcd_send_command( LCD_DISPLAY_OFF );
}

void lcd_set_cursor(uint8_t column, uint8_t row)
{
  uint8_t row_offsets[] = { 0x00, 0x40, 0x14, 0x54 };
  
  lcd_send_command(LCD_SET_CURSOR, (column + row_offsets[row]));
}

void lcd_cursor_home()
{
  lcd_send_command( LCD_CURSOR_HOME );
  delayMicroseconds( 1000 );
}

void lcd_underline_on()
{
  lcd_send_command( LCD_UNDERLINE_ON );
  delayMicroseconds( 1000 );
}

void lcd_underline_off()
{
  lcd_send_command( LCD_UNDERLINE_OFF );
  delayMicroseconds( 1000 );
}

void lcd_move_cursor_left(uint8_t)
{
  lcd_send_command( LCD_MOVE_CURSOR_LEFT );
}

void lcd_move_cursor_right(uint8_t)
{
  lcd_send_command( LCD_MOVE_CURSOR_RIGHT );
}

void lcd_blink_on()
{
  lcd_send_command( LCD_BLINK_ON );
}

void lcd_blink_off()
{
  lcd_send_command( LCD_BLINK_OFF );
}

void lcd_backspace()
{
  lcd_send_command( LCD_BACKSPACE );
}

void lcd_clear_screen()
{
  lcd_send_command( LCD_CLEAR_SCREEN );
  delayMicroseconds( 2000 );
}

void lcd_set_contrast(uint8_t contrast)
{
  if( contrast>LCD_CONTRAST_MAX )
    contrast = LCD_CONTRAST_MAX;
    
  lcd_send_command( LCD_SET_CONTRAST, contrast );
  delayMicroseconds( 500 );
}

void lcd_set_backlight(uint8_t backlight)
{
  if( backlight > LCD_BACKLIGHT_MAX )
    backlight = LCD_BACKLIGHT_MAX;
    
  lcd_send_command( LCD_SET_BACKLIGHT, backlight );
}

void lcd_load_custom_char(uint8_t address, uint8_t* data)
{
  uint8_t send_data[9];
  send_data[0] = address;
  memcpy( send_data+1, data, 8 );
  
  lcd_send_command( LCD_LOAD_CUST_CHAR, send_data, 9 );
}

void lcd_print_string(char* string)
{
  uint8_t i=0;
  while( string[i] != '\0' )
  {
    lcd_write_char( string[i] );
    i++;
  }
}

please try a smaller resistor value e.g. 3K (3x10K parallel)

What is the I2C speed set to? // use Serial.println(TWBR, DEC);

Added another 10k in parallel doesn't help.

I printed both TWBR and TWSR, here is the output:

72
F8

With a prescaler of 1 and a baud setting of 72 that works out to a setting of 100 kHz, which is what my display is rated at. Should I slow it down some?

I also tried changing one of the address pins (A2) of the MCP23008 to see if there was some bus conflict or something. As it was the addreses were only different by 1 bit. Now there's two bits difference between the addresses. Still the same problem.

I tried moving the jumpers from A5/A4 directly over to the display. Same problem.

Now that I take a closer look at it, it seems that it isn't just missing letters, it's sometimes missing commands too, such as to clear the screen or move the cursor.

EDIT: I added TWBR = 130; after Wire.begin(). and now the display appears to work flawlessly. This stupid thing should be a 100 kHz display, not 58 kHz. Oh well, as long as it works.

do you have interrupts in your sketch? => might corrupt the I2C "pulse train"