I2C issues - display not printing

Hi, I hope I can pick someone’s brain about an issue I am having.

I am building a sample based synth using a Nano board together with WavTrigger.
It’s all good now (well, sort of…) in terms of functionality, except I can’t get the display to work.

The software it’s not my code, I am quite a newbie with Arduino.

That said, thinking there might be something wrong with the display, I run an I2C scanner to check the address via the serial monitor and everything was ok. I also uploaded a test code to print something on the screen and it was all fine (classic “Hello World” stuff). So I am assuming address is fine and lcd screen working.

When I upload the actual software on the Nano, everything seems to work fine, except that I can’t see anything on screen. The screen is meant to print the name f the patch I am using. I also tried to adjust the contrast and, apart from seeing a couple of lines of rectangles, nothing was printed on screen.

Is there anything else I can try to do to sort this one out?

I appreciate if someone can help, thanks!

Start here https://forum.arduino.cc/t/how-to-get-the-best-out-of-this-forum/1111649

So, here it's the code:

#pragma GCC optimize ("O3")
#include <AltSoftSerial.h>
#include <MIDI.h>
#include <wavTrigger.h>
#include <avr/pgmspace.h>

#define EI_NOTINT0
#define EI_NOTINT1
#include <EnableInterrupt.h>

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 2);

//AltSoftSerial altSerial;
//MIDI_CREATE_INSTANCE(AltSoftSerial, altSerial, MIDI);
MIDI_CREATE_DEFAULT_INSTANCE();

wavTrigger wTrig;

// NE PAS OUBLIER DE METTRE BANK MAX A JOUR !!

struct constante {

  const int bankMax;      // 1100
  const char volumeMin;   // -90dB
  const char volumeMax;   // -12dB
  const char gainMax;     // -9dB
  const byte antiRebond;  // 250ms

} cst = {1900, -90, -12, -9, 250};

struct globalVar {

  uint32_t last_interrupt_time;
  int crossfade;
  float coef;             // Calculé dans le Setup, initialisé à 0
  float coef2;            // Calculé dans le Setup, initialisé à 0
  float coefGain;         // Calculé dans le Setup, initialisé à 0
  boolean bkAchange;
  boolean bkBchange;

} var = {0, 0, 0, 0, 0, 0, 0};

struct globalPin {

  // Analog
  const byte master;    //   ==> Analog 0
  const byte release;   //   ==> Analog 1
  const byte crossfade; //   ==> Analog 2

  // Digital
  const byte bankUpA;   //   ==> Digital 2  (Hardware INT0)
  const byte bankUpB;   //   ==> Digital 3  (Hardware INT1)
  const byte bankDnA;   //   ==> Digital 4
  const byte bankDnB;   //   ==> Digital 5

  const byte split;     //   ==> Digital 6
  const byte half;      //   ==> Digital 7

} pin = {0, 1, 2, 2, 3, 4, 5, 6, 7};

struct switchState {

  boolean half;
  boolean split;

} state = {0, 0};   // a degager

struct channel {
  // track = X X XX : channel / bank / note number
  volatile int bank;
  char volume;
  char volumeSplit;
  int tracks[12];

};

channel channelA;
channel channelB;

static void bankUpA() {
  uint32_t interrupt_time = millis();

  if (interrupt_time - var.last_interrupt_time > cst.antiRebond) {
    channelA.bank = (channelA.bank + 100) % (cst.bankMax + 100);
    var.bkAchange = 1;
  }
  var.last_interrupt_time = interrupt_time;
}

static void bankDnA() {
  uint32_t interrupt_time = millis();

  if (interrupt_time - var.last_interrupt_time > cst.antiRebond) {
    channelA.bank = ((cst.bankMax + 100) + (channelA.bank - 100)) % (cst.bankMax + 100);
    var.bkAchange = 1;
  }
  var.last_interrupt_time = interrupt_time;
}

static void bankUpB() {
  uint32_t interrupt_time = millis();

  if (interrupt_time - var.last_interrupt_time > cst.antiRebond) {
    channelB.bank = (channelB.bank + 100) % (cst.bankMax + 100);
    var.bkBchange = 1;
  }
  var.last_interrupt_time = interrupt_time;
}

static void bankDnB() {
  uint32_t interrupt_time = millis();

  if (interrupt_time - var.last_interrupt_time > cst.antiRebond) {
    channelB.bank = ((cst.bankMax + 100) + (channelB.bank - 100)) % (cst.bankMax + 100);
    var.bkBchange = 1;
  }
  var.last_interrupt_time = interrupt_time;
}

static int track(int bank, byte pitch)
{
  int track = bank + pitch;
  return track;
}

static char volume(int potard, boolean cross)
{
  float volume0;

  if (cross) {
    volume0 = sqrt(var.coef * potard) + cst.volumeMin ;   // potard = crossfade ou (1023-crossfade)
  }
  else {
    volume0 = sqrt(var.coef2 * potard) + cst.volumeMin ; // potard = volumePot
  }

  char volume = volume0;
  return volume ;
}

void handleNoteOn(byte channel, byte pitch, byte velocity)
{
  int trackA = track(channelA.bank, pitch);
  int trackB = track(channelB.bank, pitch) + 2000;

  if (state.split == LOW) {

    wTrig.trackGain(trackA, channelA.volume);       // sinon -70 pour attack
    wTrig.trackPlayPoly(trackA);

    wTrig.trackGain(trackB, channelB.volume);       // sinon -70 pour attack
    wTrig.trackPlayPoly(trackB);
  }
  else {

    trackA = trackA + 12;
    trackB = trackB - 12;

    if (pitch < 60) {

      wTrig.trackGain(trackA, channelA.volumeSplit);   // sinon -70 pour attack
      wTrig.trackPlayPoly(trackA);

    }

    else {

      wTrig.trackGain(trackB, cst.volumeMax);   // sinon -70 pour attack
      wTrig.trackPlayPoly(trackB);

    }
  }

  byte note = (pitch - 36) % 12;
  channelA.tracks[note] = trackA;
  channelB.tracks[note] = trackB;

}
void handleNoteOff(byte channel, byte pitch, byte velocity)
{
  int releaseTime = analogRead(pin.release) * 3;
  int trackA = track(channelA.bank, pitch);
  int trackB = track(channelB.bank, pitch) + 2000;

  if (state.split == LOW) {

    if (releaseTime > 200) {
      wTrig.trackFade(trackA, cst.volumeMin, releaseTime, 1);
      wTrig.trackFade(trackB, cst.volumeMin, releaseTime, 1);
    }
    else {
      wTrig.trackStop(trackA);
      wTrig.trackStop(trackB);
    }
  }

  else {
    trackA = trackA + 12;
    trackB = trackB - 12;

    if (releaseTime > 200) {
      wTrig.trackFade(trackA, cst.volumeMin, releaseTime, 1);
      wTrig.trackFade(trackB, cst.volumeMin, releaseTime, 1);
    }
    else {

      wTrig.trackStop(trackA);
      wTrig.trackStop(trackB);
    }
  }

  byte note = (pitch - 36) % 12;
  channelA.tracks[note] = 0;
  channelB.tracks[note] = 0;

}

static void crossfader(int crossPot) {

  register byte note = 11;

  if (state.split == LOW) {

    if (crossPot < 512) {

      channelA.volume = cst.volumeMax;
      channelB.volume = volume(crossPot, 1);

    }
    else {

      channelB.volume = cst.volumeMax;
      channelA.volume = volume(1023 - crossPot, 1);

    }

    do {
      if (channelA.tracks[note]) {

        wTrig.trackGain(channelA.tracks[note], channelA.volume);
        wTrig.trackGain(channelB.tracks[note], channelB.volume);
      }
    } while (--note);
  }

  else {
    channelA.volumeSplit = volume(var.crossfade, 0) ;

    do {
      if (channelA.tracks[note]) {

        wTrig.trackGain(channelA.tracks[note], channelA.volumeSplit);
      }
    } while (--note);

  }
}

