diff --git a/include/libwebsockets.h b/include/libwebsockets.h index 462658d06..ac226bb18 100644 --- a/include/libwebsockets.h +++ b/include/libwebsockets.h @@ -616,6 +616,7 @@ struct lws; #include #include #include +#include #ifdef __cplusplus } diff --git a/include/libwebsockets/lws-led.h b/include/libwebsockets/lws-led.h index 015760f0a..7f131b890 100644 --- a/include/libwebsockets/lws-led.h +++ b/include/libwebsockets/lws-led.h @@ -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 - diff --git a/include/libwebsockets/lws-pwm.h b/include/libwebsockets/lws-pwm.h new file mode 100644 index 000000000..ed3d558f5 --- /dev/null +++ b/include/libwebsockets/lws-pwm.h @@ -0,0 +1,64 @@ +/* + * Generic PWM controller ops + * + * Copyright (C) 2019 - 2020 Andy Green + * + * 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; diff --git a/lib/drivers/CMakeLists.txt b/lib/drivers/CMakeLists.txt index 192ca450d..b15a7c389 100644 --- a/lib/drivers/CMakeLists.txt +++ b/lib/drivers/CMakeLists.txt @@ -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() diff --git a/lib/drivers/led/README.md b/lib/drivers/led/README.md new file mode 100644 index 000000000..1fef8558b --- /dev/null +++ b/lib/drivers/led/README.md @@ -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 diff --git a/lib/drivers/led/led-gpio.c b/lib/drivers/led/led-gpio.c index 11267f61d..664a120c7 100644 --- a/lib/drivers/led/led-gpio.c +++ b/lib/drivers/led/led-gpio.c @@ -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 */ -}; - diff --git a/lib/drivers/led/led-seq.c b/lib/drivers/led/led-seq.c new file mode 100644 index 000000000..49381d39f --- /dev/null +++ b/lib/drivers/led/led-seq.c @@ -0,0 +1,198 @@ +/* + * Generic GPIO led + * + * Copyright (C) 2019 - 2020 Andy Green + * + * 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; +} diff --git a/lib/drivers/led/private-lib-drivers-led.h b/lib/drivers/led/private-lib-drivers-led.h new file mode 100644 index 000000000..43cfd7385 --- /dev/null +++ b/lib/drivers/led/private-lib-drivers-led.h @@ -0,0 +1,58 @@ +/* + * Generic GPIO led + * + * Copyright (C) 2019 - 2020 Andy Green + * + * 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); diff --git a/lib/drivers/pwm/pwm.c b/lib/drivers/pwm/pwm.c new file mode 100644 index 000000000..20f95e152 --- /dev/null +++ b/lib/drivers/pwm/pwm.c @@ -0,0 +1,149 @@ +/* + * Generic GPIO led + * + * Copyright (C) 2019 - 2020 Andy Green + * + * 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 +}; diff --git a/lib/plat/freertos/esp32/drivers/pwm-esp32.c b/lib/plat/freertos/esp32/drivers/pwm-esp32.c new file mode 100644 index 000000000..e65d1520f --- /dev/null +++ b/lib/plat/freertos/esp32/drivers/pwm-esp32.c @@ -0,0 +1,78 @@ +/* + * esp32 / esp-idf pwm + * + * Copyright (C) 2019 - 2020 Andy Green + * + * 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__); +} diff --git a/lib/plat/freertos/private-lib-plat-freertos.h b/lib/plat/freertos/private-lib-plat-freertos.h index f8d61499c..36fd7dea1 100644 --- a/lib/plat/freertos/private-lib-plat-freertos.h +++ b/lib/plat/freertos/private-lib-plat-freertos.h @@ -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) + + diff --git a/minimal-examples/embedded/lws-minimal-esp32/main/lws-minimal-esp32.c b/minimal-examples/embedded/lws-minimal-esp32/main/lws-minimal-esp32.c index 7dd425960..e8e7ed87b 100644 --- a/minimal-examples/embedded/lws-minimal-esp32/main/lws-minimal-esp32.c +++ b/minimal-examples/embedded/lws-minimal-esp32/main/lws-minimal-esp32.c @@ -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__);