Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for alternate or multiple i2c buses #30

Open
bperrybap opened this issue Aug 21, 2021 · 2 comments
Open

support for alternate or multiple i2c buses #30

bperrybap opened this issue Aug 21, 2021 · 2 comments

Comments

@bperrybap
Copy link
Contributor

The i/o classes that use i2c (currently hd44780_I2Cexp and hd44780_I2Clcd) are hardcoded to use an i2c wire library object named Wire
While this can be worked around using a macro as shown in issue #29
not only is that work around "ugly" but it is not optimal since does not allow assigning a bus object on a per lcd object instance.
i.e. all lcd objects must use the same Wire library object which means that they must all be on the same bus.
The solution is to pass the i/o class a Wire library object information when the constructor is declared & defined.
While a simple solution would be to simply pass in a pointer to the desired Wire library object, this is cannot be implemented in a portable way given that not all Wire libraries use the same class name for their Wire object.
There are various ways to handle this, most revolve around making the classes templated classes.
Where things get messy is that some of the more user friendly ways to handle it require much newer version of the compiler tools to use newer capabilities of the C++ standard.
I will have to balance ease of use, with backward compatibility capabilities, and tool set availability to try to ensure the widest amount of portability across not only versions of the Arduino IDE but also across the 3rd party hardware board package addons.

@cHemingway
Copy link

cHemingway commented Feb 16, 2022

Just my 2 cents, I have also encountered this problem, and I have tried this simple solution with passing in a pointer to a HardwareI2C instance in the constructor, setting the default to Wire, like so

class hd44780_I2Clcd : public hd44780 
{
public:
hd44780_I2Clcd(uint8_t i2c_addr=0, HardwareI2C *wire=&Wire) : _Addr(i2c_addr), _I2Cbus(wire) {} 
//...
private:
HardwareI2C *_I2Cbus;

And then replacing all accesses to Wire like this

_I2Cbus->begin();

The alternative would be to use the Stream class from which HardwareI2C inherits, which is a bit too generic, and I don't know how many alternative Wire library implementations even use that. I agree that templates would seem to be the way to go if either HardwareI2C or Stream isn't usable with the majority (all?) of libraries.

While this is your choice to make, I am not sure if requiring a newer version of the Arduino IDE is that much of a constraint? Surely any user starting off just goes to arduino.cc and downloads the latest?

@bperrybap
Copy link
Contributor Author

bperrybap commented Feb 16, 2022

It is more complicated than you are thinking especially when looking across many different i2c libraries and many different h/w platforms.
Using constructor parameters is not the answer since it requires hard coding things so it is not portable across all environments.

i.e. you can't ever hardcode any classname or object name because the class name for the i2c interface and the default wire object name is not the same on all h/w platforms and/or i2c libraries.

It is better to use a templated class with parameters than to pass in constructor parameters since using constructor parameters requires using known types and a known object name if using a default. These names vary across environments.
Using class templates is the only portable way to solve the problem in a way that can work on all platforms since the class can compile time adapt to whatever the classes and object names are without having to know them in advance.
It can also mix/match them in the cases where there may be multiple hd44780 lcd objects each using different i2c objects.
Templates are the answer to solve issues like this.
The code in the hd44780 i/o classes is in .h files vs .cpp files to allow easy migration to templated classes as I knew it would eventually be desirable or even needed.

More below

It is theoretically possible to make it fully backward compatible with today's hd44780 i2c i/o classes.
It would be glorious! It requires some template capabilities in C++ 17.
But.... currently for various reasons C++ 17 is currently not available so it cannot be used/relied upon.
Believe me, I REALLY wanted a fully backward compatible solution that could be extended to allow users to re-configure the i2c object used.

Currently there is simply is no way to provide full backward compatibility and full portability across all h/w platforms while allowing the users to configure the i2c wire interface used for the hd44780 i/o class objects in their sketch.
(well it could be done with lots of conditionals for each supported h/w platform inside each i2c i/o class, but I want a portable solution that "just works" on all h/w platforms)

My current solution (which I have working ) creates new i/o classes using templates that require the user to pass in some template parameters (not constructor parameters).
While it is a pain to go this direction, there are some potential advantages to doing this in the long run.
It also ensures full backward compatibility since it doesn't alter the existing i/o class code.
This would allow migrating to a new way of doing the i/o class while preserving the existing i/o classes for backward comparability.

Even if using a templated class to resolve the i2c class name and object name differences,
it is more complicated than just requiring using newer versions of the Arduino IDE to get the
the newer template capabilities which could hide all the "magic" in a portable way using templates.
i.e. It isn't possible to just require using IDE version xxx or newer.
The toolset that does the build is independent of the IDE and there are many different toolsets given there are many different 3rd party h/w platforms.
There is also how that toolset is used which is an even bigger obstacle.
For example to get some additional template capabilities to make things simpler for the user, C++17 is needed.
And while toolsets for all the h/w platforms I've looked at seem to now be recent enough to support C++ 17, they often don't use it. And in fact some explicitly disable C++ 17 to use a previous standard
The IDE and bundled avr platform tools currently hard code gcc to use C++ 11 which means the more advanced C++17 capabilities don't exist when doing a build.
So even for the bundled AVR platform with the latest tools which support C++ 17, some of the more advanced template capabilities don't exist when building using the IDE, since the build tools specify using only the C++ 11 standard capabilities.

I already have working solutions that do not depend on C++ 17 for two new i/o classes:

  • hd44780_pcf8574 (similar to hd44780_I2Cexp but does not also support MCP23008)
  • hd44780_AIP30168 (this replaces the hd44780_I2Clcd i/o clas)

Here is what the class declaration looks like:


template <class T_WIRE_OBJ, T_WIRE_OBJ& WIRE_OBJ> class hd44780_AIP31068 : public hd44780 
{

//
private:
T_WIRE_OBJ& _wire = WIRE_OBJ; // wire object to use for instance

and all the i2c object references in the code become:
_wire.begin();

etc...

The sketch code object declaration is a bit funky looking since the user must specify the i2c class and i2c object.
It looks like this:
hd44780_AIP31068<decltype(Wire), Wire> lcd; // declare lcd object: auto locate chip

Where Wire is the name of the i2c object to be used. It is not the name of the i2c library.
decltype() is used to extract the object class type to make it portable and simple as it always works regardless of the actual name.
The wire object name could be Wire, Wire1, myWire (perhaps if using your own defined object for say a s/w serial library) etc...
The advantage of using this method is that it works on all platforms and with all i2c libraries, h/w i2c or s/w i2c libraries, a predefined global object, or a sketch created i2c object it doesn't matter.
This method of specifying the i2c parameters can be consistent across muliptle i/o classes.
i.e it also works the same way for the hd44780_pcf8574 i/o class.

If C++ 17 was available, the first parameter could be eliminated to allow this:
hd44780_AIP31068<Wire> lcd; // declare lcd object: auto locate & auto config expander chip

since auto could be used instead of having to specify the type.

One thing that I'm still wrestling with is whether to toss these new i/o classes into the hd44780 library
or release them as totally separate libraries that depend on the hd44780 library being installed.
The dependencies and installation of hd44780 library can be handled automatically by the IDE libary manager so just installing the new sub library from the IDE would automatically also install the hd44780 library if needed.
Years ago when this library started it wasn't as easy to install libraries so I bundled everything.
Unfortunately there are so MANY lazy TLDR people that simply refuse to read even a few sentences of documentation so they get confused with the current hd44780 library structure.
Having separate installable libraries makes the i/o class examples easier to locate and could remove some of that confusion since the i/o class sub library will only show the examples for that sub library.

There are advantages and disadvantages to bundling vs having separate libraries.
So it isn't all upside either way.
I'm still wrestling with the decision on which way to go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants