diff options
author | Andy Green <andy@warmcat.com> | 2020-06-10 19:17:08 +0100 |
---|---|---|
committer | Andy Green <andy@warmcat.com> | 2020-06-10 19:17:08 +0100 |
commit | 15ce46d9716dfeb70ff4f6d25225bf78f1443a5b (patch) | |
tree | 6a687d780f8c39aad2724b30fb81d74ad8acbf18 /lib/drivers | |
parent | ad5a4f7040615c33fc5ab0903bf66ec0376510b9 (diff) | |
download | libwebsockets-15ce46d9716dfeb70ff4f6d25225bf78f1443a5b.tar.gz |
drivers: initial generic gpio and i2c plus bitbang
Make a start on generic peripheral and bus drivers to provide
meta-functionality regardless of platform.
On the one hand this simply provides...
- bitbang i2c on top of esp-idf gpio apis
- ssd1306 oled chip driver as found on Heltec WB32
- modifications to the minimal example test for esp32 to use that
... on the other hand, those capabilities are provided by creating:
- an abstract i2c class object
- an abstract gpio class object
- i2c class implementation using the abstract gpio for bitbang
- an abstract display class object
- an abstract display state (brightness, animated change,
on/off/init tracking, autodim after inactive, auto-off /
blanking after inactive)
... with the intention, eg, you only have to add a platform
implementation for the gpio to be able to use the i2c-based
display drivers and state handling, and i2c bitbang, without
any other modifications.
Diffstat (limited to 'lib/drivers')
-rw-r--r-- | lib/drivers/CMakeLists.txt | 14 | ||||
-rw-r--r-- | lib/drivers/devices/display/ssd1306.h | 66 | ||||
-rw-r--r-- | lib/drivers/display/lws-display.c | 126 | ||||
-rw-r--r-- | lib/drivers/display/ssd1306-i2c.c | 151 | ||||
-rw-r--r-- | lib/drivers/i2c/bitbang/lws-bb-i2c.c | 135 | ||||
-rw-r--r-- | lib/drivers/i2c/lws-i2c.c | 59 |
6 files changed, 551 insertions, 0 deletions
diff --git a/lib/drivers/CMakeLists.txt b/lib/drivers/CMakeLists.txt new file mode 100644 index 00000000..33a6b193 --- /dev/null +++ b/lib/drivers/CMakeLists.txt @@ -0,0 +1,14 @@ +list(APPEND SOURCES + drivers/display/lws-display.c + drivers/display/ssd1306-i2c.c + drivers/i2c/lws-i2c.c + drivers/i2c/bitbang/lws-bb-i2c.c +) + +if (LWS_ESP_PLATFORM) + list(APPEND SOURCES + plat/freertos/esp32/drivers/gpio-esp32.c) +endif() + +exports_to_parent_scope() + diff --git a/lib/drivers/devices/display/ssd1306.h b/lib/drivers/devices/display/ssd1306.h new file mode 100644 index 00000000..0d8fb459 --- /dev/null +++ b/lib/drivers/devices/display/ssd1306.h @@ -0,0 +1,66 @@ +/* + * Private register map for SSD1306 + * + * 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. + * + */ + +#if !defined(__LWS_SSD1306_H__) +#define __LWS_SSD1306_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, +}; + +#endif + diff --git a/lib/drivers/display/lws-display.c b/lib/drivers/display/lws-display.c new file mode 100644 index 00000000..32e85368 --- /dev/null +++ b/lib/drivers/display/lws-display.c @@ -0,0 +1,126 @@ +/* + * lws abstract display + * + * 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> + +static void +sul_cb(lws_sorted_usec_list_t *sul) +{ + lws_display_state_t *lds = lws_container_of(sul, lws_display_state_t, + sul); + + if (lds->bl_target > lds->bl_current) { + if (lds->bl_target - lds->bl_current < lds->bl_step) + lds->bl_current = lds->bl_target; + else + lds->bl_current += lds->bl_step; + } else { + if (lds->bl_current - lds->bl_target < lds->bl_step) + lds->bl_current = lds->bl_target; + else + lds->bl_current -= lds->bl_step; + } + + lds->disp->brightness(lds->disp, lds->bl_current); + + if (lds->bl_current != lds->bl_target) + /* + * Come back and move towards the target again in 50ms + */ + lws_sul_schedule(lds->ctx, 0, &lds->sul, + sul_cb, 50 * LWS_US_PER_MS); +} + +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); + + /* we fire both to dim and to blank... if already in dim state, blank */ + + if (lds->state == LWSDISPS_AUTODIMMED) { + lws_display_state_off(lds); + return; + } + + lds->state = LWSDISPS_AUTODIMMED; + lws_display_state_set_brightness(lds, lds->bl_dim, lds->bl_step); + + if (lds->off_ms >= 0) + lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb, + lds->off_ms * LWS_US_PER_MS); +} + +void +lws_display_state_init(lws_display_state_t *ds, struct lws_context *ctx, + int dim_ms, int off_ms, lws_display_brightness active, + lws_display_brightness dim, const lws_display_t *disp) +{ + memset(ds, 0, sizeof(*ds)); + ds->disp = disp; + ds->ctx = ctx; + ds->autodim_ms = dim_ms; + ds->off_ms = off_ms; + ds->bl_active = active; + ds->bl_dim = dim; +} + +void +lws_display_state_set_brightness(lws_display_state_t *lds, + lws_display_brightness target, + lws_display_brightness step) +{ + lds->bl_target = target; + lds->bl_step = step; + + lws_sul_schedule(lds->ctx, 0, &lds->sul, sul_cb, 1); +} + +void +lws_display_state_active(lws_display_state_t *lds) +{ + if (lds->state == LWSDISPS_OFF) + lds->disp->power(lds->disp, 1); + + if (lds->bl_current != lds->bl_active) + lws_display_state_set_brightness(lds, lds->bl_active, 2); + + /* reset the autodim timer */ + if (lds->autodim_ms >= 0) + lws_sul_schedule(lds->ctx, 0, &lds->sul_autodim, sul_autodim_cb, + lds->autodim_ms * LWS_US_PER_MS); + + lds->state = LWSDISPS_ACTIVE; +} + +void +lws_display_state_off(lws_display_state_t *lds) +{ + lds->disp->power(lds->disp, 0); + lws_sul_cancel(&lds->sul); + lws_sul_cancel(&lds->sul_autodim); + lds->bl_current = 0; + lds->state = LWSDISPS_OFF; +} diff --git a/lib/drivers/display/ssd1306-i2c.c b/lib/drivers/display/ssd1306-i2c.c new file mode 100644 index 00000000..a050271f --- /dev/null +++ b/lib/drivers/display/ssd1306-i2c.c @@ -0,0 +1,151 @@ +/* + * lws abstract display implementation for ssd1306 on 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. + */ + +#include <libwebsockets.h> +#include <drivers/devices/display/ssd1306.h> + + +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, 0, /* start at lowest */ + SSD1306_SETPRECHARGE, (0xf << 4) | (1 << 0), + SSD1306_SETVCOMDESELECT, (4 << 4), + SSD1306_DEACTIVATE_SCROLL, + SSD1306_DISPLAYALLON_RESUME, + SSD1306_NORMALDISPLAY, + SSD1306_DISPLAYON +}; + +int +lws_display_ssd1306_i2c_init(const struct lws_display *disp) +{ + const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)disp; + + 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); + +#if defined(LWS_PLAT_FREERTOS) + vTaskDelay(10); +#else + usleep(10000); +#endif + si->gpio->set(si->reset_gpio, 1); +#if defined(LWS_PLAT_FREERTOS) + vTaskDelay(1); +#else + usleep(1000); +#endif + } + + 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; + } + + return 0; +} + +int +lws_display_ssd1306_i2c_brightness(const struct lws_display *disp, uint8_t b) +{ + const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)disp; + 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(const struct lws_display *disp, const uint8_t *src, + lws_display_scalar x, lws_display_scalar y, + lws_display_scalar w, lws_display_scalar h) +{ + const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)disp; + uint8_t ba[6]; + int n, m; + + /* + * The display is arranged in 128x8 bands, with one byte containing + * the 8 vertical pixels of the band. + */ + + if (h < 8) + h = 8; + + ba[0] = SSD1306_COLUMNADDR; + ba[1] = x; + ba[2] = x + w - 1; + ba[3] = SSD1306_PAGEADDR; + ba[4] = y / 8; + ba[5] = ba[4] + (h / 8) - 1; + + if (lws_i2c_command_list(si->i2c, si->i2c7_address, + ba, LWS_ARRAY_SIZE(ba))) { + lwsl_err("%s: fail\n", __func__); + return 1; + } + + for (n = 0; n < (w * h) / 8;) { + 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 < w; m++) + lws_bb_i2c_write(si->i2c, src[n++]); + + lws_bb_i2c_stop(si->i2c); + y += 8; + } + + return 0; +} + +int +lws_display_ssd1306_i2c_power(const struct lws_display *disp, int state) +{ + const lws_display_ssd1306_t *si = (const lws_display_ssd1306_t *)disp; + + if (!state) + return lws_i2c_command(si->i2c, si->i2c7_address, + SSD1306_DISPLAYOFF | !!state); + + return lws_display_ssd1306_i2c_init(disp); +} diff --git a/lib/drivers/i2c/bitbang/lws-bb-i2c.c b/lib/drivers/i2c/bitbang/lws-bb-i2c.c new file mode 100644 index 00000000..99e92a55 --- /dev/null +++ b/lib/drivers/i2c/bitbang/lws-bb-i2c.c @@ -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); +} diff --git a/lib/drivers/i2c/lws-i2c.c b/lib/drivers/i2c/lws-i2c.c new file mode 100644 index 00000000..fa00008f --- /dev/null +++ b/lib/drivers/i2c/lws-i2c.c @@ -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; +} |