Update Files
This commit is contained in:
33
Kinc/Sources/kinc/libs/drivers/CMakeLists.txt
Normal file
33
Kinc/Sources/kinc/libs/drivers/CMakeLists.txt
Normal 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()
|
||||
|
44
Kinc/Sources/kinc/libs/drivers/README.md
Normal file
44
Kinc/Sources/kinc/libs/drivers/README.md
Normal 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.
|
||||
|
156
Kinc/Sources/kinc/libs/drivers/button/README.md
Normal file
156
Kinc/Sources/kinc/libs/drivers/button/README.md
Normal 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
|
532
Kinc/Sources/kinc/libs/drivers/button/lws-button.c
Normal file
532
Kinc/Sources/kinc/libs/drivers/button/lws-button.c
Normal 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;
|
||||
}
|
36
Kinc/Sources/kinc/libs/drivers/display/README.md
Normal file
36
Kinc/Sources/kinc/libs/drivers/display/README.md
Normal 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.
|
383
Kinc/Sources/kinc/libs/drivers/display/ili9341-spi.c
Normal file
383
Kinc/Sources/kinc/libs/drivers/display/ili9341-spi.c
Normal 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;
|
||||
}
|
443
Kinc/Sources/kinc/libs/drivers/display/lws-display.c
Normal file
443
Kinc/Sources/kinc/libs/drivers/display/lws-display.c
Normal 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;
|
||||
}
|
||||
|
463
Kinc/Sources/kinc/libs/drivers/display/spd1656-spi.c
Normal file
463
Kinc/Sources/kinc/libs/drivers/display/spd1656-spi.c
Normal 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;
|
||||
}
|
278
Kinc/Sources/kinc/libs/drivers/display/ssd1306-i2c.c
Normal file
278
Kinc/Sources/kinc/libs/drivers/display/ssd1306-i2c.c
Normal 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;
|
||||
}
|
495
Kinc/Sources/kinc/libs/drivers/display/ssd1675b-spi.c
Normal file
495
Kinc/Sources/kinc/libs/drivers/display/ssd1675b-spi.c
Normal 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;
|
||||
}
|
1017
Kinc/Sources/kinc/libs/drivers/display/uc8176-spi.c
Normal file
1017
Kinc/Sources/kinc/libs/drivers/display/uc8176-spi.c
Normal file
File diff suppressed because it is too large
Load Diff
135
Kinc/Sources/kinc/libs/drivers/i2c/bitbang/lws-bb-i2c.c
Normal file
135
Kinc/Sources/kinc/libs/drivers/i2c/bitbang/lws-bb-i2c.c
Normal 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);
|
||||
}
|
59
Kinc/Sources/kinc/libs/drivers/i2c/lws-i2c.c
Normal file
59
Kinc/Sources/kinc/libs/drivers/i2c/lws-i2c.c
Normal 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;
|
||||
}
|
155
Kinc/Sources/kinc/libs/drivers/led/README.md
Normal file
155
Kinc/Sources/kinc/libs/drivers/led/README.md
Normal 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
|
120
Kinc/Sources/kinc/libs/drivers/led/led-gpio.c
Normal file
120
Kinc/Sources/kinc/libs/drivers/led/led-gpio.c
Normal 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));
|
||||
}
|
200
Kinc/Sources/kinc/libs/drivers/led/led-seq.c
Normal file
200
Kinc/Sources/kinc/libs/drivers/led/led-seq.c
Normal 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;
|
||||
}
|
39
Kinc/Sources/kinc/libs/drivers/led/private-lib-drivers-led.h
Normal file
39
Kinc/Sources/kinc/libs/drivers/led/private-lib-drivers-led.h
Normal 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);
|
272
Kinc/Sources/kinc/libs/drivers/netdev/netdev.c
Normal file
272
Kinc/Sources/kinc/libs/drivers/netdev/netdev.c
Normal 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;
|
||||
}
|
243
Kinc/Sources/kinc/libs/drivers/netdev/wifi.c
Normal file
243
Kinc/Sources/kinc/libs/drivers/netdev/wifi.c
Normal 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;
|
||||
}
|
156
Kinc/Sources/kinc/libs/drivers/pwm/pwm.c
Normal file
156
Kinc/Sources/kinc/libs/drivers/pwm/pwm.c
Normal 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
|
||||
};
|
69
Kinc/Sources/kinc/libs/drivers/settings/settings.c
Normal file
69
Kinc/Sources/kinc/libs/drivers/settings/settings.c
Normal 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;
|
||||
}
|
130
Kinc/Sources/kinc/libs/drivers/spi/bitbang/lws-bb-spi.c
Normal file
130
Kinc/Sources/kinc/libs/drivers/spi/bitbang/lws-bb-spi.c
Normal 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;
|
||||
}
|
93
Kinc/Sources/kinc/libs/drivers/spi/lws-spi.c
Normal file
93
Kinc/Sources/kinc/libs/drivers/spi/lws-spi.c
Normal 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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user