Skip to content

drivers: led: is31fl3197 Arduino GIGA#96821

Open
KurtE wants to merge 6 commits intozephyrproject-rtos:mainfrom
KurtE:giga_shield_leds
Open

drivers: led: is31fl3197 Arduino GIGA#96821
KurtE wants to merge 6 commits intozephyrproject-rtos:mainfrom
KurtE:giga_shield_leds

Conversation

@KurtE
Copy link
Contributor

@KurtE KurtE commented Sep 30, 2025

Start of support for the is31fl3197 LED controller used by Arduino in the Arduino Giga Display shield.

So far there is just enough implemented that allows me to control the LEDS, to the same extent as our version of the library we implemented that runs on ArduinoCore-zephyr

@pillo79 @facchinm - I am not sure what direction Arduino is heading with functionality such as this.
Add support to zephyr? Or do it external as libraries within ArduinoCore-zephyr (either integrated within the zephyr builds or external libraries), This is also true, about for some of the other
features of the display, like the display itself. Should there be samples setup to actually
display something on the display, preferably without the need for LVGL.

So far this only has the minimal support for setting up to 4 LEDS (the GIGA only uses 3).

Added sample sketch that runs on the GIGA. Currently it works with just
west build -p -b arduino_giga_r1//m7

I put the device tree into sample/boards. Potentially it should be defined as part of the
overlay in the arduino_giga_display_shield, in which case the west build should
define the shield as part of the command.

Let me know know what you think.
Thanks

@KurtE KurtE force-pushed the giga_shield_leds branch 6 times, most recently from fd309e3 to b69ff55 Compare October 1, 2025 12:30
@KurtE
Copy link
Contributor Author

KurtE commented Oct 1, 2025

@pillo79 @facchinm and all - As far as I can tell, the basic parts of this PR are working. That is I can set the RGB led on the
giga display, the same as we have setup in the Arduino library. I know that this it currently does not fully implement all
of the features of this LED chip driver, but if this is pulled in, others can add it, especially if there are boards that use some of
the different features.

I am going to mark this as ready to review, however I know there are issues with twister, as it appears like the GIGA board
and guessing probably likewise the Portenta H7 have not been used as a setup for any of the samples. One issue I ran into
was the GIGA was not marked in the yaml file as supporting I2C, which I have added and the twister run goes a lot farther

Currently I have the sample setup to work with just specifying board as arduino_giga_r1//m7, as the shield currently
does not define the LED info. I don't know if one can specify an overlay file for a shield under a samples
boards directory, like maybe set the name as arduino_giga_display_shield.overlay? I may try it again with build line like:
west build -p -b arduino_giga_r1//m7 --shield arduino_giga_display_shield
I believe this can also be made to work with a portenta H7 with the mid carrier board. As I believe it's connector
to for the display shield includes an I2C object connected to those pins. May try that out of curiosity.
But I don't believe any of the Arduino Portenta carrier boards or shields are defined as shields in zephyr.
EDIT: - Portenta connector does not have I2C connections to the Display shield. So that won't work.
(Unless you use jumpers, which I have tried from I2C0 pins on Mid Carrier to the SCL/SDA pins on the
camera connector)

Also should mention: that the code is hard coded similarly to the is31fl3194 code in that it assumes RED/GREEN/BLUE
as the LEDS and it assumes they are on OUT1-3. I have the start of OUT4, that I again hard coded to WHITE:
But suppose I use one of these drivers for a stop light and the leds are: Green, Yellow, Red. Also should the colors
and slots be dictated by the colors passed in. For example if I have only two colors can I specify that I am using Out 2 and 3?

But before I go much further, it would help to know if this is a direction that Arduino wishes to go.

Copy link
Contributor

@simonguinot simonguinot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KurtE,

Thanks for this PR !

Can it be merged with the driver is31fl3194 ? into a is31fl319x driver ?

I didn't check but I am asking because in Linux the leds-is31fl319x driver supports the IS31FL319{0,1,3,6,9} models...

@KurtE
Copy link
Contributor Author

KurtE commented Oct 1, 2025

Can it be merged with the driver is31fl3194 ? into a is31fl319x driver ?

I didn't check but I am asking because in Linux the leds-is31fl319x driver supports the IS31FL319{0,1,3,6,9} models...

