Skip to content

drivers: led: add led_dac #88259

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jeppenodgaard
Copy link
Collaborator

Add LED driver support for DAC based LED drivers.

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen left a comment

Choose a reason for hiding this comment

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

Using a DAC to drive an LED, not a usual thing to do given how "expensive" those are compared to a PWM :D I'm all for it but we can do better given the "power" we get from using DAC (full control over voltage range, no need for external components, other than the LED of course)


#include <zephyr/device.h>

int led_dac_set_raw(const struct device *dev, uint32_t led, uint32_t value);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The header is not required, the function is only used internally anyway :) if this was to be public it would need to go to include/zephyr/drivers/led, but I don't think this is necessary :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are right. It an attempt as a work around until #87737 is resolved/gets response.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Header removed and function made static.

Comment on lines 12 to 30
/ {
leds {
compatible = "dac-leds";
led_0 {
dac = <&dac1>;
channel = <0>;
resolution = <12>;
};
};
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would use dts indentation, 8 spaces, not a blocker but I think its neater. Two spaces before / { then 8 spaces

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is more or less copy paste from gpio-leds.yaml so I did not really consider the indentation. leds actually needs to be indented that should be fixed.

Is it allowed to use tabs in comments (I think copy pasting example into a device tree source file looks as if indentation is ok, but it isn't since it is 8 spaces instead of tabs)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Its not indented to be copied directly to the devicetree, just to look like a devicetree snippet :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I revisited gpio-leds.yaml and it actually uses tabs. My editor converted them to spaces. I have changed them back to tabs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Never mind. check_compliance.py wont allow tabs after space. I changed indent to 8 spaces.


child-binding:
description: DAC LED child node
properties:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would add a max value here at minimum, possibly even a max and forward current. If we are using a DAC to drive an LED, we should do it right!
image

So add something like

max-voltage-mv:
  type: int
  description: max voltage in millivolt
forward-voltage-mv:
  type: int
  description: led forward voltage in millivolt

This way, the brightness percentage could be converted to a relative intensity quite nicely!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My intention was to use this with an opamp circuit. E.g. using TI's OPA310 example.
This is not clear at all and should be clarified.

I like your suggestion. Maybe it could be added on top of this driver and bindings so both is possible?

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen Apr 8, 2025

Choose a reason for hiding this comment

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

ok, that is a bit of a different story, usually LEDs are tied directly to the pin, with no voltage controlled current source circuit in between. Maybe add a boolean property named linear or current-controlled and voltage-controlled? so the driver knows what "type" of led driving circuit (if any) is used?

Copy link
Collaborator

@bjarki-andreasen bjarki-andreasen Apr 8, 2025

Choose a reason for hiding this comment

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

The reason this is not the case for PWM controlled LEDs BTW is that they are "fully on" / "fully off", so the voltage/current is not varied to control intensity :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

How about renaming dac-leds.yaml to dac-leds-common.yaml to prevent optional unused properties?
Then include that in dac-leds.yaml which has the two properties you suggest to make it possible to convert pct to a relative intensity.
And add another dac-leds-vccs which just includes common and nothing else.

It might be a good idea to have a current limiting resistor in series with the LED and that should probably also be part of the properties if the LED is directly tied and be part of the conversion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

sounds good :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I reworked the driver.

Copy link
Collaborator Author

@jeppenodgaard jeppenodgaard left a comment

Choose a reason for hiding this comment

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

Thanks you for the very quick review @bjarki-andreasen ! I am not sure how to change the code so I've replied to your comments.


#include <zephyr/device.h>

int led_dac_set_raw(const struct device *dev, uint32_t led, uint32_t value);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You are right. It an attempt as a work around until #87737 is resolved/gets response.


child-binding:
description: DAC LED child node
properties:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My intention was to use this with an opamp circuit. E.g. using TI's OPA310 example.
This is not clear at all and should be clarified.

I like your suggestion. Maybe it could be added on top of this driver and bindings so both is possible?

Comment on lines 12 to 30
/ {
leds {
compatible = "dac-leds";
led_0 {
dac = <&dac1>;
channel = <0>;
resolution = <12>;
};
};
};
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is more or less copy paste from gpio-leds.yaml so I did not really consider the indentation. leds actually needs to be indented that should be fixed.

Is it allowed to use tabs in comments (I think copy pasting example into a device tree source file looks as if indentation is ok, but it isn't since it is 8 spaces instead of tabs)?

@jeppenodgaard jeppenodgaard force-pushed the add_led_dac branch 2 times, most recently from 80f790c to 771898d Compare April 9, 2025 12:26
@jeppenodgaard jeppenodgaard force-pushed the add_led_dac branch 2 times, most recently from 39b4813 to af167a8 Compare April 9, 2025 12:53
@simonguinot
Copy link
Collaborator

Hi @martinjaeger. I am not familiar with DAC. Please, let me know what you think of this LED DAC driver.

@kartben kartben requested a review from Copilot April 12, 2025 12:58
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 4 out of 8 changed files in this pull request and generated no comments.

Files not reviewed (4)
  • drivers/led/CMakeLists.txt: Language not supported
  • drivers/led/Kconfig: Language not supported
  • drivers/led/Kconfig.dac: Language not supported
  • tests/drivers/build_all/led/app.overlay: Language not supported
Comments suppressed due to low confidence (1)

drivers/led/led_dac.c:130

  • [nitpick] Consider adding a comment clarifying why LED_DAC_MIN is redefined to 0 for dac_leds_vccs configurations to help readers understand the different behavior between dac_leds and dac_leds_vccs.
#define LED_DAC_MIN(n) 0

Copy link
Collaborator

@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 @jeppenodgaard,

Thanks for this driver. I apologize for the delayed review.

Please find my comments and questions below.

depends on DT_HAS_DAC_LEDS_ENABLED
select DAC
help
Enable DAC LED driver.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please, elaborate the Kconfig help description a bit (and the commit message as well).

config LED_DAC
bool "DAC LED driver"
default y
depends on DT_HAS_DAC_LEDS_ENABLED
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably you also want to depend on DT_HAS_DAC_LEDS_VCCS_ENABLED here.

return -EINVAL;
}

return led_dac_set_raw(dev, led, 0);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't you use dac_min instead of 0 here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is better to use 0 to turn off the LED off completely.

#undef DT_DRV_COMPAT
#define DT_DRV_COMPAT dac_leds_vccs
#undef LED_DAC_MIN
#define LED_DAC_MIN(n) 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Honestly, I don't like the idea of having two distinct bindings for LED DAC devices. The code above is confusing and quite messy.

Here we are just computing min and max values. What about letting the user do that with some dedicated DT properties ? With maybe an help message in the binding file explaining how to compute them ?

# SPDX-License-Identifier: Apache-2.0

description: |
Group of DAC-controlled LEDs.
Copy link
Collaborator

Choose a reason for hiding this comment

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

The title keyword has been merged into the main branch, so perhaps this can be moved to the title: Group of DAC-controlled LEDs.

# SPDX-License-Identifier: Apache-2.0

description: |
Group of DAC-controlled LEDs using voltage control current source (vccs) circuit.
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

child-binding:
description: Common DAC LED child node
properties:
dac:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
dac:
dac-dev:

I will choose this property name.

Copy link
Member

@martinjaeger martinjaeger left a comment

Choose a reason for hiding this comment

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

I don't think it's a good idea to drive an LED directly with a DAC. A DAC is a voltage output, whereas an LED is current-controlled. Many DACs have a relatively high impedance output (a few kOhms), so it shouldn't even be possible to drive an LED directly. If the DAC has a buffered output, it might be possible, but I still think that controlling the brightness via PWM is a better solution.

The approach with voltage-controlled current-source makes sense, so this should be the default.

What about just using one generic dac-leds.yaml compatible with the following optional properties (feel free to be a bit more elaborate in the descriptions, I've just drafted something to make the idea clear):

voltage-off-mv:
  type: int
  default: 0
  description: |
    Output voltage to turn off the LED
voltage-min-brightness-mv:
  type: int
  default: 0
  description: |
    Voltage at brightness 0%.
voltage-max-brightness-mv:
  type: int
  description: |
    Voltage at brightness 100%. 
    If not specified, the maximum DAC output voltage is used.
output-buffer:
  type: boolean
  description: |
    Enable the output buffer of the DAC. This is required if it is used to drive an LED directly.

I would suggest to describe that the intention of this driver is to have a VCCS. However, if someone has carefully checked the DAC hardware capabilities and wants to use it to drive an LED directly, it's still possible with above properties.

Comment on lines 42 to 46
forward-voltage-mv:
type: int
required: true
description: |
LED forward voltage in millivolt.
Copy link
Member

Choose a reason for hiding this comment

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

An LED's forward voltage is not constant, but varies with current, as shown in the images posted by @bjarki-andreasen. If we decide to specify the LED like this, we'll at least need a more clear description.

Comment on lines 47 to 52
resistor-forward-voltage-mv:
type: int
required: true
description: |
Current limiting resistor voltage in millivolt at LED forward voltage.
LED forward current [mA] * resistor resistance [Ohms]
Copy link
Member

Choose a reason for hiding this comment

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

Why not just specify the resistor value instead of the forward voltage, which is depending on the current?

.dac = DEVICE_DT_GET(DT_PHANDLE(n, dac)), \
.chan_cfg = {.channel_id = DT_PROP(n, channel), \
.resolution = DT_PROP(n, resolution), \
.buffered = false, \
Copy link
Member

Choose a reason for hiding this comment

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

It should be made sure that the output buffer is enabled if the DAC is used to drive the LED directly.

@jeppenodgaard
Copy link
Collaborator Author

Thanks you for all your reviews! Greatly appreciated!

@martinjaeger your comment makes a lot of sense.
I like your bindings suggestion. It is a lot more understandable.

The only downside I can see is that the LED intensity is not changed linearly.
Maybe this can be a future improvement if anyone will use the DAC directly.

I will start updating the PR.

Copy link
Collaborator Author

@jeppenodgaard jeppenodgaard left a comment

Choose a reason for hiding this comment

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

@martinjaeger The PR has been updated.
I did not add voltage-off-mv as I cannot see why it is needed. Please enlighten me if I am wrong 😄

return -EINVAL;
}

return led_dac_set_raw(dev, led, 0);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it is better to use 0 to turn off the LED off completely.

Add LED driver support for DAC based LED drivers.

Signed-off-by: Jeppe Odgaard <[email protected]>
Comment on lines +116 to +120
.chan_cfg = {.channel_id = DT_PROP(n, channel), \
.resolution = DT_PROP(n, resolution), \
.buffered = DT_PROP(n, output_buffer), \
.internal = false}, \
.dac_max = LED_DAC_MAX_BRIGHTNESS(n), .dac_min = LED_DAC_MIN_BRIGHTNESS(n) \
Copy link
Member

Choose a reason for hiding this comment

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

Suggested format to improve readibility (the \ characters still need to be re-aligned):

Suggested change
.chan_cfg = {.channel_id = DT_PROP(n, channel), \
.resolution = DT_PROP(n, resolution), \
.buffered = DT_PROP(n, output_buffer), \
.internal = false}, \
.dac_max = LED_DAC_MAX_BRIGHTNESS(n), .dac_min = LED_DAC_MIN_BRIGHTNESS(n) \
.chan_cfg = {
.channel_id = DT_PROP(n, channel), \
.resolution = DT_PROP(n, resolution), \
.buffered = DT_PROP(n, output_buffer), \
.internal = false, \
}, \
.dac_max = LED_DAC_MAX_BRIGHTNESS(n), \
.dac_min = LED_DAC_MIN_BRIGHTNESS(n), \

Comment on lines +18 to +19
uint32_t dac_max;
uint32_t dac_min;
Copy link
Member

Choose a reason for hiding this comment

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

I'd suggest the following to avoid confusion with the actual dac max output value (e.g. 3.3V).

Suggested change
uint32_t dac_max;
uint32_t dac_min;
uint32_t dac_max_brightness;
uint32_t dac_min_brightness;

@martinjaeger
Copy link
Member

I did not add voltage-off-mv as I cannot see why it is needed. Please enlighten me if I am wrong 😄

The idea was to define a value where the LED is fully off (similar to the point that @simonguinot made here #88259 (comment)). In theory, the VCCS could have an inverted input, so that the off-state is at high voltage and not 0. However, that's not very likely, so I'm fine if we assume a DAC output of 0 to be the off state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Devicetree area: LED Label to identify LED subsystem
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants