1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-09 00:00:04 +01:00
This commit is contained in:
Andy Green 2020-06-18 07:53:07 +01:00
parent 366333adba
commit 33f1003305
12 changed files with 843 additions and 98 deletions

View file

@ -616,6 +616,7 @@ struct lws;
#include <libwebsockets/lws-ssd1306-i2c.h>
#include <libwebsockets/lws-button.h>
#include <libwebsockets/lws-led.h>
#include <libwebsockets/lws-pwm.h>
#ifdef __cplusplus
}

View file

@ -28,30 +28,39 @@
#if !defined(__LWS_LED_H__)
#define __LWS_LED_H__
/* 0 is always OFF, for gpio, anything else is ON */
/* only b15 significant for GPIO */
typedef uint16_t lws_led_intensity_t;
typedef uint16_t lws_led_seq_phase_t;
#define LWS_LED_MAX_INTENSITY (0xffff)
#define LWS_LED_FRAME_RATE 20
#define LWS_LED_FUNC_PHASE 1024
/* the normalized max intensity */
#define LWS_LED_MAX_INTENSITY (0xffff)
/* the normalized 360 degree phase count for intensity functions */
#define LWS_LED_FUNC_PHASE 65536
/* used when the sequence doesn't stop by itself and goes around forever */
#define LWS_SEQ_LEDPHASE_TOTAL_ENDLESS (-1)
#define LWS_LED_SEQUENCER_UPDATE_INTERVAL_MS 33
struct lws_led_state; /* opaque */
struct lws_pwm_ops; /* forward ref */
typedef lws_led_intensity_t (*lws_led_lookup_t)(int idx);
typedef lws_led_intensity_t (*lws_led_lookup_t)(lws_led_seq_phase_t ph);
typedef struct lws_led_sequence_def_t {
lws_led_lookup_t func;
lws_led_seq_phase_t ledphase_offset;
int ledphase_total;
int ms_full_phase; /* to compute rate */
lws_led_lookup_t func;
lws_led_seq_phase_t ledphase_offset;
int ledphase_total; /* 65536= one cycle */
uint16_t ms;
uint8_t flags;
} lws_led_sequence_def_t;
/* this should always be first in the subclassed implementation types */
typedef struct lws_led_ops {
void (*intensity)(const struct lws_led_ops *lo, int index,
void (*intensity)(const struct lws_led_ops *lo, const char *name,
lws_led_intensity_t inten);
int (*lookup)(const struct lws_led_ops *lo, const char *name);
/**< for BOOL led control like GPIO, only inten b15 is significant */
struct lws_led_state * (*create)(const struct lws_led_ops *led_ops);
void (*destroy)(struct lws_led_state *);
} lws_led_ops_t;
@ -59,6 +68,15 @@ typedef struct lws_led_ops {
typedef struct lws_led_gpio_map {
const char *name;
_lws_plat_gpio_t gpio;
lws_led_lookup_t intensity_correction;
/**< May be NULL. If GPIO-based LED, ignored. If pwm_ops provided,
* NULL means use default CIE 100% correction function. If non-NULL,
* use the pointed-to correction function. This is useful to provide
* LED-specific intensity correction / scaling so different types of
* LED can "look the same". */
const struct lws_pwm_ops *pwm_ops;
/**< if NULL, gpio controls the led directly. If set to a pwm_ops,
* the led control is outsourced to the pwm controller. */
uint8_t active_level;
} lws_led_gpio_map_t;
@ -78,19 +96,32 @@ lws_led_gpio_create(const lws_led_ops_t *led_ops);
LWS_VISIBLE LWS_EXTERN void
lws_led_gpio_destroy(struct lws_led_state *lcs);
/**
* lws_led_gpio_intensity() - set the static intensity of an led
*
* \param lo: the base class of the led controller
* \param index: which led in the controller set
* \param inten: 16-bit unsigned intensity
*
* For LEDs controlled by a BOOL like GPIO, only inten b15 is significant.
* For PWM type LED control, as many bits as the hardware can support from b15
* down are significant.
*/
LWS_VISIBLE LWS_EXTERN void
lws_led_gpio_intensity(const struct lws_led_ops *lo, int index, lws_led_intensity_t inten);
lws_led_gpio_intensity(const struct lws_led_ops *lo, const char *name,
lws_led_intensity_t inten);
LWS_VISIBLE LWS_EXTERN int
lws_led_gpio_lookup(const struct lws_led_ops *lo, const char *name);
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);
#define lws_led_gpio_ops \
{ \
.create = lws_led_gpio_create, \
.destroy = lws_led_gpio_destroy, \
.intensity = lws_led_gpio_intensity, \
.lookup = lws_led_gpio_lookup, \
}
#endif

View file

@ -0,0 +1,64 @@
/*
* Generic PWM controller ops
*
* 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_pwm_map {
_lws_plat_gpio_t gpio;
uint8_t index;
} lws_pwm_map_t;
typedef struct lws_pwm_ops {
int (*init)(const struct lws_pwm_ops *lo);
void (*intensity)(const struct lws_pwm_ops *lo, _lws_plat_gpio_t gpio,
lws_led_intensity_t inten);
const lws_pwm_map_t *pwm_map;
uint8_t count_pwm_map;
} lws_pwm_ops_t;
LWS_VISIBLE LWS_EXTERN int
lws_pwm_plat_init(const struct lws_pwm_ops *lo);
LWS_VISIBLE LWS_EXTERN void
lws_pwm_plat_intensity(const struct lws_pwm_ops *lo, _lws_plat_gpio_t gpio,
lws_led_intensity_t inten);
#define lws_pwm_plat_ops \
.init = lws_pwm_plat_init, \
.intensity = lws_pwm_plat_intensity
/*
* May be useful for making your own transitions or sequences
*/
LWS_VISIBLE LWS_EXTERN lws_led_intensity_t
lws_led_func_linear(lws_led_seq_phase_t n);
LWS_VISIBLE LWS_EXTERN lws_led_intensity_t
lws_led_func_sine(lws_led_seq_phase_t n);
/* canned sequences that can work out of the box */
extern const lws_led_sequence_def_t lws_pwmseq_sine_endless_slow,
lws_pwmseq_sine_endless_fast,
lws_pwmseq_linear_wipe,
lws_pwmseq_sine_up, lws_pwmseq_sine_down,
lws_pwmseq_static_on, lws_pwmseq_static_off;

View file

@ -5,11 +5,15 @@ list(APPEND SOURCES
drivers/i2c/bitbang/lws-bb-i2c.c
drivers/button/lws-button.c
drivers/led/led-gpio.c
drivers/led/led-seq.c
drivers/pwm/pwm.c
)
if (LWS_ESP_PLATFORM)
list(APPEND SOURCES
plat/freertos/esp32/drivers/gpio-esp32.c)
plat/freertos/esp32/drivers/gpio-esp32.c
plat/freertos/esp32/drivers/pwm-esp32.c
)
endif()
exports_to_parent_scope()

154
lib/drivers/led/README.md Normal file
View file

@ -0,0 +1,154 @@
# 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_off|0% static

View file

@ -22,28 +22,30 @@
* IN THE SOFTWARE.
*/
#include "private-lib-core.h"
#include "drivers/led/private-lib-drivers-led.h"
typedef struct lws_led_state
#if defined(LWS_PLAT_TIMER_CB)
static LWS_PLAT_TIMER_CB(lws_led_timer_cb, th)
{
#if defined(LWS_PLAT_FREERTOS)
TimerHandle_t timer;
#endif
lws_led_gpio_controller_t *controller;
} lws_led_state_t;
lws_led_state_t *lcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
#if defined(LWS_PLAT_FREERTOS)
static void
lws_led_timer_cb(TimerHandle_t th)
{
// lws_led_state_t *lcs = pvTimerGetTimerID(th);
lws_seq_timer_handle(lcs);
}
#endif
struct lws_led_state *
lws_led_gpio_create(const lws_led_ops_t *led_ops)
{
lws_led_state_t *lcs = lws_zalloc(sizeof(lws_led_state_t), __func__);
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)
@ -51,15 +53,25 @@ lws_led_gpio_create(const lws_led_ops_t *led_ops)
lcs->controller = lgc;
#if defined(LWS_PLAT_FREERTOS)
lcs->timer = xTimerCreate("leds", 1, 0, lcs,
#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++) {
lgc->gpio_ops->mode(lgc->led_map[n].gpio, LWSGGPIO_FL_WRITE);
lgc->gpio_ops->set(lgc->led_map[n].gpio,
!lgc->led_map[n].active_level);
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;
@ -68,25 +80,12 @@ lws_led_gpio_create(const lws_led_ops_t *led_ops)
void
lws_led_gpio_destroy(struct lws_led_state *lcs)
{
#if defined(LWS_PLAT_FREERTOS)
xTimerDelete(&lcs->timer, 0);
#if defined(LWS_PLAT_TIMER_DELETE)
LWS_PLAT_TIMER_DELETE(&lcs->timer);
#endif
lws_free(lcs);
}
void
lws_led_gpio_intensity(const struct lws_led_ops *lo, int idx, lws_led_intensity_t inten)
{
const lws_led_gpio_controller_t *lgc = (lws_led_gpio_controller_t *)lo;
const lws_led_gpio_map_t *map = &lgc->led_map[idx];
lgc->gpio_ops->set(map->gpio, (!!map->active_level) ^ !inten);
// ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, inten);
// ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0);
}
int
lws_led_gpio_lookup(const struct lws_led_ops *lo, const char *name)
{
@ -100,51 +99,22 @@ lws_led_gpio_lookup(const struct lws_led_ops *lo, const char *name)
return -1;
}
static const lws_led_intensity_t sineq16[] = {
0x0000, 0x0191, 0x031e, 0x04a4, 0x061e, 0x0789, 0x08e2, 0x0a24,
0x0b4e, 0x0c5c, 0x0d4b, 0x0e1a, 0x0ec6, 0x0f4d, 0x0faf, 0x0fea,
};
static lws_led_intensity_t sine_lu(int n)
void
lws_led_gpio_intensity(const struct lws_led_ops *lo, const char *name,
lws_led_intensity_t inten)
{
switch ((n >> 4) & 3) {
case 1:
return 4096 + sineq16[n & 15];
case 2:
return 4096 + sineq16[15 - (n & 15)];
case 3:
return 4096 - sineq16[n & 15];
default:
return 4096 - sineq16[15 - (n & 15)];
}
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));
}
/* useful for sine led fade patterns */
lws_led_intensity_t lws_led_func_sine(int n)
{
/*
* 2: quadrant
* 4: table entry in quadrant
* 4: interp (LSB)
*
* total 10 bits / 1024 steps per cycle
*
* + 0: 0
* + 256: 4096
* + 512: 8192
* + 768: 4096
* +1023: 0
*/
return (sine_lu(n >> 4) * (15 - (n & 15)) +
sine_lu((n >> 4) + 1) * (n & 15)) / 15;
}
const lws_led_sequence_def_t lws_ledseq_sine_wipe = {
.func = lws_led_func_sine,
.ledphase_offset = 0, /* already at 0 amp at 0 phase */
.ledphase_total = 512, /* 180 degree phase ./^ */
.ms_full_phase = 1000, /* ie, 500ms for 180 degree */
};

198
lib/drivers/led/led-seq.c Normal file
View file

@ -0,0 +1,198 @@
/*
* 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 != -1 &&
ch->phase_budget < ch->step) {
/* 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 != -1)
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 = 0, mix, nx;
if (chs->seqs[LWS_LED_SEQ_IDX_CURR].seq)
i = chs->seqs[LWS_LED_SEQ_IDX_CURR].seq->
func(chs->seqs[LWS_LED_SEQ_IDX_CURR].ph);
if (chs->seqs[LWS_LED_SEQ_IDX_TRANSITION].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
*/
mix = chs->seqs[LWS_LED_SEQ_IDX_TRANSITION].seq->
func(chs->seqs[LWS_LED_SEQ_IDX_TRANSITION].ph);
nx = 0;
if (chs->seqs[LWS_LED_SEQ_IDX_NEXT].seq)
nx = chs->seqs[LWS_LED_SEQ_IDX_NEXT].seq->
func(chs->seqs[LWS_LED_SEQ_IDX_NEXT].ph);
i = (lws_led_intensity_t)(
((i * (65535 - mix) / 65536) +
((nx * mix) / 65536)));
}
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++) {
lws_seq_advance(lcs, &chs->seqs[LWS_LED_SEQ_IDX_CURR]);
if (chs->seqs[LWS_LED_SEQ_IDX_TRANSITION].seq) {
lws_seq_advance(lcs, &chs->seqs[LWS_LED_SEQ_IDX_NEXT]);
lws_seq_advance(lcs, &chs->seqs[LWS_LED_SEQ_IDX_TRANSITION]);
if (!chs->seqs[LWS_LED_SEQ_IDX_TRANSITION].seq) {
chs->seqs[LWS_LED_SEQ_IDX_CURR] =
chs->seqs[LWS_LED_SEQ_IDX_NEXT];
chs->seqs[LWS_LED_SEQ_IDX_NEXT].seq = NULL;
}
}
lgc->led_ops.intensity(&lgc->led_ops, map->name,
lws_seq_sample(map, chs));
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 != -1 ?
def->ledphase_total : LWS_LED_FUNC_PHASE) / (steps ? steps : 1);
if (steps && !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);
const lws_led_gpio_map_t *map;
if (index < 0)
return 1;
map = &lcs->controller->led_map[index];
lws_led_set_chs_seq(lcs, &chs[index].seqs[LWS_LED_SEQ_IDX_TRANSITION], trans);
lws_led_set_chs_seq(lcs, &chs[index].seqs[LWS_LED_SEQ_IDX_NEXT], next);
lcs->controller->led_ops.intensity(&lcs->controller->led_ops, map->name,
lws_seq_sample(map, chs));
return 0;
}

