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,33 @@
list(APPEND SOURCES
drivers/display/lws-display.c
drivers/display/ssd1306-i2c.c
drivers/display/ili9341-spi.c
drivers/display/spd1656-spi.c
drivers/display/uc8176-spi.c
drivers/display/ssd1675b-spi.c
drivers/i2c/lws-i2c.c
drivers/i2c/bitbang/lws-bb-i2c.c
drivers/spi/lws-spi.c
drivers/spi/bitbang/lws-bb-spi.c
drivers/button/lws-button.c
drivers/led/led-gpio.c
drivers/led/led-seq.c
drivers/pwm/pwm.c
drivers/settings/settings.c
)
if (LWS_WITH_NETWORK)
list(APPEND SOURCES
drivers/netdev/netdev.c
drivers/netdev/wifi.c)
endif()
if (LWS_ESP_PLATFORM)
list(APPEND SOURCES
plat/freertos/esp32/drivers/gpio-esp32.c
plat/freertos/esp32/drivers/pwm-esp32.c
)
endif()
exports_to_parent_scope()

View File

@ -0,0 +1,44 @@
# lws meta-drivers
Although drivers in lws (enabled in cmake by `LWS_WITH_DRIVERS`) provide
actual drivers for some devices like I2C OLED controllers, their main job is
to conceal from user code the underlying OS APIs being used to interface
to the SoC hardware assets.
CMake already allows lws to be platform-agnostic for build, the plat adaptations
allow lws to be platform-agnostic within itself for runtime. The lws
drivers intend to extend that agnosticism to user code.
Using this technique on supported OSes frees the user code from dependencies
on the underlying OS choice... for example, although ESP32 is very good, it
comes with a highly specific set of apis in esp-idf that mean your code is
locked in to esp-idf if you follow them. Esp-idf uses freertos apis for things
like OS timers, again if you follow those you are locked into freertos, the
end result is your work is non-portable to other platforms and completely
dependent on esp.
LWS drivers provide a thin wrapper to eliminate the OS dependencies while
still taking advantage of the work, drivers and maintenance of the underlying
OS layer without duplicating them, but bringing the flexibility to retarget
your work to other scenarios... for example, there is a generic gpio object
subclassed for specific implementations, an i2c object which may be subclassed
to use OS drivers or bitbang using the generic gpio object, buttons on top of
generic gpio, led class that can use generic gpio or pwm interchangeably,
platform-specific gpio, i2c, pwm implementations that can be used at the generic
level are defined to use underlying OS native apis and drivers.
## Building on the next layer up
At these generic objects like buttons or led controllers, there is a stable
codebase used by multiple implementations and the intention is to provide
best-of-breed features there generically, like
- sophisticated button press debounce and classification
- high quality transitions and log-response compensation and mixing for led pwm
- display dimming timers, blanking timers, generic interaction detection to unblank
which are automatically available on top of any implementation that is ported to
lws drivers.

View File

@ -0,0 +1,156 @@
# LWS GPIO Button class drivers
Lws provides an GPIO button controller class, this centralizes handling a set of
up to 31 buttons for resource efficiency. Each controller has two OS timers,
one for interrupt to bottom-half event triggering and another that runs at 5ms
intervals only when one or more button is down.
Each button has its own active level control and sophisticated state tracking;
each button can apply its own classification regime, to allow for different
physical button characteristics, if not overridden a default one is provided.
Both the controller and individual buttons specify names that are used in the
JSON events produced when the buttons perform actions.
## Button electronic to logical event processing
Buttons are monitored using GPIO interrupts since this is very cheap in the
usual case no interaction is ongoing. There is assumed to be one interrupt
per GPIO, but they are pointed at the same ISR, with an opaque pointer to an
internal struct passed per-interrupt to differentiate them and bind them to a
particular button.
The interrupt is set for notification of the active-going edge, usually if
the button is pulled-up, that's the downgoing edge only. This avoids any
ambiguity about the interrupt meaning, although oscillation is common around
the transition region when the signal is becoming inactive too.
An OS timer is used to schedule a bottom-half handler outside of interrupt
context.
To combat commonly-seen partial charging of the actual and parasitic network
around the button causing drift and oscillation, the bottom-half briefly drives
the button signal to the active level, forcing a more deterministic charge level
if it reached the point the interrupt was triggered. This removes much of the
unpredictable behaviour in the us range. It would be better done in the ISR
but many OS apis cannot perform GPIO operations in interrupt context.
The bottom-half makes sure a monitoring timer is enabled, by refcount. This
is the engine of the rest of the classification while any button is down. The
monitoring timer happens per OS tick or 5ms, whichever is longer.
## Declaring button controllers
An array of button map elements if provided first mapping at least GPIOs to
button names, and also optionally the classification regime for that button.
Then the button controller definition which points back to the button map.
```
static const lws_button_map_t bcm[] = {
{
.gpio = GPIO_NUM_0,
.smd_interaction_name = "user"
},
};
static const lws_button_controller_t bc = {
.smd_bc_name = "bc",
.gpio_ops = &lws_gpio_plat,
.button_map = &bcm[0],
.active_state_bitmap = 0,
.count_buttons = LWS_ARRAY_SIZE(bcm),
};
struct lws_button_state *bcs;
bcs = lws_button_controller_create(context, &bc);
if (!bcs) {
lwsl_err("%s: could not create buttons\n", __func__);
goto spin;
}
```
That is all that is needed for init, button events will be issued on lws_smd
when buttons are pressed.
### Regime settings
The classification regime is designed to reflect both the user interaction
style and the characteristics of a particular type of button.
Member|Default|Meaning
---|---|---
ms_min_down|20ms|Down events shorter than this are ignored
ms_min_down_longpress|300ms|Down events longer than this are reported as a long-click
ms_up_settle|20ms|After the first indication a button is no longer down, the button is ignored for this interval
ms_doubleclick_grace|120ms|The time allowed after a click to see if a second, double-click, is forthcoming
ms_repeat_down|0 / disabled|If held down, interval at which to issue `stilldown` events
flags|LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK|Control which classifications can apply
### lws_smd System Message Distribution Events
The button controller emits system messages of class `LWSSMDCL_INTERACTION`,
using a JSON formatted payload
```
{
"type": "button",
"src": "controller-name/button-name",
"event": "event-name"
}
```
For example, `{"type":"button","src":"bc/user","event":"doubleclick"}`
JSON is used because it is maintainable, extensible, self-documenting and does
not require a central, fragile-against-versioning specification of mappings.
Using button names allows the same code to adapt to different hardware or
button mappings. Button events may be synthesized for test or other purposes
cleanly and clearly.
All the events are somewhat filtered, too short glitches from EMI or whatever
are not reported. "up" and "down" events are reported for the buttons in case
the intention is the duration of the press is meaningful to the user code, but
more typically the user code wants to consume a higher-level classification of
the interaction, eg, that it can be understood as a single "double-click" event.
Event name|Meaning
---|---
down|The button passes a filter for being down, useful for duration-based response
stilldown|The regime can be configured to issue "repeat" notifications at intervals
up|The button has come up, useful for duration-based response
click|The button activity resulted in a classification as a single-click
longclick|The button activity resulted in a classification as a long-click
doubleclick|The button activity resulted in a classification as a double-click
Since double-click detection requires delaying click reporting until it becomes
clear a second click isn't coming, it is enabled as a possible classification in
the regime structure and the regime structure chosen per-button.
Typically user code is interested in, eg, a high level classification of what
the button is doing, eg, a "click" event on a specific button. Rather than
perform a JSON parse, these events can be processed as strings cheaply using
`lws_json_simple_strcmp()`, it's dumb enough to be cheap but smart enough to
understand enough JSON semantics to be accurate, while retaining the ability to
change and extend the JSON, eg
```
if (!lws_json_simple_strcmp(buf, len, "\"src\":", "bc/user")) {
if (!lws_json_simple_strcmp(buf, len, "\"event\":", "click")) {
...
}
...
}
```
### Relationship between up / down and classification
Classification|Sequencing
---|---
click|down-up-click (it's classified when it went up and cannot be a longclick)
longclick|down-longclick-up (it's classified while still down)
doubleclick|down-up-down-doubleclick-up (classified as soon as second click down long enough)
If the regime is configured for it, any "down" may be followed by one or more
"stilldown" at intervals if the button is down long enough

View File

@ -0,0 +1,532 @@
/*
* Generic GPIO / irq buttons
*
* 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"
typedef enum lws_button_classify_states {
LBCS_IDLE, /* nothing happening */
LBCS_MIN_DOWN_QUALIFY,
LBCS_ASSESS_DOWN_HOLD,
LBCS_UP_SETTLE1,
LBCS_WAIT_DOUBLECLICK,
LBCS_MIN_DOWN_QUALIFY2,
LBCS_WAIT_UP,
LBCS_UP_SETTLE2,
} lws_button_classify_states_t;
/*
* This is the opaque, allocated, non-const, dynamic footprint of the
* button controller
*/
typedef struct lws_button_state {
#if defined(LWS_PLAT_TIMER_TYPE)
LWS_PLAT_TIMER_TYPE timer; /* bh timer */
LWS_PLAT_TIMER_TYPE timer_mon; /* monitor timer */
#endif
const lws_button_controller_t *controller;
struct lws_context *ctx;
short mon_refcount;
lws_button_idx_t enable_bitmap;
lws_button_idx_t state_bitmap;
uint16_t mon_timer_count;
/* incremented each time the mon timer cb happens */
/* lws_button_each_t per button overallocated after this */
} lws_button_state_t;
typedef struct lws_button_each {
lws_button_state_t *bcs;
uint16_t mon_timer_comp;
uint16_t mon_timer_repeat;
uint8_t state;
/**^ lws_button_classify_states_t */
uint8_t isr_pending;
} lws_button_each_t;
#if defined(LWS_PLAT_TIMER_START)
static const lws_button_regime_t default_regime = {
.ms_min_down = 20,
.ms_min_down_longpress = 300,
.ms_up_settle = 20,
.ms_doubleclick_grace = 120,
.flags = LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK
};
#endif
/*
* This is happening in interrupt context, we have to schedule a bottom half to
* do the foreground lws_smd queueing, using, eg, a platform timer.
*
* All the buttons point here and use one timer per button controller. An
* interrupt here means, "something happened to one or more buttons"
*/
#if defined(LWS_PLAT_TIMER_START)
void
lws_button_irq_cb_t(void *arg)
{
lws_button_each_t *each = (lws_button_each_t *)arg;
each->isr_pending = 1;
LWS_PLAT_TIMER_START(each->bcs->timer);
}
#endif
/*
* This is the bottom-half scheduled via a timer set in the ISR. From here we
* are allowed to hold mutexes etc. We are coming here because any button
* interrupt arrived, we have to run another timer that tries to put whatever is
* observed on any active button into context and either discard it or arrive at
* a definitive event classification.
*/
#if defined(LWS_PLAT_TIMER_CB)
static LWS_PLAT_TIMER_CB(lws_button_bh, th)
{
lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
const lws_button_controller_t *bc = bcs->controller;
size_t n;
/*
* The ISR and bottom-half is shared by all the buttons. Each gpio
* IRQ has an individual opaque ptr pointing to the corresponding
* button's dynamic lws_button_each_t, the ISR marks the button's
* each->isr_pending and schedules this bottom half.
*
* So now the bh timer has fired and something to do, we need to go
* through all the buttons that have isr_pending set and service their
* state. Intermediate states should start / bump the refcount on the
* mon timer. That's refcounted so it only runs when a button down.
*/
for (n = 0; n < bc->count_buttons; n++) {
if (!each[n].isr_pending)
continue;
/*
* Hide what we're about to do from the delicate eyes of the
* IRQ controller...
*/
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
LWSGGPIO_IRQ_NONE, NULL, NULL);
each[n].isr_pending = 0;
/*
* Force the network around the switch to the
* active level briefly
*/
bc->gpio_ops->set(bc->button_map[n].gpio,
!!(bc->active_state_bitmap & (1 << n)));
bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_WRITE);
if (each[n].state == LBCS_IDLE) {
/*
* If this is the first sign something happening on this
* button, make sure the monitor timer is running to
* classify its response over time
*/
each[n].state = LBCS_MIN_DOWN_QUALIFY;
each[n].mon_timer_comp = bcs->mon_timer_count;
if (!bcs->mon_refcount++) {
#if defined(LWS_PLAT_TIMER_START)
LWS_PLAT_TIMER_START(bcs->timer_mon);
#endif
}
}
/*
* Just for a us or two inbetween here, we're driving it to the
* level we were informed by the interrupt it had enetered, to
* force to charge on the actual and parasitic network around
* the switch to a deterministic-ish state.
*
* If the switch remains in that state, well, it makes no
* difference; if it was a pre-contact and the charge on the
* network was left indeterminate, this will dispose it to act
* consistently in the short term until the pullup / pulldown
* has time to act on it or the switch comes and forces the
* network charge state itself.
*/
bc->gpio_ops->mode(bc->button_map[n].gpio, LWSGGPIO_FL_READ);
/*
* We could do a better job manipulating the irq mode according
* to the switch state. But if an interrupt comes and we have
* done that, we can't tell if it's from before or after the
* mode change... ie, we don't know what the interrupt was
* telling us. We can't trust the gpio state if we read it now
* to be related to what the irq from some time before was
* trying to tell us. So always set it back to the same mode
* and accept the limitation.
*/
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
bc->active_state_bitmap & (1 << n) ?
LWSGGPIO_IRQ_RISING :
LWSGGPIO_IRQ_FALLING,
lws_button_irq_cb_t, &each[n]);
}
}
#endif
#if defined(LWS_PLAT_TIMER_CB)
static LWS_PLAT_TIMER_CB(lws_button_mon, th)
{
lws_button_state_t *bcs = LWS_PLAT_TIMER_CB_GET_OPAQUE(th);
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
const lws_button_controller_t *bc = bcs->controller;
const lws_button_regime_t *regime;
const char *event_name;
int comp_age_ms;
char active;
size_t n;
bcs->mon_timer_count++;
for (n = 0; n < bc->count_buttons; n++) {
if (each->state == LBCS_IDLE) {
each++;
continue;
}
if (bc->button_map[n].regime)
regime = bc->button_map[n].regime;
else
regime = &default_regime;
comp_age_ms = (bcs->mon_timer_count - each->mon_timer_comp) *
LWS_BUTTON_MON_TIMER_MS;
active = bc->gpio_ops->read(bc->button_map[n].gpio) ^
(!(bc->active_state_bitmap & (1 << n)));
// lwsl_notice("%d\n", each->state);
switch (each->state) {
case LBCS_MIN_DOWN_QUALIFY:
/*
* We're trying to figure out if the initial down event
* is a glitch, or if it meets the criteria for being
* treated as the definitive start of some kind of click
* action. To get past this, he has to be solidly down
* for the time mentioned in the applied regime (at
* least when we sample it).
*
* Significant bounce at the start will abort this try,
* but if it's really down there will be a subsequent
* solid down period... it will simply restart this flow
* from a new interrupt and pass the filter then.
*
* The "brief drive on edge" strategy considerably
* reduces inconsistencies here. But physical bounce
* will continue to be observed.
*/
if (!active) {
/* We ignore stuff for a bit after discard */
each->mon_timer_comp = bcs->mon_timer_count;
each->state = LBCS_UP_SETTLE2;
break;
}
if (comp_age_ms >= regime->ms_min_down) {
/* We made it through the initial regime filter,
* the next step is wait and see if this down
* event evolves into a single/double click or
* we can call it as a long-click
*/
each->mon_timer_repeat = bcs->mon_timer_count;
each->state = LBCS_ASSESS_DOWN_HOLD;
event_name = "down";
goto emit;
}
break;
case LBCS_ASSESS_DOWN_HOLD:
/*
* How long is he going to hold it? If he holds it
* past the long-click threshold, we can call it as a
* long-click and do the up processing afterwards.
*/
if (comp_age_ms >= regime->ms_min_down_longpress) {
/* call it as a longclick */
event_name = "longclick";
each->state = LBCS_WAIT_UP;
goto emit;
}
if (!active) {
/*
* He didn't hold it past the long-click
* threshold... we could end up classifying it
* as either a click or a double-click then.
*
* If double-clicks are not allowed to be
* classified, then we can already classify it
* as a single-click.
*/
if (!(regime->flags &
LWSBTNRGMFLAG_CLASSIFY_DOUBLECLICK))
goto classify_single;
/*
* Just wait for the up settle time then start
* looking for a second down.
*/
each->mon_timer_comp = bcs->mon_timer_count;
each->state = LBCS_UP_SETTLE1;
event_name = "up";
goto emit;
}
goto stilldown;
case LBCS_UP_SETTLE1:
if (comp_age_ms > regime->ms_up_settle)
/*
* Just block anything for the up settle time
*/
each->state = LBCS_WAIT_DOUBLECLICK;
break;
case LBCS_WAIT_DOUBLECLICK:
if (active) {
/*
* He has gone down again inside the regime's
* doubleclick grace period... he's going down
* the double-click path
*/
each->mon_timer_comp = bcs->mon_timer_count;
each->state = LBCS_MIN_DOWN_QUALIFY2;
break;
}
if (comp_age_ms >= regime->ms_doubleclick_grace) {
/*
* The grace period expired, the second click
* was either not forthcoming at all, or coming
* quick enough to count: we classify it as a
* single-click
*/
goto classify_single;
}
break;
case LBCS_MIN_DOWN_QUALIFY2:
if (!active) {
/*
* He went up again too quickly, classify it
* as a single-click. It could be bounce in
* which case you might want to increase the
* ms_up_settle in the regime
*/
classify_single:
event_name = "click";
each->mon_timer_comp = bcs->mon_timer_count;
each->state = LBCS_UP_SETTLE2;
goto emit;
}
if (comp_age_ms == regime->ms_min_down) {
event_name = "down";
goto emit;
}
if (comp_age_ms > regime->ms_min_down) {
/*
* It's a double-click
*/
event_name = "doubleclick";
each->state = LBCS_WAIT_UP;
goto emit;
}
break;
case LBCS_WAIT_UP:
if (!active) {
/*
* He has stopped pressing it
*/
each->mon_timer_comp = bcs->mon_timer_count;
each->state = LBCS_UP_SETTLE2;
event_name = "up";
goto emit;
}
stilldown:
if (regime->ms_repeat_down &&
(bcs->mon_timer_count - each->mon_timer_repeat) *
LWS_BUTTON_MON_TIMER_MS > regime->ms_repeat_down) {
each->mon_timer_repeat = bcs->mon_timer_count;
event_name = "stilldown";
goto emit;
}
break;
case LBCS_UP_SETTLE2:
if (comp_age_ms < regime->ms_up_settle)
break;
each->state = LBCS_IDLE;
if (!(--bcs->mon_refcount)) {
#if defined(LWS_PLAT_TIMER_STOP)
LWS_PLAT_TIMER_STOP(bcs->timer_mon);
#endif
}
}
each++;
continue;
emit:
lws_smd_msg_printf(bcs->ctx, LWSSMDCL_INTERACTION,
"{\"type\":\"button\","
"\"src\":\"%s/%s\",\"event\":\"%s\"}",
bc->smd_bc_name,
bc->button_map[n].smd_interaction_name,
event_name);
each++;
}
}
#endif
struct lws_button_state *
lws_button_controller_create(struct lws_context *ctx,
const lws_button_controller_t *controller)
{
lws_button_state_t *bcs = lws_zalloc(sizeof(lws_button_state_t) +
(controller->count_buttons * sizeof(lws_button_each_t)),
__func__);
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
size_t n;
if (!bcs)
return NULL;
bcs->controller = controller;
bcs->ctx = ctx;
for (n = 0; n < controller->count_buttons; n++)
each[n].bcs = bcs;
#if defined(LWS_PLAT_TIMER_CREATE)
/* this only runs inbetween a gpio ISR and the bottom half */
bcs->timer = LWS_PLAT_TIMER_CREATE("bcst",
1, 0, bcs, (TimerCallbackFunction_t)lws_button_bh);
if (!bcs->timer)
return NULL;
/* this only runs when a button activity is being classified */
bcs->timer_mon = LWS_PLAT_TIMER_CREATE("bcmon", LWS_BUTTON_MON_TIMER_MS,
1, bcs, (TimerCallbackFunction_t)
lws_button_mon);
if (!bcs->timer_mon)
return NULL;
#endif
return bcs;
}
void
lws_button_controller_destroy(struct lws_button_state *bcs)
{
/* disable them all */
lws_button_enable(bcs, 0, 0);
#if defined(LWS_PLAT_TIMER_DELETE)
LWS_PLAT_TIMER_DELETE(bcs->timer);
LWS_PLAT_TIMER_DELETE(bcs->timer_mon);
#endif
lws_free(bcs);
}
lws_button_idx_t
lws_button_get_bit(struct lws_button_state *bcs, const char *name)
{
const lws_button_controller_t *bc = bcs->controller;
int n;
for (n = 0; n < bc->count_buttons; n++)
if (!strcmp(name, bc->button_map[n].smd_interaction_name))
return 1 << n;
return 0; /* not found */
}
void
lws_button_enable(lws_button_state_t *bcs,
lws_button_idx_t _reset, lws_button_idx_t _set)
{
lws_button_idx_t u = (bcs->enable_bitmap & (~_reset)) | _set;
const lws_button_controller_t *bc = bcs->controller;
#if defined(LWS_PLAT_TIMER_START)
lws_button_each_t *each = (lws_button_each_t *)&bcs[1];
#endif
int n;
for (n = 0; n < bcs->controller->count_buttons; n++) {
if (!(bcs->enable_bitmap & (1 << n)) && (u & (1 << n))) {
/* set as input with pullup or pulldown appropriately */
bc->gpio_ops->mode(bc->button_map[n].gpio,
LWSGGPIO_FL_READ |
((bc->active_state_bitmap & (1 << n)) ?
LWSGGPIO_FL_PULLDOWN : LWSGGPIO_FL_PULLUP));
#if defined(LWS_PLAT_TIMER_START)
/*
* This one is becoming enabled... the opaque for the
* ISR is the indvidual lws_button_each_t, they all
* point to the same ISR
*/
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
bc->active_state_bitmap & (1 << n) ?
LWSGGPIO_IRQ_RISING :
LWSGGPIO_IRQ_FALLING,
lws_button_irq_cb_t, &each[n]);
#endif
}
if ((bcs->enable_bitmap & (1 << n)) && !(u & (1 << n)))
/* this one is becoming disabled */
bc->gpio_ops->irq_mode(bc->button_map[n].gpio,
LWSGGPIO_IRQ_NONE, NULL, NULL);
}
bcs->enable_bitmap = u;
}

View File

@ -0,0 +1,36 @@
# lws_display
lws provides a generic "display" object that is independent of the connection
to the display, i2c and spi implementations are provided.
Its purpose is to provide basic blit, backlight binding to lws_pwm, backlight /
power management and display info like pixels wide and high in a generic way.
The generic display object `lws_display_t` can be included at the top of a
specific display implementation object, eg, binding it to additional members
to define the actual IO operations to be used, eg, i2c or spi.
When the display is instantiated, it allocates an additional structure on heap
that contains dynamic information about display state, `lws_display_state_t`.
## Power state machine
lws_display objects have convenient power state management using a single lws
sul event loop timer that is managed automatically.
State|Meaning
---|---
OFF|The display is in sleep and not showing anything
BECOMING_ACTIVE|The display was asked to come out of sleep and is waiting for .latency_wake_ms befor proceeding to ACTIVE. The backlight if any is off. After the delay, the backlight is sequenced up to `.bl_active` using `.bl_transition` sequencer
ACTIVE|The backlight is ON and the dim timer is running
AUTODIMMED|The dim timer was not told the display was active for `.autodim_ms`, we are at `.bl_dim` brightness. After `.off_ms` we will transition to OFF
The lws_pwm sequencers are used to provide customizable, smooth transitions for
the backlight, which may be nonlinear.
## Active notification
Calling `lws_display_state_active(&lds)` on eg, user interaction causes the
display state to transition to ACTIVE smoothly, taking care of waking the display
and waiting out a display-specific wake period, and sequencing the backlight
transition to active level as specified in the display structure.

View File

@ -0,0 +1,383 @@
/*
* lws abstract display implementation for ili9341 on spi
*
* Copyright (C) 2019 - 2022 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.
*
* This is somewhat complicated by the platform SPI may a) need special
* allocation for the display driver-private packed line buffers, and b) the
* allocated memory may have 32-bit alignment and access requirements.
*
* The allocation is handled by having ops members in the SPI driver ops struct,
* the alignment has to be observed in the display driver.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
ILI9341_NOP = 0x00,
ILI9341_SWRESET = 0x01,
ILI9341_RDDID = 0x04,
ILI9341_RDDST = 0x09,
ILI9341_SLPIN = 0x10,
ILI9341_SLPOUT = 0x11,
ILI9341_PTLON = 0x12,
ILI9341_NORON = 0x13,
ILI9341_RDMODE = 0x0a,
ILI9341_RDMADCTL = 0x0b,
ILI9341_RDPIXFMT = 0x0c,
ILI9341_RDIMGFMT = 0x0d,
ILI9341_RDSELFDIAG = 0x0f,
ILI9341_INVOFF = 0x20,
ILI9341_INVON = 0x21,
ILI9341_GAMMASET = 0x26,
ILI9341_DISPOFF = 0x28,
ILI9341_DISPON = 0x29,
ILI9341_CASET = 0x2a,
ILI9341_PASET = 0x2b,
ILI9341_RAMWR = 0x2c,
ILI9341_RAMRD = 0x2e,
ILI9341_PTLAR = 0x30,
ILI9341_VSCRDEF = 0x33,
ILI9341_MADCTL = 0x36,
ILI9341_VSCRSADD = 0x37,
ILI9341_PIXFMT = 0x3a,
ILI9341_FRMCTR1 = 0xb1,
ILI9341_FRMCTR2 = 0xb2,
ILI9341_FRMCTR3 = 0xb3,
ILI9341_INVCTR = 0xb4,
ILI9341_DFUNCTR = 0xb6,
ILI9341_PWCTR1 = 0xc0,
ILI9341_PWCTR2 = 0xc1,
ILI9341_PWCTR3 = 0xc2,
ILI9341_PWCTR4 = 0xc3,
ILI9341_PWCTR5 = 0xc4,
ILI9341_VMCTR1 = 0xc5,
ILI9341_VMCTR2 = 0xc7,
ILI9341_FACPUMPRAT = 0xcb,
ILI9341_FACPWCTRB = 0xcf,
ILI9341_RDID1 = 0xda,
ILI9341_RDID2 = 0xdb,
ILI9341_RDID3 = 0xdc,
ILI9341_RDID4 = 0xdd,
ILI9341_GMCTRP1 = 0xe0,
ILI9341_GMCTRN1 = 0xe1,
ILI9341_FACPWCTRA = 0xe8,
ILI9341_FACPWCTR1 = 0xea,
ILI9341_FACDRTIMCTRA = 0xed,
ILI9341_FACSETGAMMACRV = 0xf2,
ILI9341_FACDRTIMCTR = 0xf7,
};
typedef struct lws_display_ili9341_spi_state {
struct lws_display_state *lds;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
} lws_display_ili9341_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_ili9341_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ili9341_spi_state_t *)_lds->priv;
#define pack_native_pixel(_line, _x, _c) { \
if (!(_x & 1)) \
*_line = htons(_c); \
else \
{ *_line = (*_line) | (htons(_c) << 16); _line++; } }
static const uint8_t ili9341_320x240_init[] = {
/*
* This provides 70Hz 320x240 at RGB565, we assume im[3:0] is 1110
* which is 4-bit SPI
*/
3, ILI9341_FACPWCTRB, 0x00, 0x83, 0x30,
4, ILI9341_FACDRTIMCTRA, 0x64, 0x03, 0x12, 0x81,
3, ILI9341_FACPWCTRA, 0x85, 0x01, 0x79,
5, ILI9341_FACPUMPRAT, 0x39, 0x2c, 0x00, 0x34, 0x02,
1, ILI9341_FACDRTIMCTR, 0x20,
2, ILI9341_FACPWCTR1, 0x00, 0x00,
1, ILI9341_PWCTR1, 0x26,
1, ILI9341_PWCTR2, 0x11,
2, ILI9341_VMCTR1, 0x35, 0x3e,
1, ILI9341_VMCTR2, 0xbe,
1, ILI9341_MADCTL, 0x28,
1, ILI9341_VSCRSADD, 0x00,
1, ILI9341_PIXFMT, 0x55,
2, ILI9341_FRMCTR1, 0x00, 0x1b,
1, ILI9341_FACSETGAMMACRV, 0x00,
1, ILI9341_GAMMASET, 0x01,
15, ILI9341_GMCTRP1, 0x0f, 0x31, 0x2b, 0x0c, 0x0e, 0x08, 0x4e,
0xf1, 0x37, 0x07, 0x10, 0x03, 0x0e, 0x09,
0x00,
15, ILI9341_GMCTRN1, 0x00, 0x0e, 0x14, 0x03, 0x11, 0x07, 0x31,
0xc1, 0x48, 0x08, 0x0f, 0x0c, 0x31, 0x36,
0x0f,
4, ILI9341_DFUNCTR, 0x0a, 0x82, 0x27, 0x00,
0, ILI9341_SLPOUT
}, ili9341_320x240_dispon[] = {
0, ILI9341_DISPON
}, ili9341_320x240_sleep_in[] = {
0, ILI9341_SLPIN
}, ili9341_320x240_sleep_out[] = {
0, ILI9341_SLPOUT
};
int
lws_display_ili9341_spi_init(lws_display_state_t *lds)
{
const lws_display_ili9341_t *disp = lds_to_disp(lds);
lws_display_ili9341_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
/* hardware nRESET */
if (disp->gpio) {
disp->gpio->mode(disp->reset_gpio, LWSGGPIO_FL_WRITE |
LWSGGPIO_FL_PULLUP);
disp->gpio->set(disp->reset_gpio, 0);
lws_msleep(1);
disp->gpio->set(disp->reset_gpio, 1);
lws_msleep(1);
}
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_init,
LWS_ARRAY_SIZE(ili9341_320x240_init));
lws_msleep(5);
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_dispon,
LWS_ARRAY_SIZE(ili9341_320x240_dispon));
if (disp->spi->in_flight)
while (disp->spi->in_flight(disp->spi))
;
if (disp->cb)
disp->cb(priv->lds, 1);
return 0;
}
/* backlight handled by PWM */
int
lws_display_ili9341_spi_brightness(lws_display_state_t *lds, uint8_t b)
{
return 0;
}
int
lws_display_ili9341_spi_blit(lws_display_state_t *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_ili9341_spi_state_t *priv = lds_to_priv(lds);
const lws_display_ili9341_t *disp = lds_to_disp(lds);
const lws_surface_info_t *ic = &lds->disp->ic;
lws_greyscale_error_t *gedl_this, *gedl_next;
lws_colour_error_t *edl_this, *edl_next;
int bytes_pl = ic->wh_px[0].whole * 2;
static DMA_ATTR uint32_t buf[5];
lws_display_list_coord_t h, y;
lws_display_colour_t c;
lws_spi_desc_t desc;
const uint8_t *pc;
uint32_t *lo;
int n, m;
if (!priv->line[0]) {
if (disp->spi->alloc_dma)
priv->line[0] = disp->spi->alloc_dma(disp->spi,
bytes_pl * 2);
else
priv->line[0] = lws_malloc(bytes_pl * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: failed to alloc %u\n", __func__,
(unsigned int)bytes_pl * 2);
return 1;
}
priv->line[1] = (uint32_t *)((uint8_t *)priv->line[0] + bytes_pl);
if (lws_display_alloc_diffusion(ic, priv->u)) {
if (disp->spi->free_dma)
disp->spi->free_dma(disp->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lwsl_err("%s: OOM\n", __func__);
return 1;
}
}
pc = src;
lo = priv->line[box->y.whole & 1];
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
desc.src = (uint8_t *)&buf[4];
/*
* Blit a line at a time
*/
h = box->h.whole;
y = box->y.whole;
if (h > 1) {
buf[4] = ILI9341_CASET;
desc.data = (uint8_t *)&buf[0];
desc.flags = 0;
buf[0] = ((box->w.whole & 0xff) << 24) | ((box->w.whole >> 8) << 16) | (box->x.whole) | (box->x.whole);
desc.count_write = 4;
disp->spi->queue(disp->spi, &desc);
buf[4] = ILI9341_PASET;
// buf[0] = (((y + 1) & 0xff) << 24) | (((y + 1) >> 8) << 16) | ((y & 0xff) << 8) | (y >> 8);
buf[0] = (((box->h.whole) & 0xff) << 24) | (((box->h.whole) >> 8) << 16) | ((y & 0xff) << 8) | (y >> 8);
disp->spi->queue(disp->spi, &desc);
buf[4] = ILI9341_RAMWR;
/* priv->line is already allocated for DMA */
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED | LWS_SPI_FLAG_DATA_CONTINUE;
desc.count_write = 0;
disp->spi->queue(disp->spi, &desc);
return 0;
}
if (h) {
edl_this = (lws_colour_error_t *)priv->u[(box->y.whole & 1) ^ 1];
edl_next = (lws_colour_error_t *)priv->u[box->y.whole & 1];
gedl_this = (lws_greyscale_error_t *)edl_this;
gedl_next = (lws_greyscale_error_t *)edl_next;
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 0xffff);
goto go;
}
if (ic->greyscale)
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
else
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
pc += 3;
}
go:
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
if (y + 1 != ic->wh_px[1].whole)
desc.flags |= LWS_SPI_FLAG_DATA_CONTINUE;
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.count_write = bytes_pl;
desc.count_cmd = 0;
if (disp->spi->queue(disp->spi, &desc)) {
lwsl_err("%s: failed to queue\n", __func__);
}
src += bytes_pl;
y++;
return 0;
}
if (!box->h.whole) {
if (disp->spi->in_flight)
while (disp->spi->in_flight(disp->spi))
;
if (disp->spi->free_dma)
disp->spi->free_dma(disp->spi, (void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lws_free_set_NULL(priv->u[0]);
if (disp->cb)
disp->cb(priv->lds, 2);
}
return 0;
}
int
lws_display_ili9341_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_ili9341_t *disp = lds_to_disp(lds);
if (state)
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_sleep_out,
LWS_ARRAY_SIZE(ili9341_320x240_sleep_out));
else
lws_spi_table_issue(disp->spi, 0, ili9341_320x240_sleep_in,
LWS_ARRAY_SIZE(ili9341_320x240_sleep_in));
/* we're not going to do anything useful for 5ms after this */
return 0;
}

View File

@ -0,0 +1,443 @@
/*
* lws abstract display
*
* Copyright (C) 2019 - 2022 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 void
sul_autodim_cb(lws_sorted_usec_list_t *sul)
{
lws_display_state_t *lds = lws_container_of(sul, lws_display_state_t,
sul_autodim);
int next_ms = -1;
/* we fire both to dim and to blank... if already in dim state, blank */
switch (lds->state) {
case LWSDISPS_BECOMING_ACTIVE:
lws_display_state_set_brightness(lds, lds->disp->bl_active);
lds->state = LWSDISPS_ACTIVE;
next_ms = lds->autodim_ms;
break;
case LWSDISPS_ACTIVE:
/* active -> autodimmed */
lds->state = LWSDISPS_AUTODIMMED;
next_ms = lds->off_ms;
lws_display_state_set_brightness(lds, lds->disp->bl_dim);
break;
case LWSDISPS_AUTODIMMED:
/* dimmed -> OFF */
lws_display_state_set_brightness(lds, &lws_pwmseq_static_off);
lds->state = LWSDISPS_GOING_OFF;
next_ms = 600;
break;
case LWSDISPS_GOING_OFF:
/* off dimming completed, actual display OFF */
lws_display_state_off(lds);
return;
default:
return;
}
if (next_ms >= 0)
lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb,
next_ms * LWS_US_PER_MS);
}
void
lws_display_state_init(lws_display_state_t *lds, struct lws_context *ctx,
int dim_ms, int off_ms, struct lws_led_state *bl_lcs,
const lws_display_t *disp)
{
memset(lds, 0, sizeof(*lds));
lds->disp = disp;
lds->ctx = ctx;
lds->autodim_ms = dim_ms;
lds->off_ms = off_ms;
lds->bl_lcs = bl_lcs;
lds->state = LWSDISPS_OFF;
if (lds->bl_lcs)
lws_led_transition(lds->bl_lcs, "backlight", &lws_pwmseq_static_off,
&lws_pwmseq_static_on);
disp->init(lds);
}
void
lws_display_state_set_brightness(lws_display_state_t *lds,
const lws_led_sequence_def_t *pwmseq)
{
if (lds->bl_lcs)
lws_led_transition(lds->bl_lcs, "backlight", pwmseq,
lds->disp->bl_transition);
}
void
lws_display_state_active(lws_display_state_t *lds)
{
int waiting_ms;
if (lds->state == LWSDISPS_OFF) {
/* power us up */
lds->disp->power(lds, 1);
lds->state = LWSDISPS_BECOMING_ACTIVE;
waiting_ms = lds->disp->latency_wake_ms;
} else {
if (lds->state != LWSDISPS_ACTIVE && lds->bl_lcs)
lws_display_state_set_brightness(lds,
lds->disp->bl_active);
lds->state = LWSDISPS_ACTIVE;
waiting_ms = lds->autodim_ms;
}
/* reset the autodim timer */
if (waiting_ms >= 0)
lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb,
waiting_ms * LWS_US_PER_MS);
}
void
lws_display_state_off(lws_display_state_t *lds)
{
/* if no control over backlight, don't bother power down display
* since it would continue to emit, just show all-white or whatever */
if (lds->bl_lcs)
lds->disp->power(lds, 0);
lws_sul_cancel(&lds->sul_autodim);
lds->state = LWSDISPS_OFF;
}
int
lws_display_alloc_diffusion(const lws_surface_info_t *ic, lws_surface_error_t **se)
{
size_t size, gsize = ic->greyscale ? sizeof(lws_greyscale_error_t) :
sizeof(lws_colour_error_t), by;
if (*se)
return 0;
/* defer creation of dlo's 2px-high dlo-width, 2 bytespp or 6 bytespp
* error diffusion buffer */
by = ((ic->wh_px[0].whole + 15) / 8) * 8;
size = gsize * 2u * (unsigned int)by;
lwsl_info("%s: alloc'd %u for width %d\n", __func__, (unsigned int)size,
(int)ic->wh_px[0].whole);
se[0] = lws_zalloc(size, __func__);
if (!se[0])
return 1;
se[1] = (lws_surface_error_t *)(((uint8_t *)se[0]) + (size / 2));
return 0;
}
static void
dist_err_grey(const lws_greyscale_error_t *in, lws_greyscale_error_t *out,
int sixteenths)
{
out->rgb[0] = (int16_t)(out->rgb[0] +
(int16_t)((sixteenths * in->rgb[0]) / 16));
}
static void
dist_err_col(const lws_colour_error_t *in, lws_colour_error_t *out,
int sixteenths)
{
out->rgb[0] = (int16_t)(out->rgb[0] +
(int16_t)((sixteenths * in->rgb[0]) / 16));
out->rgb[1] = (int16_t)(out->rgb[1] +
(int16_t)((sixteenths * in->rgb[1]) / 16));
out->rgb[2] = (int16_t)(out->rgb[2] +
(int16_t)((sixteenths * in->rgb[2]) / 16));
}
void
dist_err_floyd_steinberg_grey(int n, int width, lws_greyscale_error_t *gedl_this,
lws_greyscale_error_t *gedl_next)
{
if (n < width - 1) {
dist_err_grey(&gedl_this[n], &gedl_this[n + 1], 7);
dist_err_grey(&gedl_this[n], &gedl_next[n + 1], 1);
}
if (n)
dist_err_grey(&gedl_this[n], &gedl_next[n - 1], 3);
dist_err_grey(&gedl_this[n], &gedl_next[n], 5);
gedl_this[n].rgb[0] = 0;
}
void
dist_err_floyd_steinberg_col(int n, int width, lws_colour_error_t *edl_this,
lws_colour_error_t *edl_next)
{
if (n < width - 1) {
dist_err_col(&edl_this[n], &edl_this[n + 1], 7);
dist_err_col(&edl_this[n], &edl_next[n + 1], 1);
}
if (n)
dist_err_col(&edl_this[n], &edl_next[n - 1], 3);
dist_err_col(&edl_this[n], &edl_next[n], 5);
edl_this[n].rgb[0] = 0;
edl_this[n].rgb[1] = 0;
edl_this[n].rgb[2] = 0;
}
/*
* #include <stdio.h>
* #include <math.h>
*
* void
* main(void)
* {
* int n;
*
* for (n = 0; n < 256; n++) {
* double d = (double)n / 255.0;
*
* printf("0x%02X, ", (unsigned int)(pow(d, (2.2)) * 255));
* }
*
* }
*/
static const uint8_t gamma2_2[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04,
0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06,
0x06, 0x06, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08,
0x09, 0x09, 0x09, 0x0A, 0x0A, 0x0A, 0x0B, 0x0B,
0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0E, 0x0E, 0x0F,
0x0F, 0x10, 0x10, 0x11, 0x11, 0x12, 0x12, 0x13,
0x13, 0x14, 0x15, 0x15, 0x16, 0x16, 0x17, 0x17,
0x18, 0x19, 0x19, 0x1A, 0x1B, 0x1B, 0x1C, 0x1D,
0x1D, 0x1E, 0x1F, 0x1F, 0x20, 0x21, 0x21, 0x22,
0x23, 0x24, 0x24, 0x25, 0x26, 0x27, 0x28, 0x28,
0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E,
0x3F, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
0x48, 0x49, 0x4A, 0x4B, 0x4D, 0x4E, 0x4F, 0x50,
0x51, 0x52, 0x54, 0x55, 0x56, 0x57, 0x58, 0x5A,
0x5B, 0x5C, 0x5D, 0x5F, 0x60, 0x61, 0x63, 0x64,
0x65, 0x67, 0x68, 0x69, 0x6B, 0x6C, 0x6D, 0x6F,
0x70, 0x72, 0x73, 0x75, 0x76, 0x77, 0x79, 0x7A,
0x7C, 0x7D, 0x7F, 0x80, 0x82, 0x83, 0x85, 0x87,
0x88, 0x8A, 0x8B, 0x8D, 0x8E, 0x90, 0x92, 0x93,
0x95, 0x97, 0x98, 0x9A, 0x9C, 0x9D, 0x9F, 0xA1,
0xA2, 0xA4, 0xA6, 0xA8, 0xA9, 0xAB, 0xAD, 0xAF,
0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBB, 0xBD,
0xBF, 0xC1, 0xC3, 0xC5, 0xC7, 0xC9, 0xCB, 0xCD,
0xCF, 0xD1, 0xD3, 0xD5, 0xD7, 0xD9, 0xDB, 0xDD,
0xDF, 0xE1, 0xE3, 0xE5, 0xE7, 0xE9, 0xEB, 0xED,
0xEF, 0xF1, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFF
};
lws_display_palette_idx_t
lws_display_palettize_grey(const lws_surface_info_t *ic,
const lws_display_colour_t *palette, size_t pdepth,
lws_display_colour_t c, lws_greyscale_error_t *ectx)
{
int best = 0x7fffffff, best_idx = 0;
lws_colour_error_t da, d, ea;
int sum, y;
size_t n;
/* put the most desirable colour (adjusted for existing error) in d */
d.rgb[0] = (int)gamma2_2[LWSDC_R(c)];
da.rgb[0] = d.rgb[0] + ectx->rgb[0];
if (da.rgb[0] < 0)
da.rgb[0] = 0;
if (da.rgb[0] > 255)
da.rgb[0] = 255;
if (ic->type == LWSSURF_565) {
y = d.rgb[0] >> 3;
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - y);
return (lws_display_palette_idx_t)y;
}
/*
* Choose a palette colour considering the error diffusion adjustments
*/
for (n = 0; n < pdepth; n++) {
y = LWSDC_ALPHA(palette[n]);
ea.rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[n])));
sum = ea.rgb[0] < 0 ? -ea.rgb[0] : ea.rgb[0];
if (sum < best) {
best_idx = (int)n;
best = sum;
}
}
/* report the error between the unadjusted colour and what we chose */
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[best_idx])));
return (lws_display_palette_idx_t)best_idx;
}
/*
* For error disffusion, it's better to use YUV and prioritize reducing error
* in Y (lumience)
*/
#if 0
static void
rgb_to_yuv(uint8_t *yuv, const uint8_t *rgb)
{
yuv[0] = 16 + ((257 * rgb[0]) / 1000) + ((504 * rgb[1]) / 1000) +
((98 * rgb[2]) / 1000);
yuv[1] = 128 - ((148 * rgb[0]) / 1000) - ((291 * rgb[1]) / 1000) +
((439 * rgb[2]) / 1000);
yuv[2] = 128 + ((439 * rgb[0]) / 1000) - ((368 * rgb[1]) / 1000) -
((71 * rgb[2]) / 1000);
}
static void
yuv_to_rgb(uint8_t *rgb, const uint8_t *_yuv)
{
unsigned int yuv[3];
yuv[0] = _yuv[0] - 16;
yuv[1] = _yuv[1] - 128;
yuv[2] = _yuv[2] - 128;
rgb[0] = ((1164 * yuv[0]) / 1000) + ((1596 * yuv[2]) / 1000);
rgb[1] = ((1164 * yuv[0]) / 1090) - ((392 * yuv[1]) / 1000) -
((813 * yuv[2]) / 1000);
rgb[2] = ((1164 * yuv[0]) / 1000) + ((2017 * yuv[1]) / 1000);
}
#endif
lws_display_palette_idx_t
lws_display_palettize_col(const lws_surface_info_t *ic,
const lws_display_colour_t *palette, size_t pdepth,
lws_display_colour_t c, lws_colour_error_t *ectx)
{
int best = 0x7fffffff, best_idx = 0, yd;
lws_colour_error_t da, d;
uint8_t ya[3];
size_t n;
int y, ch;
/* put the most desirable colour (adjusted for existing error) in d */
d.rgb[0] = (int)gamma2_2[LWSDC_R(c)];
da.rgb[0] = d.rgb[0] + ectx->rgb[0];
if (da.rgb[0] < 0)
da.rgb[0] = 0;
if (da.rgb[0] > 255)
da.rgb[0] = 255;
yd = da.rgb[0];
d.rgb[1] = (int)gamma2_2[LWSDC_G(c)];
d.rgb[2] = (int)gamma2_2[LWSDC_B(c)];
da.rgb[1] = d.rgb[1] + ectx->rgb[1];
if (da.rgb[1] < 0)
da.rgb[1] = 0;
if (da.rgb[1] > 255)
da.rgb[1] = 255;
da.rgb[2] = d.rgb[2] + ectx->rgb[2];
if (da.rgb[2] < 0)
da.rgb[2] = 0;
if (da.rgb[2] > 255)
da.rgb[2] = 255;
yd = RGB_TO_Y(da.rgb[0], da.rgb[1], da.rgb[2]);
if (ic->type == LWSSURF_565) {
ya[0] = d.rgb[0] >> 3;
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (ya[0] << 3));
ya[1] = d.rgb[1] >> 2;
ectx->rgb[1] = (int16_t)((int)da.rgb[1] - (ya[1] << 2));
ya[2] = d.rgb[2] >> 3;
ectx->rgb[2] = (int16_t)((int)da.rgb[2] - (ya[2] << 3));
return (lws_display_palette_idx_t)((ya[0] << 11) | (ya[1] << 5) | (ya[2]));
}
/*
* Choose a palette colour considering the error diffusion adjustments,
* separately choose the best Y match and the best RGB match
*/
for (n = 0; n < pdepth; n++) {
lws_colour_error_t ea;
int sum;
y = LWSDC_ALPHA(palette[n]);
ea.rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[n])));
ea.rgb[1] = (int16_t)((int)da.rgb[1] - (int)(LWSDC_G(palette[n])));
ea.rgb[2] = (int16_t)((int)da.rgb[2] - (int)(LWSDC_B(palette[n])));
/* Best considering luma match */
sum = (yd > y ? (yd - y) : (y - yd));
/*
* Best considering RGB matching
*/
sum += ((ea.rgb[0] < 0 ? -ea.rgb[0] : ea.rgb[0]) +
(ea.rgb[1] < 0 ? -ea.rgb[1] : ea.rgb[1]) +
(ea.rgb[2] < 0 ? -ea.rgb[2] : ea.rgb[2]));
if (sum < best) {
best_idx = (int)n;
best = sum;
}
}
ch = best_idx;
/* report the error between the adjusted colour and what we chose */
ectx->rgb[0] = (int16_t)((int)da.rgb[0] - (int)(LWSDC_R(palette[ch])));
ectx->rgb[1] = (int16_t)((int)da.rgb[1] - (int)(LWSDC_G(palette[ch])));
ectx->rgb[2] = (int16_t)((int)da.rgb[2] - (int)(LWSDC_B(palette[ch])));
return (lws_display_palette_idx_t)ch;
}

View File

