Using multiple Inventor HAT Minis

Hi, this isn’t so much a question as a report on the changes I needed to make to the existing Python library in order to use a pair of Inventor HAT Mini (AKA IHM) boards on a robot, where I needed support for four motors. Below also are two “bugs” or issues I found when using the HAT.

I’d first like to compliment Pimoroni for what is a quite impressive software library. It was much more than I had expected. Lots of great features for the motor controller in particular.

Given the IHM is based on the IO Expander (Nuvoton MS51) it seemed strange to me that the Motor, PID, Encoder and Servo implementations were in the ioexpander library rather than in the inventorhatmini. But that’s a side issue, I’m assuming there was some good reason for this (access to IOE internals?)

I made three changes to inventorhatmini/__init__.py. We need to declare the variable so that even if undefined it exists (this avoids an exception when calling __del__()):

        self.ioe = None

Secondly, in order to change the I2C address of the second IHM, we need to expose the underlying IO Expander with the addition of this method to __init__py:

    def get_ioe(self):
        return self.ioe

Third, running multiple IHMs puts two GPIO pins in conflict. We’ll disable them on all but the default address with the change of these lines to the __init__() method:

        if ( address == self.IOE_ADDRESS ): # only if using default address, for use with multiple HATs
            # Setup user button 
            self._pin_user_sw = gpiodevice.get_pin(self.PI_USER_SW_PIN, "IHM-SW", INPD)
    
            # Setup amplifier enable. This mutes the audio by default 
            self._pin_amp_en = gpiodevice.get_pin(self.PI_AMP_EN_PIN, "IHM-AMP-En", OUTL if start_muted else OUTH)

with one final change to avoid an exception:

    def __del__(self):
        if self.ioe:
            self.ioe.reset()

Then I modified an existing Pimoroni script and executed:

from inventorhatmini import InventorHATMini

board = InventorHATMini(init_leds=False) # default address: 0x17
_ioe = board.get_ioe()

_i2c_addr = 0x16 # new I2C address
_ioe.set_i2c_addr(_i2c_addr)

print('complete: changed I2C address of IO Expander to 0x{:02X}.'.format(_i2c_addr))

This changes the IHM attached at 0x17 to 0x16. Now two IHMs can coexist.


Now I have an Inventor HAT Mini at both 0x16 and 0x17. The user button and sound only operate on the latter. This is unavoidable if both are plugged directly into the Pi’s GPIO header.

I’d like to suggest to Pimoroni that they incorporate these small changes into the library so that people can operate multiple boards without needing to make these changes to their installed libraries.


Incompatibility with Pololu N20 Motors

The simplicity of connecting an Inventor HAT Mini directly to a Pimoroni N20 motor with encoders (e.g., Pololu #5197) is being able to use 6 pin JST SH connectors. Unfortunately, I immediately found out that the pinouts for the IHM and the Pololu motors are actually reversed. There are two solutions for this: purchase cables that are “opposite” or “reversed” (available on AliExpress); or use a needle or sharp object to de-pin the connector at one end and reverse the wires. Perhaps Pimoroni could sell some compatible cables, or at least document this on the product page. I initially couldn’t figure out why the motors weren’t working.


Plasma Bug?

I do have an additional issue that probably should be its own Topic, which is that the Plasma library doesn’t seem to work unless one executes under sudo:

RuntimeError: Failed to initialise the RGB LEDs because higher privileges are needed to control the necessary hardware of the Raspberry Pi.

I’m avoiding that issue by calling the constructor with “init_leds=False”. This does seem like a bug. This may be due to me using a global installation rather than venv, which for various reasons I cannot use.

Thanks for your thorough post about Inventor HAT Mini! I’m glad you were able to get two of them working together.

The reason for this is because, as you point out, IHM is based on the IO Expander. As such adding the support in this way lets people hook up motors, encoders and servos to our IO Expander breakout (with inventive wiring).

What would be best is if you could raise a pull request ion the Inventor HAT Mini repo for us to review. That way there is no chance of us making a mistake copying changes over, and you would receive appropriate attribution.

My one comment would be, it is unnecessary to add get_ioe() function, as you can call board.ioe.set_i2c_addr() directly.

You are correct that the pinout on the IHM is reversed compared to that of the Pololu motors. I forget the technical reason for this, but we made sure that our Micro Metal Motor Encoders (MMME) matched Pololu encoders, and sourced suitable cables that will reverse the pinout: 6 Pin JST-SH Cable. I assume you used different cables?

This is a known limitation sadly. As of recent Pi OS versions, the library we use for driving addressable LEDs needs admin access in order to control the PWM hardware in the correct way to produce the signals, as the exception explains. There is nothing we can do about this, which is why the option to init_leds=False was included.

Hi, thanks very much for responding. I’ll respond in order.

I’d never thought about connecting an N20 motor (well, a driver) directly to an IO Expander, but as it has PWM support, yes, that makes good sense. But you might want to document that somewhere, it was news to me (and I’m a big fan of the IOE).

On a PR, that’s a good idea. I could also just email the one changed file (__init__.py) to tech support. I’ve actually done a local copy of the library into my own project directory so I’d have to re-clone the repo.

I hadn’t thought of board.ioe.set_i2c_addr(), yes, a better choice.

I did kinda both. I used the sharp end of a Knipex tweezer to de-pin all four cables I had and fix it. Twiddly. The front two motors on the robot need a longer cable as the Pi and IHMs are at the back of the robot, so I ordered a pair of 10cm reversed cables from AliExpress and they arrived today. Again, I just think this should be documented, as some of your customers will have Pololu motors and wonder why they don’t work. It appear Pimoroni and SparkFun are the same, Pololu reversed. But given they’re a major supplier of N20s and have the widest range, lots of people will be using them.

Regarding Plasma, a shame. I don’t want to be running my robot OS under sudo. Though I’m not on this small robot using the servo or GPIO pins anyway and the motor LEDs work fine. I’ve got a Tiny FX W on the robot so the blinkin lights are its job.


There’s one thing that I found curious. I spent much of yesterday on it and finally gave up.

There’s the normal code for setting up the pair of IHMs:

aft_controller = InventorHATMini(address=0x17, init_servos=False, init_leds=False)
fwd_controller = InventorHATMini(address=0x16, init_servos=False, init_leds=False)

…and then I grab the motors off of each and all is fine.

But when I created a MotorController class and put those two lines inside the class so that its __init__() method created them, then grabbed the motors off of the class instance, the motors didn’t move. I added print statements all over inside the IHM and Motor classes and they printed identical results between the root script and class approaches, i.e., I could find nothing different at all.

The only solution (after much of a day trying) was to pass the aft_controller and fwd_controller objects to the class constructor from the startup script, which worked. What this says is that it’s impossible to instantiate the InventorHATMini objects within a Python class. The solution is not ideal, but workable.

I’m a journeyman/intermediate Python programmer and I frankly was baffled by this.

I’ve gone ahead and created the suggested PR at: