RGB Encoder Breakout I2C Reguster Data

Hi I would like to use the RGB Encoder Breakout with a Microchip PIC18F.

I Have no issues using the I2C on the PIC but does anyone have any info on using the RGB encoder as a generic I2C device and have any register info or how to get to the encoder data and set the RGB LED

The only help I got from Pimoroni was to point the Python libraries and try and work it out from there.

Regards Andrew

So I was halfway through trying to merge together their code in my head from the github pages and decided sod it, this is too tedious. So, I hooked up a Pico Lipo to an RGB encoder breakout and used a logic analyser to sniff the traffic.

Now, the encoder breakout has a microcontroller undearneath it (a Nuvoton chip of some sort), and that needs configured to read the encoder on the right pins, use the right pins as PWM devices for the LEDs , present that as an i2c device etc. so there’s actually a LOT of traffic before you even tell the breakout to do anything.

If you run the bare minimum setup code:

from pimoroni_i2c import PimoroniI2C
from breakout_encoder import BreakoutEncoder

PINS = {"sda": 0, "scl": 1}

i2c = PimoroniI2C(**PINS)
enc = BreakoutEncoder(i2c)

Then there is quite a lot of config code which gets thrown around.

STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0xe2;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xfa;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x6a;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc9;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x20;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc9;
ACK;
DATA = 0x20;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x71;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0xdb;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x72;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x40;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x71;
ACK;
DATA = 0xdb;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x72;
ACK;
DATA = 0x40;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc2;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x20;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc2;
ACK;
DATA = 0x20;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x40;
ACK;
DATA = 0x0d;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x06;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
DATA = 0x06;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x98;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x23;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
DATA = 0x23;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc4;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x04;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc4;
ACK;
DATA = 0x04;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x50;
ACK;
DATA = 0x0a;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x71;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0xdb;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x72;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x40;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x71;
ACK;
DATA = 0xdb;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x72;
ACK;
DATA = 0x40;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x40;
ACK;
DATA = 0x06;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x40;
ACK;
DATA = 0x06;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x05;
ACK;
DATA = 0x3c;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x04;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x01;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x04;
ACK;
DATA = 0x01;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x04;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x01;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x04;
ACK;
DATA = 0x01;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x06;
ACK;
DATA = 0x00;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x99;
ACK;
DATA = 0xff;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x91;
ACK;
DATA = 0x00;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x80;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
DATA = 0xc0;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x80;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9f;
ACK;
DATA = 0x01;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc9;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x20;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0xc9;
ACK;
DATA = 0x20;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x26;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
DATA = 0x26;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x80;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
DATA = 0x80;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x98;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x23;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
DATA = 0x23;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x50;
ACK;
DATA = 0x05;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x06;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
DATA = 0x06;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x26;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
DATA = 0x26;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x80;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
DATA = 0x80;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x98;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x23;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
DATA = 0x23;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x50;
ACK;
DATA = 0x01;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x06;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x9e;
ACK;
DATA = 0x06;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x26;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x96;
ACK;
DATA = 0x26;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x80;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x98;
ACK;
DATA = 0x80;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x98;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
STOP;
START;
Read from 0x0f - R/W = 1;
ACK;
DATA = 0x23;
NACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x73;
ACK;
DATA = 0x98;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x74;
ACK;
DATA = 0x23;
ACK;
STOP;
START;
Write to 0x0f - R/W = 0;
ACK;
DATA = 0x50;
ACK;
DATA = 0x00;
ACK;

After tinkering with the LED the relevant registers are:

Red: 0xCB
Green: 0x9B
Blue: 0x9C

And they’re all 8-bit values.

If you’re mad enough to translate all the config code into your own program let me know, and I’ll poke at which bytes change for the rotary encoder, because I’m afrad I have to run here.

Hi Shoe

You sir are a star, I thought somehow it was a simple list of registers.

I was looking at Qwiic Twist Register Map and this was a simple list of registers and what they did

I wrongly assumed this was similar, I hooked up to my PIC and started trying to read from the IC2 address registers from what I assumed was zero onwards but they all returned 0.

But if you can get what registers are used for the encoder that would be very helpful.

Thanks again for your time and effort

Regards Andrew

Hi Shoe

Just a thought, can the RGB encoder be used at default or does it have to be configured, I assume that there will be some registers that just have the current encoder status or is this too simple.

Regards Andrew

I actually don’t know exactly how the Nuvoton system works, from skimming the code I think the default firmware just gets it to run as an I2C device, and then has functions to let you set “pin X is of function Y” over i2c. For example, there was somewhere in the encoder breakout code with an output function which took an 8-bit value for a pin, and then an 8-bit value to specify PWM, and set this as “LED_GREEN”. So, I think the Nuvoton chip doesn’t know it is connected to an RGB encoder breakout until it is configured, hence the long list of registers which are set when the device is initialised.