static void masterVolume(int volumePot)
{
  char masterVolume = sqrt(var.coefGain * volumePot) + cst.volumeMin ;
  wTrig.masterGain(masterVolume);
}

void line1() {

  lcd.setCursor(0, 0);
  switch (channelA.bank)
  {
    case 0:
      lcd.print(F("A00: Violins    "));
      break;

    case 100:
      lcd.print(F("A01: Flutes     "));
      break;

    case 200:
      lcd.print(F("A02: Choir      "));
      break;

    case 300:
      lcd.print(F("A03: Strings    "));
      break;

    case 400:
      lcd.print(F("A04: Vibes      "));
      break;

    case 500:
      lcd.print(F("A05: Organ      "));
      break;

    case 600:
      lcd.print(F("A06: Bassoon    "));
      break;

    case 700:
      lcd.print(F("A07: Cello      "));
      break;

    case 800:
      lcd.print(F("A08: M300 Brass "));
      break;

    case 900:
      lcd.print(F("A09: Mixed Brass"));
      break;

    case 1000:
      lcd.print(F("A10: Teno & Alto"));
      break;

    case 1100:
      lcd.print(F("A11: Trom & Trum"));
      break;

    case 1200:
      lcd.print(F("A12: Orchestra  "));
      break;

    case 1300:
      lcd.print(F("A13: Unused     "));
      break;

    case 1400:
      lcd.print(F("A14: Unused     "));
      break;

    case 1500:
      lcd.print(F("A15: Unused     "));
      break;

    case 1600:
      lcd.print(F("A16: Unused     "));
      break;

    case 1700:
      lcd.print(F("A17: Unused     "));
      break;

    case 1800:
      lcd.print(F("A18: Unused     "));
      break;

    case 1900:
      lcd.print(F("A19: Unused     "));
      break;

  }
}

void line2() {

  lcd.setCursor(0, 1);
  switch (channelB.bank)
  {
    case 0:
      lcd.print(F("B00: Violins    "));
      break;

    case 100:
      lcd.print(F("B01: Flutes     "));
      break;

    case 200:
      lcd.print(F("B02: Choir      "));
      break;

    case 300:
      lcd.print(F("B03: Strings    "));
      break;

    case 400:
      lcd.print(F("B04: Vibes      "));
      break;

    case 500:
      lcd.print(F("B05: Organ      "));
      break;

    case 600:
      lcd.print(F("B06: Bassoon    "));
      break;

    case 700:
      lcd.print(F("B07: Cello      "));
      break;

    case 800:
      lcd.print(F("B08: M300 Brass "));
      break;

    case 900:
      lcd.print(F("B09: Mixed Brass"));
      break;

    case 1000:
      lcd.print(F("B10: Teno & Alto"));
      break;

    case 1100:
      lcd.print(F("B11: Trom & Trum"));
      break;

    case 1200:
      lcd.print(F("B12: Orchestra  "));
      break;

    case 1300:
      lcd.print(F("B13: Unused     "));
      break;

    case 1400:
      lcd.print(F("B14: Unused     "));
      break;

    case 1500:
      lcd.print(F("B15: Unused     "));
      break;

    case 1600:
      lcd.print(F("B16: Unused     "));
      break;

    case 1700:
      lcd.print(F("B17: Unused     "));
      break;

    case 1800:
      lcd.print(F("B18: Unused     "));
      break;

    case 1900:
      lcd.print(F("B19: Unused     "));
      break;
  }

}

void setup()
{
  // Pin Mode
  pinMode(pin.bankUpA, INPUT_PULLUP);
  pinMode(pin.bankDnA, INPUT_PULLUP);
  pinMode(pin.bankUpB, INPUT_PULLUP);
  pinMode(pin.bankDnB, INPUT_PULLUP);

  pinMode(pin.half, INPUT_PULLUP);
  pinMode(pin.split, INPUT_PULLUP);

  // Interruptions
  attachInterrupt(0,  bankUpA, FALLING);
  attachInterrupt(1,  bankUpB, FALLING);
  enableInterrupt(4,  bankDnA, FALLING);
  enableInterrupt(5,  bankDnB, FALLING);

  // MIDI Init
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.turnThruOff ();



  // Wav Trigger Init
  wTrig.start();
  //wTrig.setAmpPwr(0);
  wTrig.masterGain(cst.gainMax);


  // Banks
  channelA.bank = 0;         // 0-> 9 ==> 000 -> 900
  channelB.bank = 200;       // Bank Choir

  // Volumes
  channelA.volume = cst.volumeMax;      // -9
  channelB.volume = cst.volumeMax;      // -9
  channelA.volumeSplit = cst.volumeMax; // -9
  var.coef = (((float) cst.volumeMin - cst.volumeMax) * (cst.volumeMin - cst.volumeMax)) / 512;
  var.coef2 = (((float) cst.volumeMin - cst.volumeMax) * (cst.volumeMin - cst.volumeMax)) / 1024;
  var.coefGain = (((float) cst.volumeMin - cst.gainMax) * (cst.volumeMin - cst.gainMax)) / 1024;

  // initialize the LCD
  lcd.init();

  // Turn on the blacklight and print a message.
  //lcd.backlight();

  lcd.setCursor(0, 0);
  lcd.print(F("   FAR BEYOND   "));

  lcd.setCursor(0, 1);
  lcd.print(F("   PERCEPTION   "));
  delay(1000);

  lcd.setCursor(0, 1);
  lcd.print(F("                "));

  lcd.setCursor(0, 0);
  lcd.print(F("   PSYL()TRON"));
  delay(250);

  lcd.setCursor(0, 0);
  lcd.print(F("   PSY(  )RON"));
  delay(250);

  lcd.setCursor(0, 0);
  lcd.print(F("   PS(    )ON"));
  delay(250);

  lcd.setCursor(0, 0);
  lcd.print(F("   P(      )N"));
  delay(250);

  lcd.setCursor(0, 0);
  lcd.print(F("   (        )"));
  delay(250);

  line1();
  line2();
}

void loop()
{

  MIDI.read();

  if (var.bkAchange) {
    wTrig.stopAllTracks();
    line1();
    var.bkAchange = 0;
  }

  if (var.bkBchange) {
    wTrig.stopAllTracks();
    line2();
    var.bkBchange = 0;
  }

  MIDI.read();

  // Pin Reading
  state.half = digitalRead(pin.half);
  state.split = digitalRead(pin.split);


  // Master Volume
  int volumePot = analogRead(pin.master);
  static int lastVolumePot = 0;
  if (abs(volumePot - lastVolumePot) > 8) {
    masterVolume(volumePot);
    lastVolumePot = volumePot;
  }


  // Cross Fade / SPLIT
  var.crossfade = analogRead(pin.crossfade) ;
  static int lastCrossFade = 0;
  if (abs(var.crossfade - lastCrossFade) > 8) {
    crossfader(var.crossfade) ;
    lastCrossFade = var.crossfade;
  }

  MIDI.read();

  static int melloPitch = 0;

  //Half Speed
  if (state.half == HIGH && melloPitch != -32768) {

    melloPitch = melloPitch - 128;
    wTrig.samplerateOffset(melloPitch);

  }

  //Normal Speed
  if (state.half == LOW && melloPitch != 0) {

    melloPitch = melloPitch + 128;
    wTrig.samplerateOffset(melloPitch);
  }
}

Hardware is an Arduino Nano connected to an lcd display I2C with adapter I recently purchased. The Lcd screen is branded Gerui and it's a 1602 lcd Module IIC interface lcd screen. I don't have the screen with me now so I can't upload info about the chip. As I said before, connections were fine and so is the screen, because I managed to run I2C scanner and even print "hello world" on it correctly.

What address was found?

the one in the code: 0x27

1 Like

Please post the example code for that. Perhaps we can spot some difference.

1 Like

Hi Paul, this is the example code I used to check:

//YWROBOT
//Compatible with the Arduino IDE 1.0
//Library version:1.1
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void setup()
{
  lcd.init();                      // initialize the lcd 
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(3,0);
  lcd.print("Hello, world!");
  lcd.setCursor(2,1);
  lcd.print("Ywrobot Arduino!");
   lcd.setCursor(0,2);
  lcd.print("Arduino LCM IIC 2004");
   lcd.setCursor(2,3);
  lcd.print("Power By Ec-yuan!");
}


void loop()
{
}

I noticed you have //lcd.backlight(); commented out, so no backlight.

Thanks! I am not sure about that. I think the backlight can be activated with a jumper at the back of the screen and in fact I have it on. I have a video of it, but I am not sure I can post YT links on here.

Probably that instruction was left commented out by the author, on purpose?

The jumper is to enable/disable the backlight. I think you still need to turn it on via software.
You don't know which module the author actually used, may be different from your's

1 Like

quite likely, I think this is a relatively old project...

Well, did you turn the backlight on?

I can't see any difference except the lcd.backlight().

Not all i2c backpacks can actually control the backlight, I suspect.

Can you post the compilation statistics for your code, showing the used and available dynamic and program memory?

Do you mean this:

"Sketch uses 15072 bytes (49%) of program storage space. Maximum is 30720 bytes.
Global variables use 1056 bytes (51%) of dynamic memory, leaving 992 bytes for local variables. Maximum is 2048 bytes."

Yes, that's it.

But that looks ok. If the dynamic memory usage was around 80% or more, many strange and unexpected effects can occur.

It's still possible that your code is running out of dynamic memory, because the compile-time statistics can't include and memory that gets allocated when the code is running. But I can't immediately see anything like that occurring in your code.

1 Like

Thanks in any case for taking the time to check, it's really appreciated!
Maybe I will try with a different display and see what happens...

Did you turn the backlight on?

I'll do it when I go back home from work...
will let you know, but it's gonna be in few hours

I'm sure it will solve all your problems.
Have a nice day!

@smmpcomposer
One thing you may not be aware of is that not all LCD i2c backpacks are the same.
There are multiple h/w designs.
And while one design is the most popular, there are about 5 different h/w designs out there that work slightly differently.

The jumper can have different functions depending on the backpack design.
On most, removing the jumper disables backlight control and depending on the design the backlight can be permanently off or permanently on.

Also, the LiquidCrystal_I2C library is hardcoded to work with only one of the designs, those popular one.
If your backpack is not the one that it is hard-coded for, it will not work as expected or at all.

The hd44780 library not only supports all the designs out there but can auto detect which one you have and auto locate the i2c address.

My suggestion would be use the hd44780_I2Cexp i/o class of the hd44780 library instead.
It also includes a diagnostic sketch, I2CexpDiag, that can verify the operation of the LCD device with the library.
Converting your existing code would only require changing about 3 lines of the sketch code.

The hd44780 library is available in the IDE library manager.
There is a wiki with additional information including a page for the hd44780_I2Cexp i/o class.

https://github.com/duinoWitchery/hd44780/wiki

If you do decide to go this route, I highly recommend spending some time reading the hd44780 library and hd44780_I2Cexp i/o class documentation.
For sure the main wiki, the hd44780_I2Cexp wiki, and the hd44780 readme file.
All of which are available through the above link.
It can look intimidating at first, but it is actually really easy to use.

The wiki page for the hd44780_I2Cexp i/o class has instructions for how to modify code written for LiquidCrystal_I2C to work with the hd44780 library.
It is only about 3 lines of code changes.

--- bill

1 Like