Closed jamolnng closed 10 months ago
I'll add that I didn't make a pull request because I'm actually using the same driver and same display in a different project, I have just been following this one
Fantastic, this have been on my todo for a long time! I'll try it when I get a chance! Thanks alot 🙂
quick update from more of my testing: if you say try to change the brightness using the touch screen (such as a lvgl slider) and bind the brightness changing to the VALUE_CHANGED
lvgl event then quite often there are two events that will overlap causing issues and it does not behave as intended. To get around this I currently bind the brightness slider to the RELEASED
lvgl event. This may not be ideal though as then the brightness only changes when you release the slider instead of changing as the slider moves. There's definitely a way to fix it, I just haven't gotten that far yet
Hi, I did try it but felt there was still something I did not understand. The driver don't need constant pwm if I understand the code correctly right? I saw this same issue you describe and instead tested it by changing brightness using buttons instead. I'll give it another try tonight, thanks!
Correct, the driver only needs 1-32 pulses to set the brightness. The problem is they're so quick (max of 300 microseconds according to the datasheet) that manually toggling the pins with delays is most likely not consistent enough, which is why I used PWM and a timer to stop the PWM after the number of pulses needed.
The code I wrote above calculates the number of pulses to go from the current brightness to the requested brightness. The problem with this is that if they ever become out of sync then the code will not work as intended. I am looking for a fix to this
Great, I can actually test this code with a logic analyzer.
Been playing around bit, and as you say as long as it doesn't get "out of sync" it works great. I need more logic to make sure not loosing state of it and to handle power on etc. that it re-starts at max brigthness.
I actually have a prototype now using the zephyr counter driver, this may give more accurate timings and allow the backlight value to be changed quicker so you don't have to keep track of the current state. currently setting up my logic analyzer to iron out the details. will update
https://github.com/jakkra/ZSWatch/assets/1132336/869a2b54-238c-44e6-ae1f-89ff1d81b0c0
This should work with LV_EVENT_VALUE_CHANGED
prj.conf
CONFIG_COUNTER=y
CONFIG_NRFX_TIMER1=y
overlay
pwmleds {
compatible = "pwm-leds";
status = "okay";
display_bkl: pwm_led_0 {
pwms = <&pwm0 0 PWM_USEC(15) PWM_POLARITY_INVERTED>;
};
};
#include <zephyr/drivers/counter.h>
static const struct pwm_dt_spec display_bkl = PWM_DT_SPEC_GET_OR(DT_ALIAS(display_bkl), {});
static const struct device *counter_dev = DEVICE_DT_GET_OR_NULL(DT_NODELABEL(timer1));
static void brightness_alarm_start_cb(const struct device *counter_dev, uint8_t chan_id, uint32_t ticks, void *user_data);
static void brightness_alarm_run_cb(const struct device *counter_dev, uint8_t chan_id, uint32_t ticks, void *user_data);
static void brightness_alarm_stop_cb(const struct device *counter_dev, uint8_t chan_id, uint32_t ticks, void *user_data);
static struct counter_alarm_cfg _brightness_alarm_start, _brightness_alarm_run, _brightness_alarm_stop;
static void do_set_brightness(struct k_work *work);
K_WORK_DELAYABLE_DEFINE(_brightness_work, do_set_brightness);
static struct k_work_sync _brightness_cancel_sync;
uint8_t _brightness = 32;
void some_init_function()
{
_brightness_alarm_start.flags = 0;
_brightness_alarm_start.callback = &brightness_alarm_start_cb;
_brightness_alarm_run.flags = 0;
_brightness_alarm_run.callback = &brightness_alarm_run_cb;
_brightness_alarm_stop.flags = 0;
_brightness_alarm_stop.callback = &brightness_alarm_stop_cb;
_brightness_alarm_start.ticks = counter_us_to_ticks(_counter, 0);
_brightness_alarm_run.ticks = counter_us_to_ticks(_counter, 750); // documentation says 1ms but I am able to get it to work with 750us
}
void set_brightness(uint8_t brightness)
{
// this makes sure that we don't set the brightness too quickly in succession
_brightness = min(brightness, uint8_t(32));
k_work_cancel_delayable_sync(&_brightness_work, &_brightness_cancel_sync);
k_work_schedule(&_brightness_work, K_MSEC(20));
}
void do_set_brightness(struct k_work *work)
{
uint8_t npulses = 32 - _brightness;
_brightness_alarm_stop.ticks =
_brightness_alarm_run.ticks +
counter_us_to_ticks(counter_dev, display_bkl.period * (npulses + 1) / NSEC_PER_USEC);
counter_set_channel_alarm(counter_dev, 0, &_brightness_alarm_start);
counter_set_channel_alarm(counter_dev, 1, &_brightness_alarm_run);
counter_set_channel_alarm(counter_dev, 2, &_brightness_alarm_stop);
counter_start(_counter);
}
void brightness_alarm_start_cb(const struct device *counter_dev,
uint8_t chan_id, uint32_t ticks,
void *user_data)
{
pwm_set_pulse_dt(&display_bkl, display_bkl.period);
}
void brightness_alarm_run_cb(const struct device *counter_dev,
uint8_t chan_id, uint32_t ticks,
void *user_data)
{
pwm_set_pulse_dt(&display_bkl, display_bkl.period / 2);
}
void brightness_alarm_stop_cb(const struct device *counter_dev,
uint8_t chan_id, uint32_t ticks,
void *user_data)
{
pwm_set_pulse_dt(&display_bkl, 0);
counter_stop(counter_dev);
}
@jamolnng awesome, works like a charm! Thanks a lot! Will bring this in tomorrow :)
I made this small difference allowing to use level 0 as off:
void set_brightness(uint8_t brightness)
{
if (brightness == 0) {
pwm_set_pulse_dt(&display_blk, display_blk.period);
_brightness = 32;
return;
}
// this makes sure that we don't set the brightness too quickly in succession
_brightness = MIN(brightness, (uint8_t)32);
last_brightness = _brightness;
k_work_cancel_delayable_sync(&_brightness_work, &_brightness_cancel_sync);
k_work_schedule(&_brightness_work, K_MSEC(20));
}
https://github.com/jakkra/ZSWatch/assets/4318648/4ad46892-9419-45e5-8a32-d7c3a222893a
nice! there may be some power usage optimizations by disabling the PWM when not setting the brightness but that's a future problem
Sweet! Really good work 👍
Hi @jamolnng I made the final implementation now in https://github.com/jakkra/ZSWatch/pull/224. I used a semaphore instead of the work you had, same same, but felt this is slightly cleaner and less code.
FYI to get power consumption down you can either do pm_device_action_run(display_blk.dev, PM_DEVICE_ACTION_SUSPEND);
or it's actually enough to use either pulse == period or pule == 0 as in those cases the nRF pwm driver automatically disabled pwm and just writes either HIGH or LOW to the pwm pin.
I think I've got a working solution for the backlight. I only get about 16 levels of brightness difference because in the 16-31 levels I don't see a noticeable change
in app/src/drivers/zsw_display_control.c add something like:
then change this in app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_common.dts#L24: