Help me Bit Bang a little I2C for my ProMicro

High All!

I have been trying for days, to figure out how to find/use a library that will allow me to use alternate pins to drive an I2C LCD Backpack 16X2 display on my ProMicro.

I have found a few old threads here but I can’t seem to get them “bumped” up where anyone can help me get one going.

I am basically stuck with a system where I can use A0 for SDA and A1 for SCL. I don’t care about speed, I just need to stuff a little data i the display.

I have been looking at the LiquidCrystal_I2C_BitBang.h lately. It seems to be the closest to what I need.

Isn’t there anyone out there that has made this work?

I want to use a ProMicro, A0 for SDA and A1 for SCL. (there are a variety of reasons why. Please do not try to tell me to use other pins. They are all busy.

I can see the .cpp file where I can edit a range that should reassign the pins. I just don’t know if I am doing this right.

PLEASE PLEASE help me. I am a quick learner and promise to not ask a bunch of stupid questions if someone gives me a boot up on this topic.

Here is the software I would like to run. It works fine as is but I need it to run on alternate pins.

// Simple i2c LCD First Test by Greg

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

uint8_t Brks;

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void setup()
{
  lcd.begin(16, 2);
  lcd.clear();
}

void loop() {

  for (Brks=0; Brks<255; Brks++) {
    lcd.clear();
    lcd.print("Brake ");
    lcd.print(Brks, DEC);

    delay(250);
}

}

I tried to make this modification to LiquidCrystal_I2C_BitBang

// LiquidCrystal_I2C V2.0

#include "LiquidCrystal_I2C_BitBang.h"
#include <inttypes.h>
#include <Arduino.h>

////////// HARDCODED ////////////////
#if defined(__AVR_ATtiny85__)
  #define SDA_PORT PORTB
  #define SDA_PIN 0
  #define SCL_PORT PORTB
  #define SCL_PIN 2
  #define I2C_FASTMODE 1
#elif defined( __AVR_ATtinyX41__ )
// Ne koristiti 841 hardware I2C slave pinove PA6(SDA) i PA4(SCL) za bitbang I2C Master ( verovatno bug u 841 core - proveriti )
  #define SDA_PORT PORTA
  #define SDA_PIN PA7
  #define SCL_PORT PORTA
  #define SCL_PIN PA3
  #define I2C_FASTMODE 1
#elif defined (__AVR_ATmega328P__)
  #define SDA_PORT PORTD
  #define SDA_PIN 3
  #define SCL_PORT PORTD
  #define SCL_PIN 5
  #define I2C_FASTMODE 1
#elif defined (__AVR_ATmega32U4__)    //ProMicro (Leonardo) I added this
  #define SDA_PORT PORTF                    //ProMicro (Leonardo) I added this
  #define SDA_PIN PF7    //SDA PF7  A0  //ProMicro (Leonardo) I added this
  #define SCL_PORT PORTF                     //ProMicro (Leonardo) I added this
  #define SCL_PIN PF6    //SCL PF6  A1   //ProMicro (Leonardo) I added this
  #define I2C_FASTMODE 1                    //ProMicro (Leonardo) I added this
#else
  #error unknown MCU
#endif

For the purpose of making sure that my ATmega32U4 is in there. I want to use PF7 (A0) and PF6 (A1)

I just need to use a library that will let me run this on A0 and A1 respectively on a ProMicro

My above code works fine though it can only run on hardware I2C pins.

I will buy you breakfast if you can get me over this hump…

Now what do I do?

Thank You

G

gmcmurry:
I will buy you breakfast if you can get me over this hump…
Now what do I do?

Dump the libraries you are using for some other libraries.
Use SoftwareWire and hd44780 - both are available in the library manager.
While I don’t have a Pro Micro, I did test this on a Leonardo which is the same h/w and uses the same core as the micro.
The only difference being the LED definitions.

Here is a sample sketch to show how to use those libraries:

#include <SoftwareWire.h> 
const int sda=A0, scl=A1;
SoftwareWire Wire(sda,scl);
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>
hd44780_I2Cexp lcd; // declare lcd object and let it auto-configure everything.

void setup()
{
int istatus;

  istatus = lcd.begin(16,2);
  if(istatus)
  {
 // LCD initalization failed.
 // handle it anyway you want
 if(istatus < 0)
 istatus = -istatus;
 lcd.fatalError(istatus); // blinks error if LED_BUILTIN is defined
  }
  lcd.print("Hello, World!");
}

For more information on hd44780, see the github page:

And there is a “Documentation” example sketch where you can find many more links to information.
Please have a read of the documentation and the wiki as it contains vital information about the library and how and where the examples are located.
The i/o class for LCDs that use i2c expander backpacks is hd44780_I2Cexp
The library also includes a diagnostic sketch, I2CexpDiag, to help diagnose s/w or h/w issues with i2c lcd backpacks.

hd44780 is faster, is easier to install, and has many additional features over newLiquidCrystal, including it can auto configure itself so it is easier to use.

And BTW,
I like pancakes… :slight_smile:

— bill

One more thing, I'll mention just in case. This assumes you are not wanting or needing to ever use the Wire library to control the h/w i2c pins.

--- bill

Bill,

Thanks so much for all of this. This is just what I need.

I don't have any other I2C but I am using the 2nd Hardware Serial port.

I will get right to work on this and let you know how it goes.

Greg

You will be able to use other i2c devices on the same software i2c pins if you want/need to, like say an RTC. You just can't use the hardware i2c pins for hardware i2c using the Wire library.

--- bill

Thanks Bill! |500x281

Cool! With the hd44780 library, you don't have to hard code the i2c address or the pin mappings in the sketch so if you ever change LCD devices it will still "just work" even the new device uses a different i2c address and different pin mappings.

Is that a custom board you have made for the micro? Just curious what the overall project is.

--- bill

I have a 8th scale train project that is all computer controlled. It is quite large. The system uses a 16 hp gas engine to turn a 10kw alternator which, in turn, powers up to 4 2hp DC traction motors. I call it THE NAUTILUS.

I originally designed the entire system to be operated by Basic Stamp. There is a high speed serial bus that connects from a control panel to a system that operates the power system and the engine and then feeds back various operating functions to the "engineer" who can see the data on LCD / LED or VGA display.

The train goes about 10MPH (max) and has full prototypical air brakes (Westinghouse style). All funtions are controlled by the microcomputers which are all linked together by 38.4k RS232.

https://www.youtube.com/watch?v=-eJxJZfOYco&t=28s

The above link is me mumbling through a description of the control system.

This year (I have been working on this about 7 years) I decided to start using Arduino. I am pretty good at pBasic but never really programmed in C, let alone C++. This is why I am so grateful for the terrific people on this forum.

The board you see has RS232 Receivers and decodes my control protocol to switch on 4 relay when certain codes are present. There is a 20 byte protocol I developed.

Therefore, I can put one of these boards on one of my train cars, plug it into the serial bus (I use regular Cat5 and RJ45 connectors) and will turn on running lights, brake lights, backup lights, or whatever I need.

This link will take you on a little ride on the train.

https://www.youtube.com/watch?v=IWoQFLMCKSs&t=123s

You had to ask (smile). I actually enjoy showing this stuff and talking about all the systems. Happy to discuss. Anyone is welcome to PM me.

Greg

Very cool. That looks like a lot of fun. Impressive train track area. Where is that track? Are you the McMurry that has done all the visual effects? If so, my hats off, very cool stuff in some great movies.

--- bill

Yes – thats me. My day job is a VFX Supervisor

The track is at the “Los Angeles Live Steamers” at Griffith Park, Los Angeles

http://www.lals.org

Bill,

I am doing pretty good with your suggested code but in implementing it with other code I have written, I have a couple of issues.

Do I reallyt need:

{
int istatus;

  istatus = lcd.begin(16,2);
  if(istatus)
  {
 // LCD initalization failed.
 // handle it anyway you want
 if(istatus < 0)
 istatus = -istatus;
 lcd.fatalError(istatus); // blinks error if LED_BUILTIN is defined
  }

The ProMicro doesn’t really have easy access to an onboard LED. They are so bright anyway that I have them turned off in the beginning of my code. I have plenty of blinkies on my PCB

I was just going to replace it all with “lcd.begin(16,2)” but thought I would see why you suggested I use these few lines before jumping ahead.

I have another question but thought I should keep the dialog brief.

Greg

bperrybap: Cool! With the hd44780 library, you don't have to hard code the i2c address or the pin mappings in the sketch so if you ever change LCD devices it will still "just work" even the new device uses a different i2c address and different pin mappings.

--- bill

This all works so well, I am wondering if I can run 2 LCDs on the interface to show even more data.

If I don't have to hard code the i2c address, how do I distinguish more than one LCD in my code?

Greg

gmcmurry:
I am doing pretty good with your suggested code but in implementing it with other code I have written, I have a couple of issues.

Do I really need:


I was just going to replace it all with “lcd.begin(16,2)” but thought I would see why you suggested I use these few lines before jumping ahead.

You don’t need anything but lcd.begin()
The hd44780 library returns a status from begin().
You can look at that status to see if the lcd initialization has failed. If the return status is non zero initialization has failed.
The example checks for lcd initialization failure and calls a routine that blinks an LED forever.
That is what these lines do:

 if(istatus < 0)
 istatus = -istatus;
 lcd.fatalError(istatus); // blinks error if LED_BUILTIN is defined

but you can whatever you want when initialization fails, including nothing or even ignore the return status by not even looking at the return value from begin().

— bill

gmcmurry: This all works so well, I am wondering if I can run 2 LCDs on the interface to show even more data.

If I don't have to hard code the i2c address, how do I distinguish more than one LCD in my code?

Yes you can use more than a single LCD, you can use up to 16 LCDs. While you don't have to hard code the i2c address, you can. It isn't very obvious but here is a comment from the included MultiDisplay example sketch that explains a little more:

// It is also possible to create separate lcd objects
// and the library will still automatically find them.
// Example:
// hd4480_I2Cexp lcd1;
// hd4480_I2Cexp lcd2;
// The individual lcds would be referenced as lcd1 and lcd2
// i.e. lcd1.home() or lcd2.clear()
//
// It is also possible to specify the i2c address
// when specifying the lcd object.
// Example:
// hd44780_I2Cexp lcd1(0x20);
// hd44780_I2Cexp lcd2(0x27);
// This ensures that each each lcd object is assigned to a specific
// lcd device rather than letting the library automatically asign it.

When you have multiple LCDs you reference each one by its object name to talk to it. i.e. lcd1.begin(16,2), lcd2.begin(16,2) etc...

The lcd object names are totally arbitrary. i.e. you could use foo and bar as names rather than lcd1 and lcd2.

The LCDs do not have to be the same geometry. i.e. one could be 16x2 and another might be 20x4

So there are two ways to handle multiple LCDs. You can define your two lcd objects and let the library find them and assign addresses the ones it finds or you can specify them in the constructor.

If you use multiple LCDs and do not specify addresses, there is no guarantee as to the address order they will be assigned since C++ makes not guarantees as to how constructors are called. However, form what I've seen they are called in the order they are declared. The hd44780 library looks for backpacks from low address to high, so normally the library will assign the addresses to backpacks from lowest address to highest address in the order they are declared. The addresses do not need to be sequential. i.e. it could be 0x20 and 0x22

When you specify an i2c address that LCD object will only talk to an LCD device at that address.

You need to choose one way or the the other and not mix lcd objects where some have specified addresses and some do not as the library currently does not fully handle this use case.

--- bill

bperrybap: If you use multiple LCDs and do not specify addresses, there is no guarantee as to the address order they will be assigned since C++ makes not guarantees as to how constructors are called. However, form what I've seen they are called in the order they are declared. The hd44780 library looks for backpacks from low address to high, so normally the library will assign the addresses to backpacks from lowest address to highest address in the order they are declared. The addresses do not need to be sequential. i.e. it could be 0x20 and 0x22

But I assume one would still need to make sure the two LCDs do not have the same physical address; ie bridge the A0 jumper on the BackPack. I think most of these we find will have all the address jumpers open.

Correct?

Greg

gmcmurry: But I assume one would still need to make sure the two LCDs do not have the same physical address; ie bridge the A0 jumper on the BackPack. I think most of these we find will have all the address jumpers open.

Correct?

Greg

Correct. If using more than one LCD device at a time, the i2c addresses on each device must be unique. While most LCD modules have jumpers or solder pads and they are typically open when shipped, not all LCD backpacks have the same default i2c address nor the same configurable address range. This is because there are two different address ranges for the PCF8574 chips. 0x20 to 0x27 and 0x38 to 0x3f. So depending on which chip is on your backpack the default address and address range may be different. Also, some backpacks are wired to have open jumpers as high and some are wired to have open jumpers as low. So it isn't always obvious what the default address is or what it will change to when bridging a jumper.

If you are unsure about the address of your backpack, run the included I2CexpDiag sketch and it will tell you the address. (on serial port and on the LCD itself) You can even hookup up multiple LCD devices at a time and it will test all the devices it can locate.

--- bill

OK I have this all working now.

This has been a big project for me. I bought a ProMicro and went crazy.
Now I have a pretty nice device that, when I want, can operate 2 2X16 LCDs.

Help here on this forum is amazing.

My device reads serial RS232 data that contains a bunch of data. It then decodes the data and turns on several LEDs according to my requirements.

I also included code that would show me the status of the actual code on two LCD displays.

Here is my last question for this thread…

Is it OK to just NOT plug in the LCDs if I am not using them? I am not doing anything with the status from the LCD so I am guessing that when I init the LCDs, it just times out and moves on.

Should I look for some smart way to know that the LCDs are not present and bypass all that code?

It is working fine now with no LCDs but my curious mind needs to know.

Greg

gmcmurry: Here is my last question for this thread...

There is always more questions... :)

Is it OK to just NOT plug in the LCDs if I am not using them? I am not doing anything with the status from the LCD so I am guessing that when I init the LCDs, it just times out and moves on.

It is ok leave them unplugged. What can cause issues is plugging them in or unplugging them while the system is running. That can cause glitches on the i2c lines that can cause the AVR to lock up due to certain i2c signal exceptions not being properly handled down in the Wire library.

When unplugged, the hd44780 library will be returning errors but if you are not looking at the return status then your code won't know the difference.

--- bill

Perfect.

ASAP I will make a little video of what I built.

Its called a FRED (I don't know what that stands for) but its an End of Train Device. I think it should be called EOTD. Go figure.

Train history is interesting.

Greg