View file

@ -0,0 +1,58 @@
/*
* 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;
enum {
LWS_LED_SEQ_IDX_CURR,
LWS_LED_SEQ_IDX_NEXT,
LWS_LED_SEQ_IDX_TRANSITION
};
typedef struct lws_led_state_ch
{
const lws_led_sequence_def_t *seq; /* NULL = inactive */
lws_led_seq_phase_t ph;
lws_led_seq_phase_t step;
int phase_budget;
} lws_led_state_ch_t;
typedef struct lws_led_state_chs
{
lws_led_state_ch_t seqs[3];
} lws_led_state_chs_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);

149
lib/drivers/pwm/pwm.c Normal file
View file

@ -0,0 +1,149 @@
/*
* 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"
static const lws_led_intensity_t sineq16[] = {
/*
* Quadrant at sin(270) in 16 samples, normalized so
* -1 == 0 and 0 == 32767
*/
0, 158, 630, 1411, 2494, 3869, 5522, 7437,
9597, 11980, 14562, 17321, 20228, 23225, 26374, 29555,
32767 /* to interpolate against */
};
/*
* Elaborate the 90 degree phase table to 360 degrees and offset to +32768,
* notice for the last sample we have to interpolate against a 17th sample
* reflecting full scale to avoid clipping due to interpolation against the
* 16th sample again
*/
static lws_led_intensity_t
sine_lu(int n, int next)
{
switch ((n >> 4) & 3) {
case 1:
/* forwards */
return 32768 + sineq16[(n & 15) + next];
case 2:
/* scan it backwards */
return 32768 + sineq16[15 - (n & 15) + (!next)];
case 3:
/* forwards */
return 32768 - sineq16[(n & 15) + next];
default:
/* scan it backwards */
return 32768 - sineq16[15 - (n & 15) + (!next)];
}
}
/*
* The normalized phase resolution is 16-bit, however much table you decide to
* have needs interpolating or indexing in a reduced number of significant
* phase bits if it doesn't have the same phase resolution.
*
* In this sine table we have a 16 x 15-bit sample quadrant reflected 4 times
* to make 360 degrees, so 64 accurate sample points, with the rest of the
* intermediate phases generated by linear interpolation. That probably would
* sound a bit funky, but for modulating light dynamically it's more than
* enough.
*/
lws_led_intensity_t
lws_led_func_sine(lws_led_seq_phase_t n)
{
/*
* 2: quadrant
* 4: table entry in quadrant
* 10: interp (LSB)
*/
return (sine_lu(n >> 10, 0) * (0x3ff - (n & 0x3ff)) +
sine_lu(n >> 10, 1) * (n & 0x3ff)) / 0x3ff;
}
lws_led_intensity_t
lws_led_func_linear(lws_led_seq_phase_t n)
{
return (lws_led_intensity_t)n;
}
static lws_led_intensity_t
lws_led_func_static(lws_led_seq_phase_t n)
{
return n ? LWS_LED_MAX_INTENSITY : 0;
}
const lws_led_sequence_def_t lws_pwmseq_static_off = {
.func = lws_led_func_static,
.ledphase_offset = 0,
.ledphase_total = 0,
.ms = 0
};
const lws_led_sequence_def_t lws_pwmseq_static_on = {
.func = lws_led_func_static,
.ledphase_offset = 1,
.ledphase_total = 0,
.ms = 0
};
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
};
const lws_led_sequence_def_t lws_pwmseq_sine_down = {
.func = lws_led_func_sine,
.ledphase_offset = LWS_LED_FUNC_PHASE / 2, /* start at peak */
.ledphase_total = LWS_LED_FUNC_PHASE / 2, /* 180 degree ./^ */
.ms = 300
};
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
};
const lws_led_sequence_def_t lws_pwmseq_sine_endless_slow = {
.func = lws_led_func_sine,
.ledphase_offset = 0, /* already at 0 amp at 0 phase */
.ledphase_total = LWS_SEQ_LEDPHASE_TOTAL_ENDLESS,
.ms = 1500
};
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
};

View file

@ -0,0 +1,78 @@
/*
* esp32 / esp-idf pwm
*
* 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 "soc/ledc_reg.h"
#include "driver/ledc.h"
static const ledc_timer_config_t tc = {
.speed_mode = LEDC_HIGH_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK
};
int
lws_pwm_plat_init(const struct lws_pwm_ops *lo)
{
ledc_channel_config_t lc = {
.duty = 8191,
.intr_type = LEDC_INTR_FADE_END,
.speed_mode = LEDC_HIGH_SPEED_MODE,
.timer_sel = LEDC_TIMER_0,
};
size_t n;
ledc_timer_config(&tc);
for (n = 0; n < lo->count_pwm_map; n++) {
lc.channel = LEDC_CHANNEL_0 + lo->pwm_map[n].index;
lc.gpio_num = lo->pwm_map[n].gpio;
ledc_channel_config(&lc);
ledc_set_duty(LEDC_HIGH_SPEED_MODE, lc.channel, 0);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, lc.channel);
}
return 0;
}
void
lws_pwm_plat_intensity(const struct lws_pwm_ops *lo, _lws_plat_gpio_t gpio,
lws_led_intensity_t inten)
{
size_t n;
for (n = 0; n < lo->count_pwm_map; n++) {
if (lo->pwm_map[n].gpio == gpio) {
ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0 +
lo->pwm_map[n].index, inten >> 3);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0 +
lo->pwm_map[n].index);
return;
}
}
lwsl_err("%s: unknown gpio for pwm\n", __func__);
}

View file

@ -118,3 +118,14 @@ insert_wsi(const struct lws_context *context, struct lws *wsi);
#define delete_from_fd(A,B) A->lws_lookup[B - lws_plat_socket_offset()] = 0
#define LWS_PLAT_TIMER_TYPE TimerHandle_t
#define LWS_PLAT_TIMER_CB(name, var) void name(TimerHandle_t var)
#define LWS_PLAT_TIMER_CB_GET_OPAQUE(x) pvTimerGetTimerID(x)
#define LWS_PLAT_TIMER_CREATE(name, interval, repeat, opaque, cb) \
xTimerCreate(name, pdMS_TO_TICKS(interval), repeat ? pdTRUE : 0, \
opaque, cb)
#define LWS_PLAT_TIMER_DELETE(ptr) xTimerDelete(ptr, 0)
#define LWS_PLAT_TIMER_START(ptr) xTimerStart(ptr, 0)
#define LWS_PLAT_TIMER_STOP(ptr) xTimerStop(ptr, 0)

View file

@ -27,8 +27,8 @@
struct lws_context *context;
lws_sorted_usec_list_t sul;
lws_display_state_t lds;
struct lws_led_state *lls;
lws_display_state_t lds;
int interrupted;
/*
@ -82,6 +82,20 @@ static const lws_button_controller_t bc = {
.count_buttons = LWS_ARRAY_SIZE(bcm)
};
/*
* pwm controller
*/
static const lws_pwm_map_t pwm_map[] = {
{ .gpio = GPIO_NUM_25, .index = 0 }
};
static const lws_pwm_ops_t pwm_ops = {
lws_pwm_plat_ops,
.pwm_map = &pwm_map[0],
.count_pwm_map = LWS_ARRAY_SIZE(pwm_map)
};
/*
* led controller
*/
@ -90,6 +104,7 @@ static const lws_led_gpio_map_t lgm[] = {
{
.name = "alert",
.gpio = GPIO_NUM_25,
.pwm_ops = &pwm_ops, /* managed by pwm */
.active_level = 1,
},
};
@ -107,13 +122,19 @@ static const uint8_t img[] = {
static uint8_t flip;
static const lws_led_sequence_def_t *seqs[] = {
&lws_pwmseq_static_on,
&lws_pwmseq_static_off,
&lws_pwmseq_sine_endless_slow,
&lws_pwmseq_sine_endless_fast,
};
static int
smd_cb(void *opaque, lws_smd_class_t _class, lws_usec_t timestamp, void *buf,
size_t len)
{
flip = flip ^ 1;
lgc.led_ops.intensity(&lgc.led_ops,
lgc.led_ops.lookup(&lgc.led_ops, "alert"), flip);
lws_led_transition(lls, 0, seqs[flip & 3], &lws_pwmseq_linear_wipe);
flip++;
lwsl_hexdump_notice(buf, len);
@ -171,6 +192,12 @@ app_main(void)
goto spin;
}
/* pwm init must go after the led controller init */
pwm_ops.init(&pwm_ops);
lgc.led_ops.intensity(&lgc.led_ops, "alert", 0);
// lws_led_transition(lls, 0, &lws_pwmseq_sine_endless, NULL);
bcs = lws_button_controller_create(context, &bc);
if (!bcs) {
lwsl_err("%s: could not create buttons\n", __func__);