/* * lws abstract display implementation for ssd1306 on i2c * * Copyright (C) 2019 - 2022 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * * 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 #include 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; }