It should be possible to map all of the registers and values in that logic analyser dump to constants in the code, but that will take a while. I’ve not used the Sparkfun Twist, but it looks like it works similarly except the Twist firmware has all of the basic config pre-programmed perhaps? That way it would work immediately when plugged in, whereas the Pimoroni version needs configured first.

Hi Shoe

Ok I understand, it is a pity that Pimoroni did not state that as the device could be used with any micro controller that could talk over I2C and a nice write up would be so helpful without reverse engineering other code.

One question on the registers, do they start from a certain number and do you know how many there are.

Regards Andrew

Maybe this will help you: https://github.com/pimoroni/pimoroni-pico/tree/main/drivers/ioexpander

This is the C++ driver, and porting this to the PIC should be feasible. Once you have ported this lib, then you should be able to use it for application level tasks (setting LEDs and so on).

BTW: nice to see someone still using PICs :-)

Hi Bablokb

Thank you for the update, for your information I am using a PIC18F27K42 for a client.

It is used to read a couple of LED pushbuttons and a PIR sensor, this is a small office (one or two desk) unit and controls a large LED panel and fan.

I have done the development work but the end client wanted a separate controls for the fan, and I saw the RGB encoder units on Pimoroni and thought they were simple I2C devices with a register map so I could just read the encoder status and set the RGB led through reading and writing to the register map.

Regards Andrew

Hi Andrew,

have you thought about using the “Rotary Encoder (Illuminated)” instead? This is a more traditional encoder with a builtin RGB (common anode) LED. But of course it needs more pins.

Hi Bablokb

I had already thought of that and the reason is the number of wires / cables from the main control unit.

My existing development is using an 4 meter RJ45 cable to connect to the main control unit.

This is currently housing two push button switches with built in LED plus a PIR sensor.

The wiring / cable arrangement is as follows.

Core numbers and usage

1 = 0 Volt DC
2 = 5 Volt DC
3 = First Switch
4 = First Switch LED
5 = PIR Sensor
6 = Second Switch
7 = Second Switch LED.
8 = Spare

I thought of replacing the second switch with an I2C device as this only uses 2 cables SCL and SDA.
still leaving 1 core spare, Also the I2C runs at a much slower speed than say SPI.

Regards Andrew

I already have a couple of small encoders with a built in switch (de press) but this would require 3 cores and I still need a way of indicating it had been pressed, LED or something which would require an additional cable / core.

The reason for the RJ45 cable route is they are plentiful and is easy to incorporate an RJ45 socket on the PCB

Hi Bablokb

Please see a photo of the unit, the black unit is the control box with two push buttons with LED’s with s a PIR sensor in the middle, this unit is connected to the metal box housing the PIC board via the blue RJ45 cable.

One of the LED push buttons needs to be replaced with some sort of encoder / LED and the Pimironi device looked a good fit.

Hi Andrew,

very nice project. Never thought of using RJ45 this way, but it does make sense.

Have you thought about using a dedicated MCU for the front-end (control box)? A Pico would do fine and support all the components. Communication with the LED-controller could use UART with your own protocol.

Yes, this is work and adds complexity, but porting the Nuvoton-control-library also seems time-consuming.

Bernhard

Hi Bernhard

Yes I had thought of that but trying to limit the PCB to just one.

I did not realise that the information would be so little, I originally looked at the SparkFun

SparkFun Qwiic Twist - RGB Rotary Encoder Breakout, this is similar AND has a full documented I2C implementation but is only 3.3 volt’s and my PIC uses standard 5 volt
I assumed the Pimoroni would be the same and have a simple I2C interface as well.

Regards Andrew

Also the room inside the unit that houses the switches is quite small with not much room and if there was a PCB it would need to be a custom size.

Another option was to use a port expander using I2C, like the Microchip MCP23017-E/SO.

This would give me more than enough I/O lines and I could do everything over I2C.

The one thing I am not sure even if I get the Pimoroni RGB encoder to work is how the I2C works over a distance of 5 meters, this is the max length the distance is required from the control box to the PIC PCB, but the I2C speed can be lower. I think this is about 400 Khz.

Regards Andrew

Do you know about the active I2C-terminator from Adafruit: Adafruit LTC4311 I2C Extender / Active Terminator - STEMMA QT / Qwiic

One other note: there is an alternative C++ implementation, which might be easier to port: GitHub - ZodiusInfuser/IOExpander_Library: An Arduino port of the Python library for the Nuvoton MS51 Pimoroni IO Expander Breakout. It also has a class for the encoder.

Maybe chatGPT can convert this to xc8 code (oh no, not really).

Hi Bernhard

No I was not aware, I notice they are using an RJ45 cable with breakout boards to test it.

Cool Components do a simple RGB Encoder on a breakout board, this has the encoder, RGB and switch.

RGB Encoder Cool Components

I could get away with just a single LED does not matter what colour as I send it a PWM signal to control the brightness, could tie all together to get a white light.

all I then need is two signals for the A and B from the encoder and I currently have one core spare - number 8, so this could work.

I have already done the PIC code for an encoder but the ones I have have a switch as well, without extra cores I cannot do this but this could still work.

Regards Andrew