Encoders on Inventor HAT Mini

Hi, I’ve got two Inventor HAT Minis connected to four N20 motors via JST SH cables. I’ve changed the I2C address on one to 0x16 and disabled the User and I2C pins on the second to avoid GPIO conflicts. All four motors work as expected in both example scripts and my own code when setting their speed.

When using an example script like velocity_control.py with an encoder and PID controller (that’s just using the one default HAT), the motors move but no ticks seem to register on the encoders. Printing a Capture result is all zeros. Adding a print statement to the __take_reading() method in ioexpander/encoder.py returns zeros for raw_count, raw_change, and self.local_count.

So while the motor is moving my print outputs look like this:

__take_reading: raw count: 0; change: 0; local_count: 0
capture: Capture(count=0, delta=0, frequency=0.0, revolutions=0.0, degrees=0.0, radians=0.0, revolutions_delta=0.0, degrees_delta=0.0, radians_delta=0.0, revolutions_per_second=0.0, revolutions_per_minute=0.0, degrees_per_second=0.0, radians_per_second=0.0)

It would seem the encoders are configured correctly as per their defaults, as I’m printing the pins from inside the constructor:

__init__() channel: 1; pin A: 3; pin B: 4
__init__() channel: 2; pin A: 26; pin B: 1

This occurs for either the A or B motor. I’ve been digging around on this for quite some time now and can’t figure out what’s going on.

Any idea what might be going wrong?

I have two of those motors, with encoders, but mine are connected to an Inventor 2040 W (Pico W Aboard). It’s been a while since I’ve tinkered with it, but I do remember struggling with the encoders. @ZodiusInfuser helped me get it all sorted out and working. Lets see what he has to say.

Thanks, yes, I saw your earlier post in the Forum and was trying to glean any details, especially given that most of the implementation for the Inventor HAT Mini is actually within the IOE library.

I did find one strange thing I thought to mention: if I instantiate the InventorHATMini from within the script that starts the process, things work as expected. I tried embedding that same declaration within my own MotorController class and it no longer worked. I.e., there were no errors, but neither did the motors move. My solution was to declare the object and pass it into the MotorController’s constructor, which works. I find that rather hard to fathom, and it may be related to the encoder issue, no idea (since I can’t explain the above).

I’ve mentioned this previously at: Using multiple Inventor HAT Minis - #3 by maltheim

You lost me with that info. I was happy enough to get mine working the way I wanted. Even though I didn’t, and still don’t, understand how it does it. ;)
I usually understand the electronics, but get lost in the code.

Sorry, my target here are the Pimoroni developers of the library, who clearly understand what I’m talking about.

But in brief, the Inventor HAT Mini is based around the same Nuvoton MS51 chip that’s in the IO Expander, so it utilitises the IO Expander’s library, which also serves as a foundation for other MS51-based products like their Weather HAT, RGB Potentiometer and RGB Encoder.

In fact, I was somewhat surprised to learn that the support classes for things like Motor and Encoder for the Inventor HAT Mini aren’t in its library but in the IO Expander’s library. The justification is that people might use things like motors from these other MS51-based Pimoroni products.


FYI, the MS51 (AKA MCS-51) is an 8051-compatible 8 bit microcontroller, an architecture that was originally designed back in 1980 but is still (with performance and many other improvements) still being actively used a lot in industry, and someone at Pimoroni clearly saw its value.

Now, the RP2040 is being used by Pimoroni in a similar fashion such as the Inventor 2040 or the Yukon, but their support libraries aren’t based on the IO Expander given they’re not using an MS51 chip (i.e., the IOE library is CPython for the Pi, whereas the RP2040 is a microcontroller whose libraries are C/C++ and MicroPython).

From your descriptions, nothing immediately comes across as incorrect with the code. Did you try the read_encoders.py example and get the same results?

If values are not changing with that example, it could be a hardware problem or incompatibility. Have you got any scoping tools you can hook up to your encoders to confirm they are producing a signal? I know you’re using Pololu encoders rather than our MMME, so perhaps they require pull-ups / pull-downs on the signal lines to produce a pulse (which our MMME’s do not)? Or it could be that the magnet is too far away from the encoders for a signal to be picked up.

If the encoders are absolutely producing a signal then that either means the signal is not getting to the IHM or it is a code issue (but again, nothing seems incorrect there).

You’re correct that I’m using Pololu N20 motors (#5197) with the encoders already attached (and under a small black plastic shell). Pimoroni and Sparkfun both have their board connections reversed from what Pololu use, so I had to take a sharp object and de-pin all of the JST SH cables to reverse the wires at one end. But from the schematics it seems that they’re simply a direct swap so this should not be the issue. Given the motors actually work is hopefully proof of this.

I ran read_encoders.pyand spun the wheels by hand, but did not see any evidence of the encoders reading any ticks. In my instrumented copy of the library I’m still seeing:

__take_reading: raw count: 0; change: 0; local_count: 0

Given the motors are brand new and the encoders were installed by Pololu I’m not thinking this is a hardware problem, more likely some kind of incompatibility. I couldn’t find anything about whether pull-ups or pull-downs are required, though from the Encoder schematic diagram on page 3 it appears they have a 10K pullup.

I’m sure resolving this would be helpful for everyone else using Pololu N20 motors, which are very popular in the hobby market.

At this point I would start re-checking your assumptions. Yes, you can drive the motor, but that has no relation to whether the encoder is powered and working. They’re completely separate circuits.

If I was personally encountering this issue I would assess the following:

  • Does the Pololu motor work in isolation? I.e. if I provide 3v3 and GND to the encoder, do I see the voltage of A and B change as I turn the motor slowly, if I probe it with a Multimeter
  • Did I actually swap the wiring correctly? I’d post pictures for other people to see too, as they may be able to spot something I have not.
  • Do I see signals on A and B when plugged in and powered by the Inventor HAT Mini, if I probe it with a Multimeter via the exposed pins on the rear of IHM’s connectors (can be difficult)
  • Are all my motors affected or is it just one? Are all the IHM ports affected or just one?

If I was able to answer all of those favourably, then that confirms its either a software or hardware issue with the Inventor HAT Mini, and I’d reach out to support@pimoroni.com for help or a replacement, explaining the diagnosing I had just done.

I suggest you try all that.

Yes, I understand there are effectively two separate circuits, the motor (M+ and M-) and the pair of encoders (Vcc, GND, A and B).

The reason I’m certain that the motors working proves the base assumption of the wiring being okay is as follows. After reversing the cables, comparing the wires this is how they match:

           Pololu    IHMini
Red:       M1        MP
Black:     M2        MN
Blue:      Vcc       3V3
Yellow:    A         A
White:     B         B
Green:     GND       GND

This is as according to the respective schematics from Pololu (as already posted above) and Pimoroni’s Inventor HAT schematic.

I have confirmed that I swapped the wires exactly as reversed, on all four motors (I have a pair of Inventor HATs on this robot). I was particularly meticulous about this when I de-pinned and re-pinned the cables as I knew once the robot was assembled it would be really difficult to gain access to them, since the motors are underneath everything else. The ones on the front of the robot are at least visible:

The order of the wires is: red-black-blue-yellow-white-green at one end, and the exact reverse of that at the other. Attached are photos at each end. You can see the motor side is reversed (i.e., the motors have some visible markings on them and the wire colors are backwards, e.g., green is now M1/M+ rather than GND).

In the order of your bullet points:

  • I’ll have to somehow check this.
  • Yes, as described above.
  • The big problem with these JST SH cables is it’s pretty much impossible to intercept signals between the connector on the HAT and the motor unless I were to cut the wires, and unfortunately the HAT only provides Dupont connectors for M1 and M2, not the encoders. I do have the wiring harnesses from before I swapped out some older motors for these newer, more powerful ones so I may be able to rig up a harness with some kind of intercept in the middle.
  • I’ve tried this on all four motors and they’re all the same.

I’ve not tried your first bullet point yet, so I’ll try to wire up a test rig. I may be able to do this without dissembling the robot, which at this point is a lot of time. I can provide Vcc and GND and then wire A and B either to GPIO pins or to the scope and then spin the motors.

Thanks for your input on this. Hopefully I can sort out what’s going on here.

Well, I’ve rigged up a direct connection between the encoder A and B pins to two GPIO pins on the Pi, and when manually spinning the motor I can see the pulses on those pins. So I’ve confirmed that there’s no issue with the encoders: if they’re provided with power they will return pulses as expected.

I’ve then reconnected the Inventor HAT Mini to the motors and ran a modified read_encoders.py script that turns on the motors, and I’m just not seeing any ticks from the encoders at all. I’ve confirmed that the GND and Vcc wires from the motors are connected to the correct pins on the HAT (pins 1 and 4, green and blue resp.), with A (pin 3, yellow) and B (pin 2, white) as per the photo I posted.

It would really help if someone could clarify (for once) what exactly is the pinout, not on the schematic, but as the pins appear on the HAT when viewed from above:

Listing what pins 1-6 are, in order, would remove any confusion whatsoever as to the pins and wire colors, which may be contributing to this.

Interesting! Thank you for testing that. So the encoders are fine (yes I know you said that before but this is actual evidence), but there’s still unknowns whether the signals are not getting to the IHM or if the IHM firmware/software is not behaving correctly.

Unless I missed it (quite likely!), I don’t believe you specifically asked for a picture of the physical pinout before. Anyway, here you go:

  • 1 = MOT +
  • 2 = MOT -
  • 3 = 3.3V
  • 4 = ENC A
  • 5 = ENC B
  • 6 = GND

About that wiring setup your mentioned for connecting to the Pi, IHM actually supports 2 additional encoders via its GPIO pins. Perhaps you could try wiring the motor up to GPIO 1 and GPIO 1 and running this example inventorhatmini-python/examples/extras/gpio_encoder.py at main · pimoroni/inventorhatmini-python · GitHub (connecting 3.3V and GND of course)

If that works, then that points to the firmware/software of IHM being correct, but there being an electrical issue between the motors and IHM (either in the cable, or in the PCB itself)

If that does not work, then that is certainly a firmware/software issue, though I honestly could not tell you what would be failing there if the program is not returning any error. Incorrectly flashed at our factory?? I don’t know.

That would be a case of reach out to support for a replacement, and to return your current units so I can test them personally. It may also be worth getting our cables bundled in at the same time too.

Here’s the full function header for encoders on the GPIO pins.

def encoder_from_gpio_pins(self, channel, gpio_a, gpio_b, direction=NORMAL_DIR, counts_per_rev=ROTARY_CPR, count_microsteps=False):

You’ll need to set count_microsteps to True to accurately detect each motor encoder count (otherwise it just counts every 4).

Thanks, that clarifies I am at least using the right pins. This will clearly take a bit more time to debug. I’ve ordered some Delrin plastic to replace the lower 3mm plate on the robot with a 5mm plate, so that will require deconstructing much of the robot, which will be a good time to isolate a single Inventor HAT Mini.

Before the plastic arrives I was trying out hooking a TinyFX to an IO Expander via the former’s Qwiik connector (something I’ve done on a previous robot), and I noted that the IOE constructor that was called for that is not the same one as for the Inventor HAT Mini (I’d added print statements to each so I could tell).

Given the strange behaviour of the board needing to be created in the starting script rather than in a class (which I’ve described before, i.e., I pass board into the class as a workaround), I’m wondering if there’s some clue there. The Encoders are clearly getting created and I can see the loop running, but there just isn’t anything happening in terms of the A/B signals actually getting read. I’m just wondering if there’s some weirdness where the actual instances of the class I’m calling aren’t the instances being created and used. Or something strange like that…

I’ll hopefully in the next few days get the plastic and can try an isolated experiment with a fresh SD card, a Pi Zero and just a single Inventor HAT Mini.

Okay, a brief progress report. I’ve deconstructed the robot to do some structural work, so it’s now just a brand new OS on a Pi Zero (my Pi Zero 2 W died, unfortunately), one of the two Inventor HAT Minis, and two motors. I’ve verified the wiring as per your documentation above, and it all appears fine.

I ran read_encoders.py and turned the wheels manually, but there is no indication on the console that the encoders are being read.

So the next thing to try will be with the gpio_encoder.py using the GPIO pins, which will require me creating a harness for that. Tomorrow.

I should reiterate that when I plugged the encoders directly into the Pi’s GPIO pins using a simple GPIO reader script, I could see signals changing when I rotated the wheels.

Though I’m curious: I executed the motor_profiler.py script and it spun the motor and generated a console output like:

[...]
Duty = 0.67, Expected = 3.6180000000000003, Measured = 0.0, Diff = 3.6180000000000003
Duty = 0.68, Expected = 3.6720000000000006, Measured = 0.0, Diff = 3.6720000000000006
Duty = 0.69, Expected = 3.726, Measured = 0.0, Diff = 3.726
Duty = 0.7, Expected = 3.78, Measured = 0.0, Diff = 3.78
Duty = 0.71, Expected = 3.834, Measured = 0.0, Diff = 3.834
Duty = 0.72, Expected = 3.888, Measured = 0.0, Diff = 3.888
[...]

So is “Measured = 0.0” an indication of no encoder data?

Thanks for the update.

That is correct.

I honestly do not understand what is going on here. Maybe your test with the GPIO pins will shed some light (especially if you can probe/scope the pins).

In the meantime I will see if we can get some of those Pololu encoders in for testing here. Perhaps there’s some conflict between their onboard pull-ups and the internal ones of the IHM??

Okay, so a theory from our lead engineer. Because the Pololu encoders use open drain hall effect sensors, the signal voltages may not be going all the way up to 3.3V and down to GND, and so IHM is having difficulty detecting them.

With a multimeter could you measure both the high and low voltages of the encoder pins, both with your original set-up you used to confirm signals were being received, and once you have the gpio_encoder.py wiring set up for IHM.

I ran gpio_encoder.py and didn’t see anything different, i.e., no ticks.

So I wired things up for my scope, here’s my setup:

I created a little pin-interceptor board, with the wires coming up from the Pololu motor and on to the Inventor HAT Mini. I’ve hooked one of the encoder pins to my Iwatsu scope.

When running single_motor.py I can see the output from the encoder going from 0 to 3.3V. I’ve marked on the screen the 3.3V and 5V levels with some masking tape.

But without changing the wiring at all, running read_encoder.py and hand-spinning the wheel I see no output on the console indicating the encoders are counting, e.g.,

A = 0.0, B = 0.0, 
A = 0.0, B = 0.0, 
A = 0.0, B = 0.0,

So I’m a bit mystified…

Though on a lark I added some print lines to encoder.py, one on the constructor, another on __take_reading(). When I run single_motor.py, the one on the constructor prints this to the console:

Encoder channel=1; pins: (3, 4); counts per rev: 3000
Encoder channel=2; pins: (26, 1); counts per rev: 3000

and the one on __take_reading() never gets called.

Thanks for that thorough testing. Those voltages do not seem to be off, which is good, but bad for getting us further with the diagnosis. Equally, the gpio_encoder.py set up also not showing ticks does not help.

Unless the encoders are doing something immensely stupid like outputting two completely in phase A and B signals, it all points to either a Python configuration issue or a software library issue.

Sadly that second print not getting called makes sense. single_motor.py does not call any encoder functions after that initial constructor call. A more representative test would be the read_encoders.py example.

I’ve asked a team member to set up a IHM with fresh software and see what results they get. I’ll report back with the results.

No worries, my mistake. I can report that read_encoders.py doesn’t report anything either. My print line returns the raw count and raw change from within __take_reading():

Encoder channel=1; pins: (3, 4); counts per rev: 600
Encoder channel=2; pins: (26, 1); counts per rev: 600
raw count: 0; raw change: 0
A = 0.0, 
raw count: 0; raw change: 0
B = 0.0, 
raw count: 0; raw change: 0
A = 0.0, 
raw count: 0; raw change: 0
B = 0.0, 
raw count: 0; raw change: 0
A = 0.0, 
raw count: 0; raw change: 0
B = 0.0,

This is with me spinning the motor by hand. So __take_reading() is getting called, but there are no ticks registered.

This is running Python 3.11.2 on a Raspberry Pi Zero W V1.1 running RPi OS Lite (headless Bookworm).

I thought we should at least rule that out so I ran it up again with both channels:

…looks fine to me.