Update Files

This commit is contained in:
2025-01-22 17:22:38 +01:00
parent 89b9349629
commit 4c5e729485
5132 changed files with 1195369 additions and 0 deletions

View File

@ -0,0 +1,155 @@
# lws_led gpio and pwm class drivers
Lws provides an abstract led controller class that can bind an array of LEDs
to gpio and pwm controllers, and automatically handled pwm sequencers.
Lumience intensity is corrected for IEC curves to match perceptual intensity,
and the correction can be overridden per led for curve adaptation matching.
Intensity is normalized to a 16-bit scale, when controlled by a GPIO b15 is
significant and the rest ignored. When controlled by PWM, as many bits from
b15 down are significant as the PWM arrangements can represent.
The PWM sequencers use arbitrary function generation callbacks on a normalized
16-bit phase space, they can choose how much to interpolate and how much to put
in a table, a 64-sample, 16-bit sine function is provided along with 16-bit
linear sawtooth.
Changing the sequencer is subject to a third transition function sequencer, this
can for example mix the transition linearly over, eg, 500ms so the leds look
very smooth.
## Defining an led controller
An array of inidividual LED information is provided first, and referenced by
the LED controller definintion. Leds are named so code does not introduce
dependencies on specific implementations.
```
static const lws_led_gpio_map_t lgm[] = {
{
.name = "alert",
.gpio = GPIO_NUM_25,
.pwm_ops = &pwm_ops,
.active_level = 1,
},
};
static const lws_led_gpio_controller_t lgc = {
.led_ops = lws_led_gpio_ops,
.gpio_ops = &lws_gpio_plat,
.led_map = &lgm[0],
.count_leds = LWS_ARRAY_SIZE(lgm)
};
struct lws_led_state *lls;
lls = lgc.led_ops.create(&lgc.led_ops);
if (!lls) {
lwsl_err("%s: could not create led\n", __func__);
goto spin;
}
```
For GPIO control, the active level of the GPIO to light the LED may be set.
Each LED may bind to a pwm controller, in which case setting the intensity
programs the pwm controller corresponding to the GPIO.
## Setting the intensity directly
```
lgc.led_ops.intensity(&lgc.led_ops, "alert", 0);
```
## Defining Sequencer
Some common sequencers are provided out of the box, you can also define your
own arbitrary ones.
The main point is sequencers have a function that returns an intensity for each
of 65536 phase steps in its cycle. For example, this is the linear function
that is included
```
lws_led_intensity_t
lws_led_func_linear(lws_led_seq_phase_t n)
{
return (lws_led_intensity_t)n;
}
```
It simply returns an intensity between 0 - 65535 matching the phase angle of
0 - 65535 that it was given, so it's a sawtooth ramp.
An interpolated sine function is also provided that returns an intensity
between 0 - 65535 reflecting one cycle of sine wave for the phase angle of 0 -
65535.
These functions are packaged into sequencer structures like this
```
const lws_led_sequence_def_t lws_pwmseq_sine_endless_fast = {
.func = lws_led_func_sine,
.ledphase_offset = 0, /* already at 0 amp at 0 phase */
.ledphase_total = LWS_SEQ_LEDPHASE_TOTAL_ENDLESS,
.ms = 750
};
```
This "endless" sequencer cycles through the sine function at 750ms per cycle.
Non-endless sequencers have a specific start and end in the phase space, eg
```
const lws_led_sequence_def_t lws_pwmseq_sine_up = {
.func = lws_led_func_sine,
.ledphase_offset = 0, /* already at 0 amp at 0 phase */
.ledphase_total = LWS_LED_FUNC_PHASE / 2, /* 180 degree ./^ */
.ms = 300
};
```
... this one traverses 180 degrees of the sine wave starting from 0 and ending
at full intensity, over 300ms.
A commonly-used, provided one is like this, as used in the next section
```
const lws_led_sequence_def_t lws_pwmseq_linear_wipe = {
.func = lws_led_func_linear,
.ledphase_offset = 0,
.ledphase_total = LWS_LED_FUNC_PHASE - 1,
.ms = 300
};
```
## Setting the intensity using sequencer transitions
The main api for high level sequenced control is
```
int
lws_led_transition(struct lws_led_state *lcs, const char *name,
const lws_led_sequence_def_t *next,
const lws_led_sequence_def_t *trans);
```
This fades from the current sequence to a new sequence, using `trans` sequencer
intensity as the mix factor. `trans` is typically `lws_pwmseq_linear_wipe`,
fading between the current and new linearly over 300ms. At the end of the
`trans` sequence, the new sequence simply replaces the current one and the
transition is completed.
Sequencers use a single 30Hz OS timer while any sequence is active.
exported sequencer symbol|description
---|---
lws_pwmseq_sine_endless_slow|continuous 100% sine, 1.5s cycle
lws_pwmseq_sine_endless_fast|continuous 100% sine, 0.75s cycle
lws_pwmseq_linear_wipe|single 0 - 100% ramp over 0.3s
lws_pwmseq_sine_up|single 0 - 100% using sine curve over 0.3s
lws_pwmseq_sine_down|single 100% - 0 using sine curve over 0.3s
lws_pwmseq_static_on|100% static
lws_pwmseq_static_half|50% static
lws_pwmseq_static_off|0% static

View File

@ -0,0 +1,120 @@
/*
* Generic GPIO led
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "private-lib-core.h"
#include "drivers/led/private-lib-drivers-led.h"
#if defined(LWS_PLAT_TIMER_CB)
static LWS_PLAT_TIMER_CB(lws_led_timer_cb, th)
{
lws_led_state_t *lcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
lws_seq_timer_handle(lcs);
}
#endif
struct lws_led_state *
lws_led_gpio_create(const lws_led_ops_t *led_ops)
{
lws_led_gpio_controller_t *lgc = (lws_led_gpio_controller_t *)led_ops;
/*
* We allocate the main state object, and a 3 x seq dynamic footprint
* for each led, since it may be sequencing the transition between two
* other sequences.
*/
lws_led_state_t *lcs = lws_zalloc(sizeof(lws_led_state_t) +
(lgc->count_leds * sizeof(lws_led_state_chs_t)),
__func__);
int n;
if (!lcs)
return NULL;
lcs->controller = lgc;
#if defined(LWS_PLAT_TIMER_CREATE)
lcs->timer = LWS_PLAT_TIMER_CREATE("leds",
LWS_LED_SEQUENCER_UPDATE_INTERVAL_MS, 1, lcs,
(TimerCallbackFunction_t)lws_led_timer_cb);
if (!lcs->timer)
return NULL;
#endif
for (n = 0; n < lgc->count_leds; n++) {
const lws_led_gpio_map_t *map = &lgc->led_map[n];
if (map->pwm_ops) {
lgc->gpio_ops->mode(map->gpio, LWSGGPIO_FL_READ);
lgc->gpio_ops->set(map->gpio, 0);
} else {
lgc->gpio_ops->mode(map->gpio, LWSGGPIO_FL_WRITE);
lgc->gpio_ops->set(map->gpio,
!lgc->led_map[n].active_level);
}
}
return lcs;
}
void
lws_led_gpio_destroy(struct lws_led_state *lcs)
{
#if defined(LWS_PLAT_TIMER_DELETE)
LWS_PLAT_TIMER_DELETE(lcs->timer);
#endif
lws_free(lcs);
}
int
lws_led_gpio_lookup(const struct lws_led_ops *lo, const char *name)
{
const lws_led_gpio_controller_t *lgc = (lws_led_gpio_controller_t *)lo;
int n;
for (n = 0; n < lgc->count_leds; n++)
if (!strcmp(name, lgc->led_map[n].name))
return n;
return -1;
}
void
lws_led_gpio_intensity(const struct lws_led_ops *lo, const char *name,
lws_led_intensity_t inten)
{
const lws_led_gpio_controller_t *lgc = (lws_led_gpio_controller_t *)lo;
int idx = lws_led_gpio_lookup(lo, name);
const lws_led_gpio_map_t *map;
if (idx < 0)
return;
map = &lgc->led_map[idx];
if (map->pwm_ops)
map->pwm_ops->intensity(map->pwm_ops, map->gpio, inten);
else
lgc->gpio_ops->set(map->gpio,
(!!map->active_level) ^ !(inten & 0x8000));
}

View File

@ -0,0 +1,200 @@
/*
* Generic GPIO led
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "private-lib-core.h"
#include "drivers/led/private-lib-drivers-led.h"
/*
* 64 entry interpolated CIE correction
* https://en.wikipedia.org/wiki/Lightness
*/
uint16_t cie[] = {
0, 113, 227, 340, 454, 568, 688, 824, 976, 1146,
1335, 1543, 1772, 2023, 2296, 2592, 2914, 3260, 3633, 4034,
4463, 4921, 5409, 5929, 6482, 7067, 7687, 8341, 9032, 9761,
10527, 11332, 12178, 13064, 13993, 14964, 15980, 17040, 18146, 19299,
20500, 21750, 23049, 24400, 25802, 27256, 28765, 30328, 31946, 33622,
35354, 37146, 38996, 40908, 42881, 44916, 47014, 49177, 51406, 53700,
56062, 58492, 60992, 63561,
65535 /* for interpolation */
};
/*
* This is the default intensity correction function, it can be overridden
* per-led to eg, normalize intensity of different leds
*/
static lws_led_intensity_t
cie_antilog(lws_led_intensity_t lin)
{
return (cie[lin >> 10] * (0x3ff - (lin & 0x3ff)) +
cie[(lin >> 10) + 1] * (lin & 0x3ff)) / 0x3ff;
}
static void
lws_seq_advance(lws_led_state_t *lcs, lws_led_state_ch_t *ch)
{
if (!ch->seq)
return;
if (ch->phase_budget != LWS_SEQ_LEDPHASE_TOTAL_ENDLESS &&
(ch->phase_budget < ch->step || !ch->phase_budget)) {
/* we are done */
ch->seq = NULL;
if (!(--lcs->timer_refcount)) {
#if defined(LWS_PLAT_TIMER_STOP)
LWS_PLAT_TIMER_STOP(lcs->timer);
#endif
}
return;
}
ch->ph += ch->step;
if (ch->phase_budget != LWS_SEQ_LEDPHASE_TOTAL_ENDLESS)
ch->phase_budget -= ch->step;
}
static lws_led_intensity_t
lws_seq_sample(const lws_led_gpio_map_t *map, lws_led_state_chs_t *chs)
{
unsigned int i;
if (chs->seqs[LLSI_CURR].seq)
chs->seqs[LLSI_CURR].last = chs->seqs[LLSI_CURR].seq->
func(chs->seqs[LLSI_CURR].ph);
if (chs->seqs[LLSI_TRANS].seq) {
/*
* If a transition is ongoing, we need to use the transition
* intensity as the mixing factor between the still-live current
* and newly-live next sequences
*/
chs->seqs[LLSI_TRANS].last = chs->seqs[LLSI_TRANS].seq->
func(chs->seqs[LLSI_TRANS].ph);
if (chs->seqs[LLSI_NEXT].seq)
chs->seqs[LLSI_NEXT].last = chs->seqs[LLSI_NEXT].seq->
func(chs->seqs[LLSI_NEXT].ph);
i = (lws_led_intensity_t)(((
(unsigned int)chs->seqs[LLSI_CURR].last *
(65535 - chs->seqs[LLSI_TRANS].last) >> 16) +
(((unsigned int)chs->seqs[LLSI_NEXT].last *
(unsigned int)chs->seqs[LLSI_TRANS].last) >> 16)));
} else
i = chs->seqs[LLSI_CURR].last;
return map->intensity_correction ? map->intensity_correction(i) :
cie_antilog((lws_led_intensity_t)i);
}
void
lws_seq_timer_handle(lws_led_state_t *lcs)
{
lws_led_gpio_controller_t *lgc = lcs->controller;
lws_led_state_chs_t *chs = (lws_led_state_chs_t *)&lcs[1];
const lws_led_gpio_map_t *map = &lgc->led_map[0];
unsigned int n;
for (n = 0; n < lgc->count_leds; n++) {
lgc->led_ops.intensity(&lgc->led_ops, map->name,
lws_seq_sample(map, chs));
lws_seq_advance(lcs, &chs->seqs[LLSI_CURR]);
if (chs->seqs[LLSI_TRANS].seq) {
lws_seq_advance(lcs, &chs->seqs[LLSI_NEXT]);
lws_seq_advance(lcs, &chs->seqs[LLSI_TRANS]);
/*
* When we finished the transition, we can make the
* "next" sequence the current sequence and no need for
* a "next" or a transition any more.
*/
if (!chs->seqs[LLSI_TRANS].seq) {
chs->seqs[LLSI_CURR] = chs->seqs[LLSI_NEXT];
chs->seqs[LLSI_NEXT].seq = NULL;
}
}
map++;
chs++;
}
}
static int
lws_led_set_chs_seq(struct lws_led_state *lcs, lws_led_state_ch_t *dest,
const lws_led_sequence_def_t *def)
{
int steps;
dest->seq = def;
dest->ph = def->ledphase_offset;
dest->phase_budget = def->ledphase_total;
/*
* We need to compute the incremental phase angle step to cover the
* total number of phases in the indicated ms, incrementing at the
* timer rate of LWS_LED_SEQUENCER_UPDATE_RATE_HZ. Eg,
*
* 65536 phase steps (one cycle) in 2000ms at 30Hz timer rate means we
* will update 2000ms / 33ms = 60 times, so we must step at at
* 65536 / 60 = 1092 phase angle resolution
*/
steps = def->ms / LWS_LED_SEQUENCER_UPDATE_INTERVAL_MS;
dest->step = (def->ledphase_total != LWS_SEQ_LEDPHASE_TOTAL_ENDLESS ?
def->ledphase_total : LWS_LED_FUNC_PHASE) / (steps ? steps : 1);
if (!lcs->timer_refcount++) {
#if defined(LWS_PLAT_TIMER_START)
LWS_PLAT_TIMER_START(lcs->timer);
#endif
}
return steps;
}
int
lws_led_transition(struct lws_led_state *lcs, const char *name,
const lws_led_sequence_def_t *next,
const lws_led_sequence_def_t *trans)
{
lws_led_state_chs_t *chs = (lws_led_state_chs_t *)&lcs[1];
int index = lws_led_gpio_lookup(&lcs->controller->led_ops, name);
if (index < 0)
return 1;
lws_led_set_chs_seq(lcs, &chs[index].seqs[LLSI_TRANS], trans);
lws_led_set_chs_seq(lcs, &chs[index].seqs[LLSI_NEXT], next);
return 0;
}

View File

@ -0,0 +1,39 @@
/*
* Generic GPIO led
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
typedef struct lws_led_state
{
#if defined(LWS_PLAT_TIMER_TYPE)
LWS_PLAT_TIMER_TYPE timer;
#endif
lws_led_gpio_controller_t *controller;
int timer_refcount;
} lws_led_state_t;
void
lws_seq_timer_handle(lws_led_state_t *lcs);
int
lws_led_gpio_lookup(const struct lws_led_ops *lo, const char *name);