Good question, both of them have stuff in common, and I started off that way, but then found many of the registers
appear to be different. Like fixed max current here versus set. Also can control which channels are active or not,
maybe you can with the 94 as well. They probably can be combined, but unclear how much they would share in
common.

@pillo79 As the one who originally did the 94 version, What do you think?

@pillo79
Copy link
Contributor

pillo79 commented Oct 2, 2025

Thanks Kurt! I also think this is worth merging with the existing driver, there's a lot of common code, and the few exceptions look like they can be specifically handled. Take a look at the end of drivers/led/lp50xx.c, for example, in how it defines id and max_leds depending on one of the many HW models. If you need many of these, you can also store id directly or a clever enum index into a register table, etc.

Oh, and re/ Arduino attitude, upstream is the way to go so 👍 on that! By all means make it part of the Giga Display shield, that way it will be automatically available everywhere you use that and not only in the test code 🙂

@mjs513
Copy link
Contributor

mjs513 commented Oct 2, 2025

@KurtE
Had to pretty much do the same thing for trying to merge the OV7675 into the OV7670 driver. Heres the link I used as a base to the LED driver:

zephyr/drivers/led/lp50xx.c

Lines 391 to 413 in 8843fd9

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5009
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5009, LP5012_NUM_MODULES)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5012
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5012, LP5012_NUM_MODULES)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5018
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5018, LP5024_NUM_MODULES)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5024
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5024, LP5024_NUM_MODULES)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5030
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5030, LP5036_NUM_MODULES)
#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT ti_lp5036
DT_INST_FOREACH_STATUS_OKAY_VARGS(LP50XX_DEVICE, 5036, LP5036_NUM_MODULES)

@KurtE
Copy link
Contributor Author

KurtE commented Oct 2, 2025

Thanks @pillo79 @mjs513,

Thanks Kurt! I also think this is worth merging with the existing driver

Can do. At least the basics, that the display shield uses. Don't have any other things to try more advanced stuff on.

My first thoughts were to hack it and drive everything off of Product ID. I believe the 94 has a fixed ID of 0xce
Whereas the 97s ID is the I2C 8 bit address: Currently checked like:
if (prod_id != (config->bus.addr << 1)) {

Oh, and re/ Arduino attitude, upstream is the way to go so 👍 on that! By all means make it part of the Giga Display shield, that way it will be automatically available everywhere you use that and not only in the test code 🙂

I agree, that in the Zephyr world, having it all here under the shield is the way to go and not have to put the DT info in the
test sketch. I had it there to begin with, with the testing, will move it back there.

Some questions on this, should I then remove the GIGA from the integration section of the sample.yaml? As I don't believe
there are any other examples for them?

Include Portenta H7? I have a overlay I experimented with yesterday, that had the LEDS working on an H7 plugged into a MID
Carrier, which has a display shield plugged into it. But it only worked if I put jumpers to I2C pins. I tried it as the Display Shield
schematic that looked like there was a connection for Portenta with I2C pins... Just not sure how.
Guessing probably punt for now.

Main Secondary Question:

Currently with ArduinoCore-zephyr if I wish to build for a GIGA, it includes all of the Giga Shield stuff, regardless if I actually have a shield.

With the MBED version, only the basics for the display are included with a board release in the library Arduino_H7_video, The
rest of the functionality is contained in libraries you need to install.

I decided to try first with the LEDS as it is pretty self contained and not very large. Our zephyr version of Arduino library for this:

#include "GigaDisplayRGB.h"

GigaDisplayRGB::GigaDisplayRGB(){
}

void GigaDisplayRGB::begin()
{
    Wire1.begin();
    writeByte(0x1, 0xF1);
    writeByte(0x2, 0xFF);
}

void GigaDisplayRGB::on(uint8_t r, uint8_t g, uint8_t b)
{
    writeByte(0x10, r);
    writeByte(0x11, g);
    writeByte(0x12, b);
    writeByte(0x2b, 0xc5);
}

void GigaDisplayRGB::off()
{
    on(0, 0, 0);
}

void GigaDisplayRGB::writeByte(uint8_t subAddress, uint8_t data)
{
    Wire1.beginTransmission(0x50);
    Wire1.write(subAddress);
    Wire1.write(data);
    Wire1.endTransmission();
}

Longer term question: how much of the stuff in the ArduinoCore-zephyr overlays/config files maybe should be
migrated upstream, but that is beyond this scope.

@KurtE
Copy link
Contributor Author

KurtE commented Oct 2, 2025

Quick question:

Current driver is pretty hard coded to RED/GREEN/BLUE and if I read it correctly it is assumed that the user
always has to pass in 3 colors in that order, but the output code may change the order of them as maybe the
channels to the hardware are not wired that way?

Now with the 97 it can handle up to 4 channels, I started off hard coding the 4th channel to WHITE for maybe
RGBW type LEDS.

But the LED Header file defines other colors as well:

#define LED_COLOR_ID_WHITE      0
#define LED_COLOR_ID_RED        1
#define LED_COLOR_ID_GREEN      2
#define LED_COLOR_ID_BLUE       3
#define LED_COLOR_ID_AMBER      4
#define LED_COLOR_ID_VIOLET     5
#define LED_COLOR_ID_YELLOW     6
#define LED_COLOR_ID_IR         7
#define LED_COLOR_ID_MAX        8

There feels like there should be way to specify different colors? And maybe mappings?
Ok to ignore for now?

Also is there any reason things are hard coded to have to set all of the LEDS each time? That is should it be
allowed to only set the Green LED, by passing in index 1, count 1?
That is can there be a few different pieces of code that maybe change a state of one LED. Sort of along the line that
GIGA has similar like LED (minus driver)
image
And each of the three colors are often driven by different places in the code base.

Thanks

@KurtE
Copy link
Contributor Author

KurtE commented Oct 2, 2025

Sorry, another quick question: Is there any Boards other than the Nicla Sense ME that uses is31fl3194?
(I don't have one of those to test that I did not break it)

@KurtE KurtE force-pushed the giga_shield_leds branch from 8e0f5b9 to f2c2e67 Compare January 5, 2026 20:53
&is31fl319##id##_config_##n, POST_KERNEL, \
CONFIG_LED_INIT_PRIORITY, &is31fl319x_led_api);

#ifdef CONFIG_DT_HAS_ISSI_IS31FL3194_ENABLED
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to support multiple "compatible" values,
this is a cleaner way to write it than using #undef.
Just for your reference.

#define SSD1306_DEFINE(node_id) \
static struct ssd1306_data data##node_id; \
static const struct ssd1306_config config##node_id = { \
.reset = GPIO_DT_SPEC_GET_OR(node_id, reset_gpios, {0}), \
.supply = GPIO_DT_SPEC_GET_OR(node_id, supply_gpios, {0}), \
.height = DT_PROP(node_id, height), \
.width = DT_PROP(node_id, width), \
.segment_offset = DT_PROP(node_id, segment_offset), \
.page_offset = DT_PROP(node_id, page_offset), \
.display_offset = DT_PROP(node_id, display_offset), \
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
.segment_remap = DT_PROP(node_id, segment_remap), \
.com_invdir = DT_PROP(node_id, com_invdir), \
.com_sequential = DT_PROP(node_id, com_sequential), \
.prechargep = DT_PROP(node_id, prechargep), \
.color_inversion = DT_PROP(node_id, inversion_on), \
.ssd1309_compatible = DT_NODE_HAS_COMPAT(node_id, solomon_ssd1309fb), \
.sh1106_compatible = DT_NODE_HAS_COMPAT(node_id, sinowealth_sh1106), \
.ready_time_ms = DT_PROP(node_id, ready_time_ms), \
.use_internal_iref = DT_PROP(node_id, use_internal_iref), \
COND_CODE_1(DT_ON_BUS(node_id, spi), (SSD1306_CONFIG_SPI(node_id)), \
(SSD1306_CONFIG_I2C(node_id))) \
}; \
\
DEVICE_DT_DEFINE(node_id, ssd1306_init, NULL, &data##node_id, &config##node_id, \
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1306_driver_api);
DT_FOREACH_STATUS_OKAY(solomon_ssd1306fb, SSD1306_DEFINE)
DT_FOREACH_STATUS_OKAY(solomon_ssd1309fb, SSD1306_DEFINE)
DT_FOREACH_STATUS_OKAY(sinowealth_sh1106, SSD1306_DEFINE)

&is31fl319##id##_config_##n, POST_KERNEL, \
CONFIG_LED_INIT_PRIORITY, &is31fl319x_led_api);

#ifdef CONFIG_DT_HAS_ISSI_IS31FL3194_ENABLED
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to support multiple "compatible" values,
this is a cleaner way to write it than using #undef.
Just for your reference.

#define SSD1306_DEFINE(node_id) \
static struct ssd1306_data data##node_id; \
static const struct ssd1306_config config##node_id = { \
.reset = GPIO_DT_SPEC_GET_OR(node_id, reset_gpios, {0}), \
.supply = GPIO_DT_SPEC_GET_OR(node_id, supply_gpios, {0}), \
.height = DT_PROP(node_id, height), \
.width = DT_PROP(node_id, width), \
.segment_offset = DT_PROP(node_id, segment_offset), \
.page_offset = DT_PROP(node_id, page_offset), \
.display_offset = DT_PROP(node_id, display_offset), \
.multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
.segment_remap = DT_PROP(node_id, segment_remap), \
.com_invdir = DT_PROP(node_id, com_invdir), \
.com_sequential = DT_PROP(node_id, com_sequential), \
.prechargep = DT_PROP(node_id, prechargep), \
.color_inversion = DT_PROP(node_id, inversion_on), \
.ssd1309_compatible = DT_NODE_HAS_COMPAT(node_id, solomon_ssd1309fb), \
.sh1106_compatible = DT_NODE_HAS_COMPAT(node_id, sinowealth_sh1106), \
.ready_time_ms = DT_PROP(node_id, ready_time_ms), \
.use_internal_iref = DT_PROP(node_id, use_internal_iref), \
COND_CODE_1(DT_ON_BUS(node_id, spi), (SSD1306_CONFIG_SPI(node_id)), \
(SSD1306_CONFIG_I2C(node_id))) \
}; \
\
DEVICE_DT_DEFINE(node_id, ssd1306_init, NULL, &data##node_id, &config##node_id, \
POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1306_driver_api);
DT_FOREACH_STATUS_OKAY(solomon_ssd1306fb, SSD1306_DEFINE)
DT_FOREACH_STATUS_OKAY(solomon_ssd1309fb, SSD1306_DEFINE)
DT_FOREACH_STATUS_OKAY(sinowealth_sh1106, SSD1306_DEFINE)

};

#ifdef CONFIG_DT_HAS_ISSI_IS31FL3194_ENABLED
/* IS31FL3194 model registers and values */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to put it in #ifdef because the model name is designed to be unique. It is also better to put it at the beginning.


#ifdef CONFIG_DT_HAS_ISSI_IS31FL3197_ENABLED
/* IS31FL3197 model registers and values */
#define IS31FL3197_PROD_ID_REG 0x00
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


#define IS31FL3197_CHANNEL_COUNT 4

static const struct is31f1319x_model is31f13197_model = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, there will be no name duplication, so I think it's okay to add __maybe_unused and remove the #ifdef.

@KurtE
Copy link
Contributor Author

KurtE commented Jan 15, 2026

@ALL I removed the #if/endif pairs to enumerate the devices as to mimic
the way it is done in the video\ov767x driver.

It builds and I programmed both a Arduino GIGA R1 as well as an Arduino Nicla Sense ME

@KurtE KurtE requested a review from soburi January 15, 2026 03:33
const struct is31f1319x_model *regs = config->regs;
int i, ret;
uint8_t prod_id, band;
int i, j, ret;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not fixed in the right commit and other lines are not fixed at all, it needs to be fixed

@KurtE
Copy link
Contributor Author

KurtE commented Jan 20, 2026

@thedjnK

not fixed in the right commit and other lines are not fixed at all, it needs to be fixed

Not sure what are saying?

I understand the don't add the third variable to a line that already had two... Which I did that way, as usually find you try
to go with the flow of the existing code. But now I understand is important now to not have three variables defined in
a line when before there was only two, and I later removed it.

However not sure what the "other lines are not fixed" means. The other comments about changing how to handle
the multiple devices types in the enumeration? Or are you suggesting I am supposed to fix lines that are unrelated
to the changes being made that do not reflect your coding standard, such as the line above it?

@KurtE
Copy link
Contributor Author

KurtE commented Jan 23, 2026

@thedjnK - Hopefully I got the variables changed you were concerned about in the right commit.

@KurtE KurtE requested a review from thedjnK January 23, 2026 15:34
/*
* output LED RGB color table
*/
void display_RGB_color_table(const struct device *dev, uint32_t led)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

camel case for functions and variables

}

/*
* output LED RGB color table
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start comments with a capital letter

Comment on lines 224 to 226
/*
* Main
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*
* Main
*/
@KurtE KurtE requested a review from thedjnK January 27, 2026 22:42
Removed the driver knowing anything about what
color each channel is.  When you output to
an LED with a buffer it simply writes the
channels out with buffer.

Also removed the assumptions that the leds
are either a) RGB  or b) 3 channels and
instead use them the way they are defined.

That is for example if we have 4 channels, we
could define an LED with just one color in it
and we could define another with three.

The user can define 4 leds each with color yellow.

For each LED it counts how many buffers were used
by previous LEDs and starts there and outputs N
channels.

Decided it looked cleaner to use helper function
write channels, so implemented it and added it
to the call table.

Updated sample:
It manually computes which channel maps to red, green and blue and then
uses those indexes to map the table of colors to the right channels.
Signed-off-by: Kurt Eckhardt <kurte@rockisland.com>
@KurtE KurtE requested a review from thedjnK February 10, 2026 03:26
thedjnK
thedjnK previously approved these changes Feb 10, 2026
soburi
soburi previously approved these changes Feb 10, 2026
Copy link
Contributor

@simonguinot simonguinot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @KurtE

The driver itself is in a pretty good shape now. Well done.

I only have minor comments. The main thing is the sample. It seems really complicated to me and I suggest we drop the RGB mixing color test.

See my comments below.

help
Enable LED driver for Lumissil Microsystems (a division of ISSI)
IS31FL3194. This chip supports one RGB LED or 3 independent LEDs.
IS31FL3197. This chip supports 4 LEDs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, rephrase. It is quite confusing.

#define DT_DRV_COMPAT issi_is31fl3194

/**
* @file
Copy link
Contributor

@simonguinot simonguinot Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should replace IS31FL3194 with IS31FL319x in the line below.

Still valid. It is now the IS31FL319x LED driver.

* @file
* @brief IS31FL3194 LED driver
*
* The IS31FL3194 is a 3-channel LED driver that communicates over I2C.
Copy link
Contributor

@simonguinot simonguinot Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And you can transform this description into a list of controller models supported by this driver.

@@ -1,3 +1,3 @@
/*
* Copyright (c) 2024 Arduino SA
*
Copy link
Contributor

@simonguinot simonguinot Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may want to add your copyright here.

/*
* Set one color (R or G or B) to all on and then fade it down to off
*/
void fade_one_color(const struct device *dev, uint8_t led, uint8_t led_count,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe led_count would be better named color_count or num_colors,

The third parameter of led_set_color is named num_colors, So it is quite confusing to read that a led_count parameter is passed.

fade_all_channels(led_dev, device_channel_count);

/* Check for RGB LED */
rgb_led = generate_rgb_color_mapping(led_dev);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All the RGB handling seems complicated to me.

IMO, it is enough to iterate the LEDs, display their name, the number of colors, and for each color display the ID or name and perform an action (e.g. off, on, fade).

It would be nice to make it simple :)

Add the registers and the like to the .c file plus add a Kconf file
plus bindings.

Updated sample: to also check for is31fl3197 boards

Signed-off-by: Kurt Eckhardt <kurte@rockisland.com>
Added is31fl3197@50 to overlay

Also updated GIGA board yaml file to say that it supports i2c

And updated sample to mention it

Signed-off-by: Kurt Eckhardt <kurte@rockisland.com>
@KurtE KurtE dismissed stale reviews from soburi and thedjnK via 52bdedd February 12, 2026 23:38
Removed all of the color mixing example code, which
looped at the end of the example.

This included code, that on LED setups which have
RGB colors, would show mixing of the LEDs for
different colors like yellow.  It also striped out
the fading of single colros at the end for those
LED setups that do not have a RGB configured.

Note: not sure if the code now exercises every
API.

Signed-off-by: Kurt Eckhardt <kurte@rockisland.com>

Update main.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Boards/SoCs area: Devicetree Binding PR modifies or adds a Device Tree binding area: Devicetree Bindings area: Documentation Infrastructure area: LED Label to identify LED subsystem area: Samples Samples area: Shields Shields (add-on boards)

10 participants