@ -0,0 +1,463 @@
/*
* lws abstract display implementation for Epd 7-colour ACEP SPD1656 on spi
*
* Copyright (C) 2019 - 2022 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.
*
* Based on datasheet
*
* https://www.waveshare.com/w/upload/b/bf/SPD1656_1.1.pdf
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SPD1656_CMD_PSR = 0x00,
SPD1656_CMD_PWR = 0x01,
SPD1656_CMD_POF = 0x02,
SPD1656_CMD_PFS = 0x03,
SPD1656_CMD_PON = 0x04,
SPD1656_CMD_BTST = 0x06,
SPD1656_CMD_DSLP = 0x07,
SPD1656_CMD_DTM1 = 0x10,
SPD1656_CMD_DSP = 0x11,
SPD1656_CMD_DRF = 0x12,
SPD1656_CMD_PLL = 0x30,
SPD1656_CMD_TSE = 0x41,
SPD1656_CMD_CDI = 0x50,
SPD1656_CMD_TCON = 0x60,
SPD1656_CMD_TRES = 0x61,
SPD1656_CMD_PWS = 0xe3,
};
typedef enum {
LWSDISPST_IDLE,
LWSDISPST_INIT1,
LWSDISPST_INIT2,
LWSDISPST_INIT3,
LWSDISPST_INIT4,
LWSDISPST_WRITE1,
LWSDISPST_WRITE2,
LWSDISPST_WRITE3,
LWSDISPST_WRITE4,
LWSDISPST_WRITE5,
LWSDISPRET_ASYNC = 1
} lws_display_update_state_t;
static const uint8_t spd1656_init1[] = {
2, SPD1656_CMD_PSR, 0xef, 0x08,
4, SPD1656_CMD_PWR, 0x37, 0x00, 0x23, 0x23,
1, SPD1656_CMD_PFS, 0x00,
3, SPD1656_CMD_BTST, 0xc7, 0xc7, 0x1d,
1, SPD1656_CMD_PLL, 0x39,
1, SPD1656_CMD_TSE, 0x00,
1, SPD1656_CMD_CDI, 0x37,
1, SPD1656_CMD_TCON, 0x22,
}, spd1656_init2[] = {
4, SPD1656_CMD_TRES, 0, 0, 0, 0, /* filled in */
1, SPD1656_CMD_PWS, 0xaa,
}, spd1656_init3[] = {
1, SPD1656_CMD_CDI, 0x37,
}, spd1656_off[] = {
1, SPD1656_CMD_DSLP, 0xa5,
}, spd1656_write1[] = {
4, SPD1656_CMD_TRES, 0, 0, 0, 0, /* filled in */
}, spd1656_write1a[] = {
0, SPD1656_CMD_DTM1
/* ... frame data ... */
}, spd1656_write2[] = {
0, SPD1656_CMD_PON,
}, spd1656_write3[] = {
0, SPD1656_CMD_DRF,
}, spd1656_write4[] = {
0, SPD1656_CMD_POF,
};
typedef struct lws_display_spd1656_spi_state {
struct lws_display_state *lds;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
int state;
int budget;
} lws_display_spd1656_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_spd1656_spi_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_spd1656_spi_state_t *)_lds->priv;
#define pack_native_pixel(_line, _x, _c) \
{ *_line = (*_line & ~(0xf << (((_x ^ 1) & 7) * 4))) | \
(_c << (((_x ^ 1) & 7) * 4)); \
if ((_x & 7) == 7) \
_line++; }
static void
async_cb(lws_sorted_usec_list_t *sul);
#define BUSY_TIMEOUT_BUDGET 60
static int
check_busy(lws_display_spd1656_spi_state_t *priv, int level)
{
const lws_display_spd1656_spi_t *ea = lds_to_disp(priv->lds);
if (ea->gpio->read(ea->busy_gpio) == level)
return 0; /* good */
if (!--priv->budget) {
lwsl_err("%s: timeout waiting idle %d\n", __func__, level);
return -1; /* timeout */
}
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 50);
return 1; /* keeping on trying */
}
static void
async_cb(lws_sorted_usec_list_t *sul)
{
lws_display_spd1656_spi_state_t *priv = lws_container_of(sul,
lws_display_spd1656_spi_state_t, sul);
const lws_display_spd1656_spi_t *ea = lds_to_disp(priv->lds);
uint8_t buf[32];
//int budget = 5;
switch (priv->state) {
case LWSDISPST_INIT1:
/* take reset low for a short time */
ea->gpio->set(ea->reset_gpio, 0);
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 2);
break;
case LWSDISPST_INIT2:
/* park reset high again and then wait a bit */
ea->gpio->set(ea->reset_gpio, 1);
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT3:
if (check_busy(priv, 1))
return;
lws_spi_table_issue(ea->spi, 0, spd1656_init1,
LWS_ARRAY_SIZE(spd1656_init1));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
memcpy(buf, spd1656_init2, LWS_ARRAY_SIZE(spd1656_init2));
/* width and height filled in from display struct */
buf[2] = (ea->disp.ic.wh_px[0].whole >> 8) & 0xff;
buf[3] = ea->disp.ic.wh_px[0].whole & 0xff;
buf[4] = (ea->disp.ic.wh_px[1].whole >> 8) & 0xff;
buf[5] = ea->disp.ic.wh_px[1].whole & 0xff;
lws_spi_table_issue(ea->spi, 0, buf,
LWS_ARRAY_SIZE(spd1656_init2));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT4:
priv->state = LWSDISPST_IDLE;
lws_spi_table_issue(ea->spi, 0, spd1656_init3,
LWS_ARRAY_SIZE(spd1656_init3));
if (ea->spi->in_flight)
while (ea->spi->in_flight(ea->spi))
;
if (ea->cb)
ea->cb(priv->lds, 1);
break;
case LWSDISPST_WRITE1:
/* rendered and sent the whole frame of pixel data */
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_spi_table_issue(ea->spi, 0, spd1656_write2,
LWS_ARRAY_SIZE(spd1656_write2));
/* fallthru */
case LWSDISPST_WRITE2:
if (check_busy(priv, 1))
return;
priv->state++;
priv->budget = 20000 / 50;
lws_spi_table_issue(ea->spi, 0, spd1656_write3,
LWS_ARRAY_SIZE(spd1656_write3));
/*
* this is going to start the refresh, it may wait in check_busy
* for serveral seconds while it does the sequence on the panel
*/
/* fallthru */
case LWSDISPST_WRITE3:
if (check_busy(priv, 1))
return;
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_spi_table_issue(ea->spi, 0, spd1656_write4,
LWS_ARRAY_SIZE(spd1656_write4));
/* fallthru */
case LWSDISPST_WRITE4:
if (check_busy(priv, 1))
return;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 200);
break;
case LWSDISPST_WRITE5:
/* fully completed the blit */
priv->state = LWSDISPST_IDLE;
if (ea->cb)
ea->cb(priv->lds, 2);
break;
default:
break;
}
}
int
lws_display_spd1656_spi_init(struct lws_display_state *lds)
{
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
lws_display_spd1656_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
ea->gpio->mode(ea->busy_gpio, LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
ea->gpio->mode(ea->reset_gpio, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_PULLUP);
ea->gpio->set(ea->reset_gpio, 1);
priv->state = LWSDISPST_INIT1;
lws_sul_schedule(lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 200);
return 0;
}
/* no backlight */
int
lws_display_spd1656_spi_brightness(const struct lws_display *disp, uint8_t b)
{
return 0;
}
int
lws_display_spd1656_spi_blit(struct lws_display_state *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_spd1656_spi_state_t *priv = lds_to_priv(lds);
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
lws_greyscale_error_t *gedl_this, *gedl_next;
const lws_surface_info_t *ic = &ea->disp.ic;
lws_colour_error_t *edl_this, *edl_next;
size_t bytes_pl = ic->wh_px[0].whole / 2;
const uint8_t *pc = src;
lws_display_colour_t c;
lws_spi_desc_t desc;
uint8_t temp[10];
uint32_t *lo;
int n, m;
if (priv->state) {
lwsl_warn("%s: ignoring as busy\n", __func__);
return 1; /* busy */
}
if (!priv->line[0]) {
/*
* We have to allocate the packed line and error diffusion
* buffers
*/
if (ea->spi->alloc_dma)
priv->line[0] = ea->spi->alloc_dma(ea->spi, bytes_pl * 2);
else
priv->line[0] = lws_zalloc(bytes_pl * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
priv->line[1] = (uint32_t *)(((uint8_t *)priv->line[0]) + bytes_pl);
if (lws_display_alloc_diffusion(ic, priv->u)) {
if (ea->spi->free_dma)
ea->spi->free_dma(ea->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
}
lo = priv->line[box->y.whole & 1];
// lwsl_notice("%s: switch %d\n", __func__, box->h.whole);
switch (box->h.whole) {
case 0: /* update is finished */
priv->state = LWSDISPST_WRITE1;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb, 1);
if (ea->spi->free_dma)
ea->spi->free_dma(ea->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lws_free_set_NULL(priv->u[0]);
return 0;
case 1: /* single line = issue line */
edl_this = (lws_colour_error_t *)priv->u[(box->y.whole & 1) ^ 1];
edl_next = (lws_colour_error_t *)priv->u[box->y.whole & 1];
gedl_this = (lws_greyscale_error_t *)edl_this;
gedl_next = (lws_greyscale_error_t *)edl_next;
memset(&desc, 0, sizeof(desc));
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 1 /* white */);
goto go;
}
if (ic->greyscale)
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
else
for (n = 0; n < ic->wh_px[0].whole; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
pc += 3;
}
go:
/* priv->line is already allocated for DMA */
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
desc.flags |= box->y.whole + 1 != ic->wh_px[1].whole ?
LWS_SPI_FLAG_DATA_CONTINUE : 0;
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.count_write = ic->wh_px[0].whole / 2;
ea->spi->queue(ea->spi, &desc);
return 0;
default:
/* Start whole page update... no partial updates on this controller,
* box must be whole display */
lwsl_notice("%s: start update\n", __func__);
memcpy(temp, spd1656_write1, LWS_ARRAY_SIZE(spd1656_write1));
/* width and height filled in from display struct */
temp[2] = (lds->disp->ic.wh_px[0].whole >> 8) & 0xff;
temp[3] = lds->disp->ic.wh_px[0].whole & 0xff;
temp[4] = (lds->disp->ic.wh_px[1].whole >> 8) & 0xff;
temp[5] = lds->disp->ic.wh_px[1].whole & 0xff;
lws_spi_table_issue(ea->spi, 0, temp,
LWS_ARRAY_SIZE(spd1656_write1));
lws_spi_table_issue(ea->spi, LWS_SPI_FLAG_DATA_CONTINUE,
spd1656_write1a, LWS_ARRAY_SIZE(spd1656_write1a));
return 0;
}
}
int
lws_display_spd1656_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_spd1656_spi_t *ea = lds_to_disp(lds);
if (!state) {
lws_spi_table_issue(ea->spi, 0, spd1656_off, LWS_ARRAY_SIZE(spd1656_off));
if (ea->gpio)
ea->gpio->set(ea->reset_gpio, 0);
return 0;
}
return 0;
}

View File

@ -0,0 +1,278 @@
/*
* lws abstract display implementation for ssd1306 on i2c
*
* Copyright (C) 2019 - 2022 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.
*
*
* The OLED display is composed of 128 x 8 bytes, where the bytes contain 8
* columnar pixels in a single row. We can handle it by buffering 8 lines and
* then issuing it as 128 linear bytes.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SSD1306_SETLOWCOLUMN = 0x00,
SSD1306_SETHIGHCOLUMN = 0x10,
SSD1306_MEMORYMODE = 0x20,
SSD1306_COLUMNADDR = 0x21,
SSD1306_PAGEADDR = 0x22,
SSD1306_DEACTIVATE_SCROLL = 0x2e,
SSD1306_SETSTARTLINE = 0x40,
SSD1306_SETCONTRAST = 0x81,
SSD1306_CHARGEPUMP = 0x8d,
SSD1306_SEGREMAP = 0xa0,
SSD1306_SETSEGMENTREMAP = 0xa1,
SSD1306_DISPLAYALLON_RESUME = 0xa4,
SSD1306_DISPLAYALLON = 0xa5,
SSD1306_NORMALDISPLAY = 0xa6,
SSD1306_INVERTDISPLAY = 0xa7,
SSD1306_SETMULTIPLEX = 0xa8,
SSD1306_DISPLAYOFF = 0xae,
SSD1306_DISPLAYON = 0xaf,
SSD1306_COMSCANINC = 0xc0,
SSD1306_COMSCANDEC = 0xc8,
SSD1306_SETDISPLAYOFFSET = 0xd3,
SSD1306_SETDISPLAYCLOCKDIV = 0xd5,
SSD1306_SETPRECHARGE = 0xd9,
SSD1306_SETCOMPINS = 0xda,
SSD1306_SETVCOMDESELECT = 0xdb,
SSD1306_NOP = 0xe3,
};
static uint8_t ssd1306_128x64_init[] = {
SSD1306_DISPLAYOFF,
SSD1306_SETDISPLAYCLOCKDIV, 0xf0,
SSD1306_SETMULTIPLEX, 64 - 1,
SSD1306_SETDISPLAYOFFSET, 0,
SSD1306_CHARGEPUMP, 0x14,
SSD1306_MEMORYMODE, 0,
SSD1306_SEGREMAP | (0 << 0),
SSD1306_COMSCANDEC,
SSD1306_SETCOMPINS, (1 << 4) | 0x02,
SSD1306_SETCONTRAST, 0x7f,
SSD1306_SETPRECHARGE, (0xf << 4) | (1 << 0),
SSD1306_SETVCOMDESELECT, (4 << 4),
SSD1306_DEACTIVATE_SCROLL,
SSD1306_DISPLAYALLON_RESUME,
SSD1306_NORMALDISPLAY,
//SSD1306_DISPLAYON
};
typedef struct lws_display_ssd1306_i2c_state_t {
struct lws_display_state *lds;
uint8_t *line8;
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
} lws_display_ssd1306_i2c_state_t;
#define lds_to_disp(_lds) (const lws_display_ssd1306_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ssd1306_i2c_state_t *)_lds->priv;
int
lws_display_ssd1306_i2c_init(lws_display_state_t *lds)
{
const lws_display_ssd1306_t *si = lds_to_disp(lds);
lws_display_ssd1306_i2c_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
si->i2c->init(si->i2c);
if (si->gpio) {
si->gpio->mode(si->reset_gpio, LWSGGPIO_FL_WRITE |
LWSGGPIO_FL_PULLUP);
si->gpio->set(si->reset_gpio, 0);
lws_msleep(1);
si->gpio->set(si->reset_gpio, 1);
lws_msleep(1);
}
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ssd1306_128x64_init,
LWS_ARRAY_SIZE(ssd1306_128x64_init))) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
if (si->cb)
si->cb(lds, 1);
return 0;
}
int
lws_display_ssd1306_i2c_contrast(lws_display_state_t *lds, uint8_t b)
{
const lws_display_ssd1306_t *si = lds_to_disp(lds);
uint8_t ba[2];
ba[0] = SSD1306_SETCONTRAST;
ba[1] = b;
return lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, LWS_ARRAY_SIZE(ba));
}
int
lws_display_ssd1306_i2c_blit(lws_display_state_t *lds, const uint8_t *src,
lws_box_t *box, lws_dll2_owner_t *ids)
{
lws_display_ssd1306_i2c_state_t *priv = lds_to_priv(lds);
const lws_display_ssd1306_t *si = lds_to_disp(lds);
const lws_surface_info_t *ic = &lds->disp->ic;
lws_greyscale_error_t *gedl_this, *gedl_next;
int bytes_pl = (ic->wh_px[0].whole + 7) / 8;
lws_display_list_coord_t y = box->y.whole;
const uint8_t *pc = src;
lws_display_colour_t c;
uint8_t ba[6], *lo;
int n, m;
/*
* The display is arranged in 128x8 bands, with one byte containing
* the 8 vertical pixels of the band.
*/
if (!priv->line8) {
priv->line8 = lws_malloc(bytes_pl * 8, __func__);
if (!priv->line8)
return 1;
if (lws_display_alloc_diffusion(ic, priv->u)) {
lws_free_set_NULL(priv->line8);
lwsl_err("%s: OOM\n", __func__);
return 1;
}
}
lo = priv->line8;
switch (box->h.whole) {
default: /* start */
break;
case 0: /* end */
lws_free_set_NULL(priv->line8);
lws_free_set_NULL(priv->u[0]);
lwsl_err("%s: End of raster\n", __func__);
ba[0] = SSD1306_NORMALDISPLAY;
ba[1] = SSD1306_DISPLAYON;
if (lws_i2c_command_list(si->i2c, si->i2c7_address, ba, 2)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
if (si->cb)
si->cb(priv->lds, 2);
break;
case 1: /* per line */
gedl_this = (lws_greyscale_error_t *)priv->u[(box->y.whole & 1) ^ 1];
gedl_next = (lws_greyscale_error_t *)priv->u[box->y.whole & 1];
for (n = ic->wh_px[0].whole - 1; n >= 0; n--) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
if (m)
lo[n] = (lo[n] | (1 << (y & 7)));
else
lo[n] = (lo[n] & ~(1 << (y & 7)));
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
pc++;
}
if ((y & 7) != 7)
break;
ba[0] = SSD1306_COLUMNADDR;
ba[1] = box->x.whole;
ba[2] = box->x.whole + box->w.whole - 1;
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, 3)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
ba[0] = SSD1306_PAGEADDR;
ba[1] = y / 8;
ba[2] = ba[1] + ((ic->wh_px[0].whole) / 8) - 1;
if (lws_i2c_command_list(si->i2c, si->i2c7_address,
ba, 3)) {
lwsl_err("%s: fail\n", __func__);
return 1;
}
lws_bb_i2c_start(si->i2c);
lws_bb_i2c_write(si->i2c, si->i2c7_address << 1);
lws_bb_i2c_write(si->i2c, SSD1306_SETSTARTLINE | y);
for (m = 0; m < box->w.whole; m++)
lws_bb_i2c_write(si->i2c, priv->line8[m]);
lws_bb_i2c_stop(si->i2c);
break;
}
return 0;
}
int
lws_display_ssd1306_i2c_power(lws_display_state_t *lds, int state)
{
#if 0
const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)lds->disp;
if (!state)
return lws_i2c_command(si->i2c, si->i2c7_address,
SSD1306_DISPLAYOFF | !!state);
return lws_display_ssd1306_i2c_init(lds);
#endif
return 0;
}

View File

@ -0,0 +1,495 @@
/*
* lws abstract display implementation for SSD1675B on SPI
*
* Copyright (C) 2019 - 2022 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.
*
* Based on datasheet
*
* https://cdn-learn.adafruit.com/assets/assets/000/092/748/original/SSD1675_0.pdf
*
* This chip takes a planar approach with two distinct framebuffers for b0 and
* b1 of the red levels. But the panel is B&W so we ignore red.
*
* Notice this 2.13" B&W panel needs POSITION B on the Waveshare ESP32
* prototype board DIP switch.
*/
#include <private-lib-core.h>
#include <dlo/private-lib-drivers-display-dlo.h>
enum {
SSD1675B_CMD_DRIVER_OUT_CTRL = 0x01,
SSD1675B_CMD_GATE_DRIVEV_CTRL = 0x03,
SSD1675B_CMD_SOURCE_DRIVEV_CTRL = 0x04,
SSD1675B_CMD_DEEP_SLEEP = 0x10,
SSD1675B_CMD_DATA_ENTRY_MODE = 0x11,
SSD1675B_CMD_SW_RESET = 0x12,
SSD1675B_CMD_MAIN_ACTIVATION = 0x20,
SSD1675B_CMD_DISPLAY_UPDATE_CTRL = 0x22,
SSD1675B_CMD_WRITE_BW_SRAM = 0x24,
SSD1675B_CMD_WRITE_RED_SRAM = 0x26,
SSD1675B_CMD_VCOM_VOLTAGE = 0x2C,
SSD1675B_CMD_LUT = 0x32,
SSD1675B_CMD_WRITE_DISPLAY_OPTIONS = 0x37,
SSD1675B_CMD_DUMMY_LINE = 0x3A,
SSD1675B_CMD_GATE_TIME = 0x3B,
SSD1675B_CMD_BORDER_WAVEFORM = 0x3C,
SSD1675B_CMD_SET_RAM_X = 0x44,
SSD1675B_CMD_SET_RAM_Y = 0x45,
SSD1675B_CMD_SET_COUNT_X = 0x4e,
SSD1675B_CMD_SET_COUNT_Y = 0x4f,
SSD1675B_CMD_SET_ANALOG_BLOCK_CTRL = 0x74,
SSD1675B_CMD_SET_DIGITAL_BLOCK_CTRL = 0x7e,
};
typedef enum {
LWSDISPST_IDLE,
LWSDISPST_INIT1,
LWSDISPST_INIT2,
LWSDISPST_INIT3,
LWSDISPST_INIT4,
LWSDISPST_WRITE1,
LWSDISPST_WRITE2,
LWSDISPST_WRITE3,
LWSDISPST_WRITE4,
LWSDISPST_WRITE5,
LWSDISPRET_ASYNC = 1
} lws_display_update_state_t;
//static
const uint8_t ssd1675b_init1_full[] = {
0, SSD1675B_CMD_SW_RESET,
/* wait idle */
}, ssd1675b_init1_part[] = {
1, SSD1675B_CMD_VCOM_VOLTAGE, 0x26,
/* wait idle */
}, ssd1675b_init2_full[] = {
1, SSD1675B_CMD_SET_ANALOG_BLOCK_CTRL, 0x54,
1, SSD1675B_CMD_SET_DIGITAL_BLOCK_CTRL, 0x3b,
3, SSD1675B_CMD_DRIVER_OUT_CTRL, 0xf9, 0x00, 0x00,
1, SSD1675B_CMD_DATA_ENTRY_MODE, 0x03,
2, SSD1675B_CMD_SET_RAM_X, 0x00, 0x0f,
4, SSD1675B_CMD_SET_RAM_Y, 0x00, 0x00, 0xf9, 0x00,
1, SSD1675B_CMD_BORDER_WAVEFORM, 0x03,
1, SSD1675B_CMD_VCOM_VOLTAGE, 0x55,
1, SSD1675B_CMD_GATE_DRIVEV_CTRL, 0x15,
3, SSD1675B_CMD_SOURCE_DRIVEV_CTRL, 0x41, 0xa8, 0x32,
1, SSD1675B_CMD_DUMMY_LINE, 0x30,
1, SSD1675B_CMD_GATE_TIME, 0x0a,
70, SSD1675B_CMD_LUT,
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM:VS 0 ~7
0x03, 0x03, 0x00, 0x00, 0x02, // TP0 A~D RP0
0x09, 0x09, 0x00, 0x00, 0x02, // TP1 A~D RP1
0x03, 0x03, 0x00, 0x00, 0x02, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
1, SSD1675B_CMD_SET_COUNT_X, 0x00,
2, SSD1675B_CMD_SET_COUNT_Y, 0x00, 0x00,
}, ssd1675b_init2_part[] = {
70, SSD1675B_CMD_LUT,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT0: BB: VS 0 ~7
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT1: BW: VS 0 ~7
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT2: WB: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT3: WW: VS 0 ~7
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //LUT4: VCOM:VS 0 ~7
0x0A, 0x00, 0x00, 0x00, 0x00, // TP0 A~D RP0
0x00, 0x00, 0x00, 0x00, 0x00, // TP1 A~D RP1
0x00, 0x00, 0x00, 0x00, 0x00, // TP2 A~D RP2
0x00, 0x00, 0x00, 0x00, 0x00, // TP3 A~D RP3
0x00, 0x00, 0x00, 0x00, 0x00, // TP4 A~D RP4
0x00, 0x00, 0x00, 0x00, 0x00, // TP5 A~D RP5
0x00, 0x00, 0x00, 0x00, 0x00, // TP6 A~D RP6
7, SSD1675B_CMD_WRITE_DISPLAY_OPTIONS, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00,
1, SSD1675B_CMD_DISPLAY_UPDATE_CTRL, 0xc0,
0, SSD1675B_CMD_MAIN_ACTIVATION,
/* wait idle */
}, ssd1675b_init3_part[] = {
1, SSD1675B_CMD_BORDER_WAVEFORM, 0x01
}, ssd1675b_off[] = {
1, SSD1675B_CMD_DEEP_SLEEP, 0x01
}, ssd1675b_wp1[] = {
0, SSD1675B_CMD_WRITE_BW_SRAM,
}, ssd1675b_wp2[] = {
0, SSD1675B_CMD_WRITE_RED_SRAM,
}, ssd1675b_complete_full[] = {
1, SSD1675B_CMD_DISPLAY_UPDATE_CTRL, 0xc7,
0, SSD1675B_CMD_MAIN_ACTIVATION
};
typedef struct lws_display_ssd1675b_spi_state {
struct lws_display_state *lds;
uint8_t *planebuf;
uint32_t *line[2];
lws_surface_error_t *u[2];
lws_sorted_usec_list_t sul;
size_t pb_len;
size_t pb_pos;
int state;
int budget;
} lws_display_ssd1675b_spi_state_t;
#define lds_to_disp(_lds) (const lws_display_ssd1675b_spi_t *)_lds->disp;
#define lds_to_priv(_lds) (lws_display_ssd1675b_spi_state_t *)_lds->priv;
/*
* The lws greyscale line composition buffer is width x Y bytes linearly.
*
* For SSD1675B, this is processed into a private buffer layout in priv->line
* that is sent over SPI to the chip, the format is both packed and planar: the
* first half is packed width x 1bpp "B&W" bits, and the second half is packed
* width x "red" bits. We only support B&W atm.
*/
/* MSB plane is in first half of priv linebuf */
#define pack_native_pixel(_line, _x, _c) \
{ *_line = (*_line & ~(1 << (((_x ^ 7) & 31)))) | \
(_c << (((_x ^ 7) & 31))); \
if ((_x & 31) == 31) \
_line++; }
static void
async_cb(lws_sorted_usec_list_t *sul);
#define BUSY_TIMEOUT_BUDGET 160
static int
check_busy(lws_display_ssd1675b_spi_state_t *priv, int level)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(priv->lds);
if (ea->gpio->read(ea->busy_gpio) == level)
return 0; /* good */
if (!--priv->budget) {
lwsl_err("%s: timeout waiting idle %d\n", __func__, level);
return -1; /* timeout */
}
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 50);
return 1; /* keeping on trying */
}
static int
spi_issue_table(struct lws_display_state *lds, const uint8_t *table, size_t len)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_spi_desc_t desc;
size_t pos = 0;
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
while (pos < len) {
desc.count_write = table[pos++];
desc.src = &table[pos++];
desc.data = &table[pos];
pos += desc.count_write;
ea->spi->queue(ea->spi, &desc);
}
return 0;
}
static void
async_cb(lws_sorted_usec_list_t *sul)
{
lws_display_ssd1675b_spi_state_t *priv = lws_container_of(sul,
lws_display_ssd1675b_spi_state_t, sul);
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(priv->lds);
switch (priv->state) {
case LWSDISPST_INIT1:
/* take reset low for a short time */
ea->gpio->set(ea->reset_gpio, 0);
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT2:
/* park reset high again and then wait a bit */
ea->gpio->set(ea->reset_gpio, 1);
priv->state++;
priv->budget = BUSY_TIMEOUT_BUDGET;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 20);
break;
case LWSDISPST_INIT3:
if (check_busy(priv, 0))
return;
spi_issue_table(priv->lds, ssd1675b_init1_full,
LWS_ARRAY_SIZE(ssd1675b_init1_full));
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul,
async_cb, LWS_US_PER_MS * 10);
break;
case LWSDISPST_INIT4:
if (check_busy(priv, 0))
return;
priv->state = LWSDISPST_IDLE;
spi_issue_table(priv->lds, ssd1675b_init2_full,
LWS_ARRAY_SIZE(ssd1675b_init2_full));
if (ea->cb)
ea->cb(priv->lds, 1);
break;
case LWSDISPST_WRITE1:
/*
* Finalize the write of the planes, LUT set then REFRESH
*/
spi_issue_table(priv->lds, ssd1675b_complete_full,
LWS_ARRAY_SIZE(ssd1675b_complete_full));
priv->budget = BUSY_TIMEOUT_BUDGET;
priv->state++;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 50);
break;
case LWSDISPST_WRITE2:
if (check_busy(priv, 0))
return;
if (ea->spi->free_dma)
ea->spi->free_dma(ea->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lws_free_set_NULL(priv->u[0]);
/* fully completed the blit */
priv->state = LWSDISPST_IDLE;
if (ea->cb)
ea->cb(priv->lds, 2);
break;
default:
break;
}
}
int
lws_display_ssd1675b_spi_init(struct lws_display_state *lds)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_display_ssd1675b_spi_state_t *priv;
priv = lws_zalloc(sizeof(*priv), __func__);
if (!priv)
return 1;
priv->lds = lds;
lds->priv = priv;
ea->gpio->mode(ea->busy_gpio, LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
ea->gpio->mode(ea->reset_gpio, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_PULLUP);
ea->gpio->set(ea->reset_gpio, 1);
priv->state = LWSDISPST_INIT1;
lws_sul_schedule(lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 200);
return 0;
}
/* no backlight */
int
lws_display_ssd1675b_spi_brightness(const struct lws_display *disp, uint8_t b)
{
return 0;
}
int
lws_display_ssd1675b_spi_blit(struct lws_display_state *lds, const uint8_t *src,
lws_box_t *box)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
lws_display_ssd1675b_spi_state_t *priv = lds_to_priv(lds);
lws_greyscale_error_t *gedl_this, *gedl_next;
const lws_surface_info_t *ic = &ea->disp.ic;
int plane_line_bytes = (ic->wh_px[0].whole + 7) / 8;
lws_colour_error_t *edl_this, *edl_next;
const uint8_t *pc = src;
lws_display_colour_t c;
lws_spi_desc_t desc;
uint32_t *lo;
int n, m;
if (priv->state) {
lwsl_warn("%s: ignoring as busy\n", __func__);
return 1; /* busy */
}
if (!priv->line[0]) {
/*
* We have to allocate the packed line and error diffusion
* buffers
*/
if (ea->spi->alloc_dma)
priv->line[0] = ea->spi->alloc_dma(ea->spi, (plane_line_bytes + 4) * 2);
else
priv->line[0] = lws_zalloc((plane_line_bytes + 4) * 2, __func__);
if (!priv->line[0]) {
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
priv->line[1] = (uint32_t *)(((uint8_t *)priv->line[0]) + plane_line_bytes + 4);
if (lws_display_alloc_diffusion(ic, priv->u)) {
if (ea->spi->free_dma)
ea->spi->free_dma(ea->spi,
(void **)&priv->line[0]);
else
lws_free_set_NULL(priv->line[0]);
lwsl_err("%s: OOM\n", __func__);
priv->state = LWSDISPST_IDLE;
return 1;
}
}
lo = priv->line[box->y.whole & 1];
switch (box->h.whole) {
case 0: /* update needs to be finalized */
priv->state = LWSDISPST_WRITE1;
lws_sul_schedule(priv->lds->ctx, 0, &priv->sul, async_cb,
LWS_US_PER_MS * 2);
break;
case 1: /* single line = issue line */
edl_this = (lws_colour_error_t *)priv->u[(box->y.whole & 1) ^ 1];
edl_next = (lws_colour_error_t *)priv->u[box->y.whole & 1];
gedl_this = (lws_greyscale_error_t *)edl_this;
gedl_next = (lws_greyscale_error_t *)edl_next;
if (!pc) {
for (n = 0; n < ic->wh_px[0].whole; n++)
pack_native_pixel(lo, n, 1 /* white */);
goto go;
}
if (ic->greyscale) {
gedl_next[ic->wh_px[0].whole - 1].rgb[0] = 0;
for (n = 0; n < plane_line_bytes * 8; n++) {
c = (pc[0] << 16) | (pc[0] << 8) | pc[0];
m = lws_display_palettize_grey(ic, ic->palette,
ic->palette_depth, c, &gedl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_grey(n, ic->wh_px[0].whole,
gedl_this, gedl_next);
if (n < ic->wh_px[0].whole)
pc++;
}
} else {
edl_next[ic->wh_px[0].whole - 1].rgb[0] = 0;
edl_next[ic->wh_px[0].whole - 1].rgb[1] = 0;
edl_next[ic->wh_px[0].whole - 1].rgb[2] = 0;
for (n = 0; n < plane_line_bytes * 8; n++) {
c = (pc[2] << 16) | (pc[1] << 8) | pc[0];
m = lws_display_palettize_col(ic, ic->palette,
ic->palette_depth, c, &edl_this[n]);
pack_native_pixel(lo, n, (uint8_t)m);
dist_err_floyd_steinberg_col(n, ic->wh_px[0].whole,
edl_this, edl_next);
if (n < ic->wh_px[0].whole)
pc += 3;
}
}
go:
memset(&desc, 0, sizeof(desc));
if (!box->y.whole)
spi_issue_table(priv->lds, ssd1675b_wp1,
LWS_ARRAY_SIZE(ssd1675b_wp1));
desc.data = (uint8_t *)priv->line[box->y.whole & 1];
desc.flags = LWS_SPI_FLAG_DMA_BOUNCE_NOT_NEEDED;
desc.count_write = plane_line_bytes;
ea->spi->queue(ea->spi, &desc);
return 0;
default: /* starting update */
break;
}
return 0;
}
int
lws_display_ssd1675b_spi_power(lws_display_state_t *lds, int state)
{
const lws_display_ssd1675b_spi_t *ea = lds_to_disp(lds);
if (!state) {
spi_issue_table(lds, ssd1675b_off, LWS_ARRAY_SIZE(ssd1675b_off));
if (ea->gpio)
ea->gpio->set(ea->reset_gpio, 0);
return 0;
}
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
/*
* I2C bitbang implementation using generic gpio
*
* 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.
*
* This is like an abstract class for gpio, a real implementation provides
* functions for the ops that use the underlying OS gpio arrangements.
*/
#include <libwebsockets.h>
int
lws_bb_i2c_init(const lws_i2c_ops_t *octx)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
ctx->gpio->mode(ctx->scl, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
ctx->gpio->mode(ctx->sda, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
return 0;
}
int
lws_bb_i2c_start(const lws_i2c_ops_t *octx)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
ctx->gpio->set(ctx->sda, 1);
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
if (!ctx->gpio->read(ctx->sda))
return 1;
ctx->gpio->set(ctx->sda, 0);
ctx->delay();
ctx->gpio->set(ctx->scl, 0);
return 0;
}
void
lws_bb_i2c_stop(const lws_i2c_ops_t *octx)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
ctx->gpio->set(ctx->sda, 0);
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
while (!ctx->gpio->read(ctx->scl))
;
ctx->gpio->set(ctx->sda, 1);
ctx->delay();
}
int
lws_bb_i2c_write(const lws_i2c_ops_t *octx, uint8_t data)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
int n;
for (n = 0; n < 8; n++) {
ctx->gpio->set(ctx->sda, !!(data & (1 << 7)));
ctx->delay();
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
data <<= 1;
ctx->gpio->set(ctx->scl, 0);
}
ctx->gpio->set(ctx->sda, 1);
ctx->delay();
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
n = ctx->gpio->read(ctx->sda);
ctx->gpio->set(ctx->scl, 0);
ctx->delay();
return !!n; /* 0 = ACKED = OK */
}
int
lws_bb_i2c_read(const lws_i2c_ops_t *octx)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
int n, r = 0;
ctx->gpio->set(ctx->sda, 1);
for (n = 7; n <= 0; n--) {
ctx->gpio->set(ctx->scl, 0);
ctx->delay();
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
if (ctx->gpio->read(ctx->sda))
r |= 1 << n;
}
ctx->gpio->set(ctx->scl, 0);
return r;
}
void
lws_bb_i2c_set_ack(const lws_i2c_ops_t *octx, int ack)
{
lws_bb_i2c_t *ctx = (lws_bb_i2c_t *)octx;
ctx->gpio->set(ctx->scl, 0);
ctx->gpio->set(ctx->sda, !!ack);
ctx->delay();
ctx->gpio->set(ctx->scl, 1);
ctx->delay();
ctx->gpio->set(ctx->scl, 0);
ctx->delay();
ctx->gpio->set(ctx->sda, 1);
}

View File

@ -0,0 +1,59 @@
/*
* Generic I2C
*
* 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.
*
* These are generic helpers made up of calls to the i2c driver ops, so they
* just need implementing once like this and are usable for any i2c underlying
* implementation via the ops.
*/
#include <libwebsockets.h>
int
lws_i2c_command(const lws_i2c_ops_t *ctx, uint8_t ads7, uint8_t c)
{
if (ctx->start(ctx))
return 1;
if (ctx->write(ctx, ads7 << 1)) {
ctx->stop(ctx);
return 1;
}
ctx->write(ctx, 0);
ctx->write(ctx, c);
ctx->stop(ctx);
return 0;
}
int
lws_i2c_command_list(const lws_i2c_ops_t *ctx, uint8_t ads7, const uint8_t *buf,
size_t len)
{
while (len--)
if (lws_i2c_command(ctx, ads7, *buf++))
return 1;
return 0;
}

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);

View File

@ -0,0 +1,272 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 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_struct_map_t lsm_wifi_creds[] = {
LSM_CARRAY (lws_wifi_creds_t, ssid, "ssid"),
LSM_CARRAY (lws_wifi_creds_t, passphrase, "passphrase"),
LSM_UNSIGNED (lws_wifi_creds_t, alg, "alg"),
LSM_STRING_PTR (lws_wifi_creds_t, bssid, "bssid"),
};
static const lws_struct_map_t lsm_netdev_credentials[] = {
LSM_LIST (lws_netdevs_t, owner_creds, lws_wifi_creds_t, list,
NULL, lsm_wifi_creds, "credentials"),
};
static const lws_struct_map_t lsm_netdev_schema[] = {
LSM_SCHEMA (lws_netdevs_t, NULL, lsm_netdev_credentials,
"lws-netdev-creds"),
};
//LSM_CHILD_PTR (lws_netdev_instance_wifi_t, ap_cred, lws_wifi_creds_t,
// NULL, lsm_wifi_creds, "ap_cred"),
//LSM_STRING_PTR (lws_netdev_instance_wifi_t, ap_ip, "ap_ip"),
int
lws_netdev_credentials_settings_set(lws_netdevs_t *nds)
{
lws_struct_serialize_t *js;
size_t w = 0, max = 2048;
int n, r = 1;
uint8_t *buf;
buf = lws_malloc(max, __func__); /* length should be computed */
js = lws_struct_json_serialize_create(lsm_netdev_schema,
LWS_ARRAY_SIZE(lsm_netdev_schema), 0, nds);
if (!js)
goto bail;
n = lws_struct_json_serialize(js, buf, max, &w);
lws_struct_json_serialize_destroy(&js);
if (n != LSJS_RESULT_FINISH)
goto bail;
lwsl_notice("%s: setting %s\n", __func__, buf);
if (!lws_settings_plat_set(nds->si, "netdev.creds", buf, w))
r = 0;
bail:
if (r)
lwsl_err("%s: failed\n", __func__);
lws_free(buf);
return r;
}
int
lws_netdev_credentials_settings_get(lws_netdevs_t *nds)
{
struct lejp_ctx ctx;
lws_struct_args_t a;
size_t l = 0;
uint8_t *buf;
int m;
memset(&a, 0, sizeof(a));
if (lws_settings_plat_get(nds->si, "netdev.creds", NULL, &l)) {
lwsl_notice("%s: not in settings\n", __func__);
return 1;
}
buf = lws_malloc(l, __func__);
if (!buf)
return 1;
if (lws_settings_plat_get(nds->si, "netdev.creds", buf, &l)) {
lwsl_err("%s: unexpected settings get fail\n", __func__);
goto bail;
}
a.map_st[0] = lsm_netdev_schema;
a.map_entries_st[0] = LWS_ARRAY_SIZE(lsm_netdev_schema);
a.ac_block_size = 512;
lws_struct_json_init_parse(&ctx, NULL, &a);
m = lejp_parse(&ctx, (uint8_t *)buf, l);
lws_free(buf);
if (m < 0 || !a.dest) {
lwsl_notice("%s: JSON decode failed '%s'\n",
__func__, lejp_error_to_string(m));
goto bail1;
}
/*
* Forcibly set the state of the nds creds owner to the synthesized
* one in the ac, and keep the ac for as long as we keep the creds out
*/
nds->owner_creds = ((lws_netdevs_t *)a.dest)->owner_creds;
nds->ac_creds = a.ac;
return 0;
bail:
lws_free(buf);
bail1:
lwsac_free(&a.ac);
return 1;
}
lws_wifi_creds_t *
lws_netdev_credentials_find(lws_netdevs_t *netdevs, const char *ssid,
const uint8_t *bssid)
{
lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(
&netdevs->owner_creds)) {
lws_wifi_creds_t *w = lws_container_of(p, lws_wifi_creds_t, list);
if (!strcmp(ssid, (const char *)&w[1]) &&
!memcmp(bssid, w->bssid, 6))
return w;
} lws_end_foreach_dll(p);
return NULL;
}
lws_netdev_instance_t *
lws_netdev_find(lws_netdevs_t *netdevs, const char *ifname)
{
lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(
&netdevs->owner)) {
lws_netdev_instance_t *ni = lws_container_of(p,
lws_netdev_instance_t, list);
if (!strcmp(ifname, ni->name))
return ni;
} lws_end_foreach_dll(p);
return NULL;
}
/*
* Context forwards NETWORK related smd here, in lws thread context
*/
int
lws_netdev_smd_cb(void *opaque, lws_smd_class_t _class, lws_usec_t timestamp,
void *buf, size_t len)
{
struct lws_context *ctx = (struct lws_context *)opaque;
const char *iface;
char setname[16];
size_t al = 0;
/* deal with anything from whole-network perspective */
/* pass through netdev-specific messages to correct platform handler */
iface = lws_json_simple_find(buf, len, "\"if\":", &al);
if (!iface)
return 0;
lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(
&ctx->netdevs.owner)) {
lws_netdev_instance_t *ni = lws_container_of(
p, lws_netdev_instance_t, list);
if (!strncmp(ni->name, iface, al)) {
/*
* IP assignment on our netif? We can deal with marking
* the last successful association generically...
*/
if (ni->type == LWSNDTYP_WIFI &&
!lws_json_simple_strcmp(buf, len, "\"type\":",
"ipacq")) {
const char *ev = lws_json_simple_find(buf, len,
"\"ipv4\":", &al);
lws_netdev_instance_wifi_t *wnd =
(lws_netdev_instance_wifi_t *)ni;
if (!ev)
return 0;
lws_snprintf(setname, sizeof(setname),
"netdev.last.%s", iface);
lws_settings_plat_printf(ctx->netdevs.si,
setname, "{\"ssid\":\"%s\",\"bssid\":"
"\"%02X%02X%02X%02X%02X%02X\"}",
wnd->current_attempt_ssid,
wnd->current_attempt_bssid[0],
wnd->current_attempt_bssid[1],
wnd->current_attempt_bssid[2],
wnd->current_attempt_bssid[3],
wnd->current_attempt_bssid[4],
wnd->current_attempt_bssid[5]);
}
/*
* Pass it through to related netdev instance for
* private actions
*/
return ni->ops->event(ni, timestamp, buf, len);
}
} lws_end_foreach_dll(p);
return 0;
}
/*
* This is the generic part of the netdev instance initialization that's always
* the same, regardless of the netdev type
*/
void
lws_netdev_instance_create(lws_netdev_instance_t *ni, struct lws_context *ctx,
const lws_netdev_ops_t *ops, const char *name,
void *platinfo)
{
ni->ops = ops;
ni->name = name;
ni->platinfo = platinfo;
/* add us to the list of active netdevs */
lws_dll2_add_tail(&ni->list, &ctx->netdevs.owner);
}
void
lws_netdev_instance_remove_destroy(struct lws_netdev_instance *ni)
{
lws_dll2_remove(&ni->list);
lws_free(ni);
}
lws_netdevs_t *
lws_netdevs_from_ctx(struct lws_context *ctx)
{
return &ctx->netdevs;
}

View File

@ -0,0 +1,243 @@
/*
* libwebsockets - lws_netdev_wifi generic state handling
*
* Copyright (C) 2010 - 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.
*
* The generic wifi netdevs follow a
*/
#include "private-lib-core.h"
int
lws_netdev_wifi_rssi_sort_compare(const lws_dll2_t *d, const lws_dll2_t *i)
{
const lws_wifi_sta_t *wsd = (const lws_wifi_sta_t *)d,
*wsi = (const lws_wifi_sta_t *)i;
return rssi_averaged(wsd) > rssi_averaged(wsi);
}
void
lws_netdev_wifi_scan_empty(lws_netdev_instance_wifi_t *wnd)
{
lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, lws_dll2_get_head(
&wnd->scan)) {
lws_wifi_sta_t *s = lws_container_of(p, lws_wifi_sta_t, list);
lws_dll2_remove(p);
lws_free(s);
} lws_end_foreach_dll_safe(p, p1);
}
void
lws_netdev_wifi_scan(lws_sorted_usec_list_t *sul)
{
lws_netdev_instance_wifi_t *wnd = lws_container_of(sul,
lws_netdev_instance_wifi_t, sul_scan);
wnd->inst.ops->scan(&wnd->inst);
}
lws_wifi_sta_t *
lws_netdev_wifi_scan_find(lws_netdev_instance_wifi_t *wnd, const char *ssid,
const uint8_t *bssid)
{
lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(
&wnd->scan)) {
lws_wifi_sta_t *w = lws_container_of(p, lws_wifi_sta_t, list);
if (!strcmp(ssid, (const char *)&w[1]) &&
!memcmp(bssid, w->bssid, 6))
return w;
} lws_end_foreach_dll(p);
return NULL;
}
int
lws_netdev_wifi_scan_select(lws_netdev_instance_wifi_t *wnd)
{
lws_netdevs_t *netdevs = lws_netdevs_from_ndi(&wnd->inst);
struct lws_context *cx = lws_context_from_netdevs(netdevs);
uint32_t least_recent = 0xffffffff;
lws_wifi_creds_t *pc = NULL;
lws_wifi_sta_t *pw = NULL;
/*
* Trim enough of the lowest RSSI guys in order to get us below the
* limit we are allowed to keep track of...
*/
while (wnd->scan.count > LWS_WIFI_MAX_SCAN_TRACK) {
struct lws_dll2 *p = lws_dll2_get_tail(&wnd->scan);
lws_wifi_sta_t *w = lws_container_of(p, lws_wifi_sta_t, list);
lws_dll2_remove(p);
lws_free(w);
}
/*
* ... let's dump what's left
*/
lws_start_foreach_dll(struct lws_dll2 *, p, lws_dll2_get_head(
&wnd->scan)) {
lws_wifi_sta_t *w = lws_container_of(p, lws_wifi_sta_t, list);
lwsl_notice("%s: %s, %02X:%02X:%02X:%02X:%02X:%02X, ch %d, rssi %d\n",
__func__, (const char *)&w[1], w->bssid[0],
w->bssid[1], w->bssid[2], w->bssid[3], w->bssid[4],
w->bssid[5], w->ch, rssi_averaged(w));
} lws_end_foreach_dll(p);
/*
* make sure we have our device's connection credentials at hand
*/
if (!netdevs->ac_creds &&
lws_netdev_credentials_settings_get(netdevs))
return 0;
netdevs->refcount_creds++;
/*
* Let's go through each starting from the best RSSI seeing if we
* have credentials... if we do, pick the one we least-recently tried
*/
lws_start_foreach_dll(struct lws_dll2 *, p1, wnd->scan.head) {
lws_wifi_sta_t *w = lws_container_of(p1, lws_wifi_sta_t, list);
lws_start_foreach_dll(struct lws_dll2 *, q,
netdevs->owner_creds.head) {
lws_wifi_creds_t *c = lws_container_of(q,
lws_wifi_creds_t,
list);
if (!strcmp((const char *)&w[1], c->ssid) &&
w->last_seen < least_recent) {
/*
* Not <= so we stick with higher RSSI when
* all 0
*/
pc = c;
pw = w;
least_recent = w->last_seen;
}
} lws_end_foreach_dll(q);
} lws_end_foreach_dll(p1);
if (least_recent != 0xffffffff) {
/*
* We picked one to try... note what we're trying so we can
* record it in settings as last successful
*/
lws_strncpy(wnd->current_attempt_ssid, (const char *)&pw[1],
sizeof(wnd->current_attempt_ssid));
memcpy(wnd->current_attempt_bssid, pw->bssid, LWS_ETH_ALEN);
wnd->inst.ops->connect(&wnd->inst, pc->ssid, pc->passphrase,
pw->bssid);
} else {
/*
* We couldn't see anyone we recognized on this scan, let's
* rescan in a bit
*/
lwsl_notice("%s: nothing usable in scan, redoing in 3s\n", __func__);
lws_sul_schedule(cx, 0, &wnd->sul_scan, lws_netdev_wifi_scan,
3 * LWS_US_PER_SEC);
}
if (!--netdevs->refcount_creds) {
lws_dll2_owner_clear(&netdevs->owner_creds);
lwsac_free(&netdevs->ac_creds);
}
return 0;
}
/*
* Initially our best bet is just try to reconnect to whatever we last
* succeeded to connect to
*/
int
lws_netdev_wifi_redo_last(lws_netdev_instance_wifi_t *wnd)
{
lws_netdevs_t *netdevs = lws_netdevs_from_ndi(&wnd->inst);
uint8_t buf[256], bssid[LWS_ETH_ALEN];
const char *ssid, *pp = "", *pb;
char setname[16], ssid_copy[33];
size_t l = sizeof(buf), al;
lws_wifi_creds_t *cred;
/*
* Let's try to retreive the last successful connect info for this
* netdev
*/
lws_snprintf(setname, sizeof(setname), "netdev.last.%s", wnd->inst.name);
if (lws_settings_plat_get(netdevs->si, setname, buf, &l))
return 1;
lwsl_notice("%s: last successful %s\n", __func__, buf);
ssid = lws_json_simple_find((const char *)buf, l, "\"ssid\":", &al);
if (!ssid || al > 32)
return 1;
memcpy(ssid_copy, ssid, al);
ssid_copy[al + 1] = '\0';
pb = lws_json_simple_find((const char *)buf, l, "\"bssid\":", &al);
if (!pb)
return 1;
lws_hex_to_byte_array(pb, bssid, sizeof(bssid));
/*
* make sure we have our device's connection credentials at hand
*/
if (!netdevs->ac_creds &&
lws_netdev_credentials_settings_get(netdevs))
return 1;
netdevs->refcount_creds++;
cred = lws_netdev_credentials_find(netdevs, ssid_copy, bssid);
if (cred)
pp = cred->passphrase;
lws_strncpy(wnd->current_attempt_ssid, ssid_copy,
sizeof(wnd->current_attempt_ssid));
memcpy(wnd->current_attempt_bssid, bssid, LWS_ETH_ALEN);
wnd->inst.ops->connect(&wnd->inst, ssid_copy, pp, bssid);
if (!--netdevs->refcount_creds) {
lws_dll2_owner_clear(&netdevs->owner_creds);
lwsac_free(&netdevs->ac_creds);
}
return 0;
}

View File

@ -0,0 +1,156 @@
/*
* 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 ((int)n * LWS_LED_MAX_INTENSITY) / 2;
}
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_half = {
.func = lws_led_func_static,
.ledphase_offset = 1,
.ledphase_total = 0,
.ms = 0
};
const lws_led_sequence_def_t lws_pwmseq_static_on = {
.func = lws_led_func_static,
.ledphase_offset = 2,
.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,69 @@
/*
* lws_settings
*
* 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>
lws_settings_instance_t *
lws_settings_init(const lws_settings_ops_t *so, void *opaque_plat)
{
lws_settings_instance_t *si = lws_zalloc(sizeof(*si), __func__);
if (!si)
return NULL;
si->so = so;
si->opaque_plat = opaque_plat;
return si;
}
void
lws_settings_deinit(lws_settings_instance_t **si)
{
lws_free(*si);
*si = NULL;
}
int
lws_settings_plat_printf(lws_settings_instance_t *si, const char *name,
const char *format, ...)
{
va_list ap;
uint8_t *p;
int n;
va_start(ap, format);
n = vsnprintf(NULL, 0, format, ap);
va_end(ap);
p = lws_malloc(n + 2, __func__);
va_start(ap, format);
vsnprintf((char *)p, n + 2, format, ap);
va_end(ap);
n = si->so->set(si, name, p, n);
lws_free(p);
return n;
}

View File

@ -0,0 +1,130 @@
/*
* SPI bitbang implementation using generic gpio
*
* 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 <libwebsockets.h>
int
lws_bb_spi_init(const lws_spi_ops_t *octx)
{
lws_bb_spi_t *ctx = (lws_bb_spi_t *)octx;
int n;
for (n = 0; n < LWS_SPI_BB_MAX_CH; n++) {
if (ctx->flags & (1 << n))
ctx->gpio->mode(ctx->ncs[n], LWSGGPIO_FL_WRITE);
if (ctx->flags & (1 << (n + 4)))
ctx->gpio->mode(ctx->ncmd[n], LWSGGPIO_FL_WRITE);
}
ctx->gpio->mode(ctx->clk, LWSGGPIO_FL_WRITE |
((octx->bus_mode & LWSSPIMODE_CPOL) ?
0 : LWSGGPIO_FL_START_LOW));
ctx->gpio->mode(ctx->mosi, LWSGGPIO_FL_WRITE | LWSGGPIO_FL_START_LOW);
ctx->gpio->mode(ctx->miso, LWSGGPIO_FL_READ | LWSGGPIO_FL_PULLUP);
return 0;
}
/* if active, prepare DnC before this and call separately for Cmd / Data */
static void
lws_bb_spi_write(lws_bb_spi_t *ctx, const uint8_t *buf, size_t len)
{
uint8_t u, inv = !!(ctx->bb_ops.bus_mode & LWSSPIMODE_CPOL);
while (len--) {
int n;
u = *buf++;
for (n = 0; n < 4; n++) {
ctx->gpio->set(ctx->clk, inv);
ctx->gpio->set(ctx->mosi, !!(u & 0x80));
ctx->gpio->set(ctx->clk, !inv);
ctx->gpio->set(ctx->clk, inv);
ctx->gpio->set(ctx->mosi, !!(u & 0x40));
ctx->gpio->set(ctx->clk, !inv);
u <<= 2;
}
}
ctx->gpio->set(ctx->clk, 0 ^ inv);
}
static void
lws_bb_spi_read(lws_bb_spi_t *ctx, uint8_t *buf, size_t len)
{
uint8_t u = 0;
uint8_t inv = !!(ctx->bb_ops.bus_mode & LWSSPIMODE_CPOL);
while (len--) {
int n;
for (n = 0; n < 8; n++) {
ctx->gpio->set(ctx->clk, inv);
u = (u << 1) | !!ctx->gpio->read(ctx->miso);
ctx->gpio->set(ctx->mosi, !!(u & 0x80));
ctx->gpio->set(ctx->clk, !inv);
}
*buf++ = u;
}
ctx->gpio->set(ctx->clk, 0 ^ inv);
}
int
lws_bb_spi_queue(const lws_spi_ops_t *octx, const lws_spi_desc_t *desc)
{
lws_bb_spi_t *ctx = (lws_bb_spi_t *)octx;
const uint8_t *src = desc->src;
/* clock to idle */
ctx->gpio->set(ctx->clk, 0 ^ !!(octx->bus_mode & LWSSPIMODE_CPOL));
/* enable nCS */
ctx->gpio->set(ctx->ncs[desc->channel], 0);
if (desc->count_cmd) {
ctx->gpio->set(ctx->ncmd[desc->channel], 0);
lws_bb_spi_write(ctx, src, desc->count_cmd);
ctx->gpio->set(ctx->ncmd[desc->channel], 1);
src += desc->count_cmd;
}
if (desc->count_write)
lws_bb_spi_write(ctx, desc->data, desc->count_write);
if (desc->count_read)
lws_bb_spi_read(ctx, desc->dest, desc->count_read);
if (desc->flags & LWS_SPI_FLAG_DATA_CONTINUE)
return 0;
/* disable nCS */
ctx->gpio->set(ctx->ncs[desc->channel], 1);
/* clock to idle */
ctx->gpio->set(ctx->clk, 0 ^ !!(octx->bus_mode & LWSSPIMODE_CPOL));
return 0;
}

View File

@ -0,0 +1,93 @@
/*
* Generic SPI
*
* Copyright (C) 2019 - 2022 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 <libwebsockets.h>
int
lws_spi_table_issue(const lws_spi_ops_t *spi_ops, uint32_t flags,
const uint8_t *p, size_t len)
{
lws_spi_desc_t desc;
size_t pos = 0;
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
desc.flags = flags;
while (pos < len) {
desc.count_write = p[pos++];
desc.src = (uint8_t *)&p[pos++];
if (desc.count_write)
desc.data = (uint8_t *)&p[pos];
else
desc.data = NULL;
if (spi_ops->queue(spi_ops, &desc) != ESP_OK) {
lwsl_err("%s: unable to queue\n", __func__);
return 1;
}
pos += desc.count_write;
}
return 0;
}
int
lws_spi_readback(const lws_spi_ops_t *spi_ops, uint32_t flags,
const uint8_t *p, size_t len, uint8_t *rb, size_t rb_len)
{
lws_spi_desc_t desc;
size_t pos = 0;
memset(&desc, 0, sizeof(desc));
desc.count_cmd = 1;
desc.flags = flags;
while (pos < len) {
desc.count_write = p[pos++];
desc.src = (uint8_t *)&p[pos++];
if (desc.count_write)
desc.data = (uint8_t *)&p[pos];
else
desc.data = NULL;
desc.dest = rb;
desc.count_read = rb_len;
if (spi_ops->queue(spi_ops, &desc) != ESP_OK) {
lwsl_err("%s: unable to queue\n", __func__);
return 1;
}
pos += desc.count_write;
}
return 0;
}