diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b067bad --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +.idea + +*.o +*.P diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..81e2502 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Ivan Belokobylskiy + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0bc25cc --- /dev/null +++ b/README.md @@ -0,0 +1,179 @@ +ST7789 Driver for MicroPython +============================= + + +Overview +-------- +This is a driver for MicroPython to handle cheap displays +based on ST7789 chip. + +

+ ST7789 display photo +

+ +It is written in pure C, so you have to build +firmware by yourself. +Only ESP8266 and ESP32 are supported for now. + + +Building instruction +--------------------- + +Prepare build tools as described in the manual. +You should follow the instruction for building MicroPython and +ensure that you can build the firmware without this display module. + +Clone this module alongside the MPY sources: + + $ git clone https://github.com/devbis/st7789_mpy.git + +Go to MicroPython ports directory and for ESP8266 run: + + $ cd micropython/ports/esp8266 + +for ESP32: + + $ cd micropython/ports/esp32 + +And then compile the module with specified USER_C_MODULES dir + + $ make USER_C_MODULES=../../../st7789_mpy/ all + + +If you have other user modules, copy the st7789_driver/st7789 to +the user modules directory + +Upload the resulting firmware to your MCU as usual with esptool.py +(See +[MicroPython docs](http://docs.micropython.org/en/latest/esp8266/tutorial/intro.html#deploying-the-firmware) +for more info) + + +Working examples +---------------- + +This module was tested on ESP32 and ESP8266 MCUs. + +You have to provide `machine.SPI` object and at least two pins for RESET and +DC pins on the screen for the display object. + + + # ESP 8266 + + import machine + import st7789 + spi = machine.SPI(1, baudrate=40000000, polarity=1) + display = st7789.ST7789(spi, 240, 240, reset=machine.Pin(5, machine.Pin.OUT), dc=machine.Pin(4, machine.Pin.OUT)) + + +For ESP32 modules you have to provide specific pins for SPI. +Unfortunately, I was unable to run this display on SPI(1) interface. +For machine.SPI(2) == VSPI you have to use + +- CLK: Pin(18) +- MOSI: Pin(23) + +Other SPI pins are not used. + + + # ESP32 + + import machine + import st7789 + spi = machine.SPI(2, baudrate=40000000, polarity=1, sck=machine.Pin(18), mosi=machine.Pin(23)) + display = st7789.ST7789(spi, 240, 240, reset=machine.Pin(4, machine.Pin.OUT), dc=machine.Pin(2, machine.Pin.OUT)) + + +I couldn't run the display on an SPI with baudrate higher than 40MHZ + +Methods +------------- + +This driver supports only 16bit colors in RGB565 notation. + + +- `ST7789.fill(color)` + + Fill the entire display with the specified color. + +- `ST7789.pixel(x, y, color)` + + Set the specified pixel to the given color. + +- `ST7789.line(x0, y0, x1, y1, color)` + + Draws a single line with the provided `color` from (`x0`, `y0`) to + (`x1`, `y1`). + +- `ST7789.hline(x, y, length, color)` + + Draws a single horizontal line with the provided `color` and `length` + in pixels. Along with `vline`, this is a fast version with reduced + number of SPI calls. + +- `ST7789.vline(x, y, length, color)` + + Draws a single horizontal line with the provided `color` and `length` + in pixels. + +- `ST7789.rect(x, y, width, height, color)` + + Draws a rectangle from (`x`, `y`) with corresponding dimensions + +- `ST7789.fill_rect(x, y, width, height, color)` + + Fill a rectangle starting from (`x`, `y`) coordinates + +- `ST7789.blit_buffer(buffer, x, y, width, height)` + + Copy bytes() or bytearray() content to the screen internal memory. + Note: every color requires 2 bytes in the array + +Also, the module exposes predefined colors: + `BLACK`, `BLUE`, `RED`, `GREEN`, `CYAN`, `MAGENTA`, `YELLOW`, and `WHITE` + + +Performance +----------- + +For the comparison I used an excelent library for Arduino +that can handle this screen. + +https://github.com/ananevilya/Arduino-ST7789-Library/ + +Also, I used my slow driver for this screen, written in pure python. + +https://github.com/devbis/st7789py_mpy/ + +I used these modules to draw a line from 0,0 to 239,239 +The table represents the time in milliseconds for each case + +| | Arduino-ST7789 | st7789py_mpy | st7789_mpy | +|---------|----------------|--------------|---------------| +| ESP8266 | 26 | 450 | 12 | +| ESP32 | 23 | 450 | 47 | + + +As you can see, the ESP32 module draws a line 4 times slower than +the older ESP8266 module. + + +Troubleshooting +--------------- + +#### Overflow of iram1_0_seg + +When building a firmware for esp8266 you can see this failure message from +the linker: + + LINK build/firmware.elf + xtensa-lx106-elf-ld: build/firmware.elf section `.text' will not fit in region `iram1_0_seg' + xtensa-lx106-elf-ld: region `iram1_0_seg' overflowed by 292 bytes + Makefile:192: recipe for target 'build/firmware.elf' failed + +To fix this issue, you have to put st7789 module to irom0 section. +Edit `esp8266_common.ld` file in the `ports/esp8266` dir and add a line + + *st7789/*.o(.literal* .text*) + +in the `.irom0.text : ALIGN(4)` section diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..6c6aa7c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.0 \ No newline at end of file diff --git a/docs/ST7789.jpg b/docs/ST7789.jpg new file mode 100644 index 0000000..9e5c32f Binary files /dev/null and b/docs/ST7789.jpg differ diff --git a/st7789/micropython.mk b/st7789/micropython.mk new file mode 100644 index 0000000..79dda11 --- /dev/null +++ b/st7789/micropython.mk @@ -0,0 +1,6 @@ +ST7789_MOD_DIR := $(USERMOD_DIR) +SRC_USERMOD += $(addprefix $(ST7789_MOD_DIR)/, \ + st7789.c \ +) +CFLAGS_USERMOD += -I$(ST7789_MOD_DIR) -DMODULE_ST7789_ENABLED=1 +# CFLAGS_USERMOD += -DEXPOSE_EXTRA_METHODS=1 diff --git a/st7789/st7789.c b/st7789/st7789.c new file mode 100644 index 0000000..bacd100 --- /dev/null +++ b/st7789/st7789.c @@ -0,0 +1,559 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ivan Belokobylskiy + * + * 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. + */ + +#define __ST7789_VERSION__ "0.1.0" + +#include "py/obj.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "extmod/machine_spi.h" + +#include "st7789.h" + +#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; } +#define ABS(N) ((N<0)?(-N):(N)) +#define mp_hal_delay_ms(delay) (mp_hal_delay_us(delay * 1000)) + +#define CS_LOW() { if(self->cs) {mp_hal_pin_write(self->cs, 0);} } +#define CS_HIGH() { if(self->cs) {mp_hal_pin_write(self->cs, 1);} } +#define DC_LOW() (mp_hal_pin_write(self->dc, 0)) +#define DC_HIGH() (mp_hal_pin_write(self->dc, 1)) +#define RESET_LOW() (mp_hal_pin_write(self->reset, 0)) +#define RESET_HIGH() (mp_hal_pin_write(self->reset, 1)) + + +STATIC void write_spi(mp_obj_base_t *spi_obj, const uint8_t *buf, int len) { + mp_machine_spi_p_t *spi_p = (mp_machine_spi_p_t*)spi_obj->type->protocol; + spi_p->transfer(spi_obj, len, buf, NULL); +} + +// this is the actual C-structure for our new object +typedef struct _st7789_ST7789_obj_t { + mp_obj_base_t base; + + mp_obj_base_t *spi_obj; + uint8_t width; + uint8_t height; + mp_hal_pin_obj_t reset; + mp_hal_pin_obj_t dc; + mp_hal_pin_obj_t cs; + mp_hal_pin_obj_t backlight; +} st7789_ST7789_obj_t; + + +// just a definition +mp_obj_t st7789_ST7789_make_new( const mp_obj_type_t *type, + size_t n_args, + size_t n_kw, + const mp_obj_t *args ); +STATIC void st7789_ST7789_print( const mp_print_t *print, + mp_obj_t self_in, + mp_print_kind_t kind ) { + (void)kind; + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_printf(print, "", self->width, self->height, self->spi_obj); +} + +/* methods start */ + +STATIC void write_cmd(st7789_ST7789_obj_t *self, uint8_t cmd, const uint8_t *data, int len) { + CS_LOW() + if (cmd) { + DC_LOW(); + write_spi(self->spi_obj, &cmd, 1); + } + if (len > 0) { + DC_HIGH(); + write_spi(self->spi_obj, data, len); + } + CS_HIGH() +} + +STATIC void set_window(st7789_ST7789_obj_t *self, uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { + if (x0 > x1 || x1 > self->width) { + return; + } + if (y0 > y1 || y1 > self->height) { + return; + } + uint8_t bufx[4] = {x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF}; + uint8_t bufy[4] = {y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF}; + write_cmd(self, ST7789_CASET, bufx, 4); + write_cmd(self, ST7789_RASET, bufy, 4); + write_cmd(self, ST7789_RAMWR, NULL, 0); +} + +STATIC void draw_pixel(st7789_ST7789_obj_t *self, uint8_t x, uint8_t y, uint16_t color) { + uint8_t hi = color >> 8, lo = color; + set_window(self, x, y, x, y); + DC_HIGH(); + CS_LOW(); + write_spi(self->spi_obj, &hi, 1); + write_spi(self->spi_obj, &lo, 1); + CS_HIGH(); +} + +STATIC mp_obj_t st7789_ST7789_hard_reset(mp_obj_t self_in) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + + CS_LOW(); + RESET_HIGH(); + mp_hal_delay_ms(50); + RESET_LOW(); + mp_hal_delay_ms(50); + RESET_HIGH(); + mp_hal_delay_ms(150); + CS_HIGH(); + return mp_const_none; +} + +STATIC mp_obj_t st7789_ST7789_soft_reset(mp_obj_t self_in) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + + write_cmd(self, ST7789_SWRESET, NULL, 0); + mp_hal_delay_ms(150); + return mp_const_none; +} + +// do not expose extra method to reduce size +#ifdef EXPOSE_EXTRA_METHODS +STATIC mp_obj_t st7789_ST7789_write(mp_obj_t self_in, mp_obj_t command, mp_obj_t data) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + + mp_buffer_info_t src; + if (data == mp_const_none) { + write_cmd(self, (uint8_t)mp_obj_get_int(command), NULL, 0); + } else { + mp_get_buffer_raise(data, &src, MP_BUFFER_READ); + write_cmd(self, (uint8_t)mp_obj_get_int(command), (const uint8_t*)src.buf, src.len); + } + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_3(st7789_ST7789_write_obj, st7789_ST7789_write); + +MP_DEFINE_CONST_FUN_OBJ_1(st7789_ST7789_hard_reset_obj, st7789_ST7789_hard_reset); +MP_DEFINE_CONST_FUN_OBJ_1(st7789_ST7789_soft_reset_obj, st7789_ST7789_soft_reset); + +STATIC mp_obj_t st7789_ST7789_sleep_mode(mp_obj_t self_in, mp_obj_t value) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(mp_obj_is_true(value)) { + write_cmd(self, ST7789_SLPIN, NULL, 0); + } else { + write_cmd(self, ST7789_SLPOUT, NULL, 0); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(st7789_ST7789_sleep_mode_obj, st7789_ST7789_sleep_mode); + +STATIC mp_obj_t st7789_ST7789_set_window(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x0 = mp_obj_get_int(args[1]); + mp_int_t x1 = mp_obj_get_int(args[2]); + mp_int_t y0 = mp_obj_get_int(args[3]); + mp_int_t y1 = mp_obj_get_int(args[4]); + + set_window(self, x0, y0, x1, y1); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_set_window_obj, 5, 5, st7789_ST7789_set_window); + +#endif + +STATIC mp_obj_t st7789_ST7789_inversion_mode(mp_obj_t self_in, mp_obj_t value) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + if(mp_obj_is_true(value)) { + write_cmd(self, ST7789_INVON, NULL, 0); + } else { + write_cmd(self, ST7789_INVOFF, NULL, 0); + } + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(st7789_ST7789_inversion_mode_obj, st7789_ST7789_inversion_mode); + + +STATIC void fill_color_buffer(mp_obj_base_t* spi_obj, uint16_t color, int length) { + uint8_t hi = color >> 8, lo = color; + const int buffer_pixel_size = 128; + int chunks = length / buffer_pixel_size; + int rest = length % buffer_pixel_size; + + uint8_t buffer[buffer_pixel_size * 2]; // 128 pixels + // fill buffer with color data + for (int i = 0; i < length && i < buffer_pixel_size; i++) { + buffer[i*2] = hi; + buffer[i*2 + 1] = lo; + } + + if (chunks) { + for (int j = 0; j < chunks; j ++) { + write_spi(spi_obj, buffer, buffer_pixel_size*2); + } + } + if (rest) { + write_spi(spi_obj, buffer, rest*2); + } +} + + +STATIC mp_obj_t st7789_ST7789_fill_rect(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t h = mp_obj_get_int(args[4]); + mp_int_t color = mp_obj_get_int(args[5]); + + set_window(self, x, y, x + w - 1, y + h - 1); + DC_HIGH(); + CS_LOW(); + fill_color_buffer(self->spi_obj, color, w * h); + CS_HIGH(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_fill_rect_obj, 6, 6, st7789_ST7789_fill_rect); + + +STATIC mp_obj_t st7789_ST7789_fill(mp_obj_t self_in, mp_obj_t _color) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t color = mp_obj_get_int(_color); + + set_window(self, 0, 0, self->width, self->height); + DC_HIGH(); + CS_LOW(); + fill_color_buffer(self->spi_obj, color, self->width * self->height); + CS_HIGH(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(st7789_ST7789_fill_obj, st7789_ST7789_fill); + + +STATIC mp_obj_t st7789_ST7789_pixel(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t color = mp_obj_get_int(args[3]); + + draw_pixel(self, x, y, color); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_pixel_obj, 4, 4, st7789_ST7789_pixel); + + +STATIC mp_obj_t st7789_ST7789_line(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x0 = mp_obj_get_int(args[1]); + mp_int_t y0 = mp_obj_get_int(args[2]); + mp_int_t x1 = mp_obj_get_int(args[3]); + mp_int_t y1 = mp_obj_get_int(args[4]); + mp_int_t color = mp_obj_get_int(args[5]); + + int16_t steep = ABS(y1 - y0) > ABS(x1 - x0); + if (steep) { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = ABS(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0<=x1; x0++) { + if (steep) { + draw_pixel(self, y0, x0, color); + } else { + draw_pixel(self, x0, y0, color); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_line_obj, 6, 6, st7789_ST7789_line); + + +STATIC mp_obj_t st7789_ST7789_blit_buffer(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_buffer_info_t buf_info; + mp_get_buffer_raise(args[1], &buf_info, MP_BUFFER_READ); + mp_int_t x = mp_obj_get_int(args[2]); + mp_int_t y = mp_obj_get_int(args[3]); + mp_int_t w = mp_obj_get_int(args[4]); + mp_int_t h = mp_obj_get_int(args[5]); + + set_window(self, x, y, x + w - 1, y + h - 1); + DC_HIGH(); + CS_LOW(); + + const int buf_size = 256; + int i = 0; + int chunks = buf_info.len / buf_size; + int rest = buf_info.len % buf_size; + for (; i < chunks; i ++) { + write_spi(self->spi_obj, (const uint8_t*)buf_info.buf + i*buf_size, buf_size); + } + if (rest) { + write_spi(self->spi_obj, (const uint8_t*)buf_info.buf + i*buf_size, rest); + } + CS_HIGH(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_blit_buffer_obj, 6, 6, st7789_ST7789_blit_buffer); + + +STATIC mp_obj_t st7789_ST7789_init(mp_obj_t self_in) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(self_in); + st7789_ST7789_hard_reset(self_in); + st7789_ST7789_soft_reset(self_in); + write_cmd(self, ST7789_SLPOUT, NULL, 0); + + const uint8_t color_mode[] = { COLOR_MODE_65K | COLOR_MODE_16BIT}; + write_cmd(self, ST7789_COLMOD, color_mode, 1); + mp_hal_delay_ms(10); + const uint8_t madctl[] = { ST7789_MADCTL_ML | ST7789_MADCTL_RGB }; + write_cmd(self, ST7789_MADCTL, madctl, 1); + + write_cmd(self, ST7789_INVON, NULL, 0); + mp_hal_delay_ms(10); + write_cmd(self, ST7789_NORON, NULL, 0); + mp_hal_delay_ms(10); + + const mp_obj_t args[] = { + self_in, + mp_obj_new_int(0), + mp_obj_new_int(0), + mp_obj_new_int(self->width), + mp_obj_new_int(self->height), + mp_obj_new_int(BLACK) + }; + st7789_ST7789_fill_rect(6, args); + write_cmd(self, ST7789_DISPON, NULL, 0); + mp_hal_delay_ms(500); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_1(st7789_ST7789_init_obj, st7789_ST7789_init); + + +STATIC void fast_hline(st7789_ST7789_obj_t *self, uint8_t x, uint8_t y, uint16_t w, uint16_t color) { + set_window(self, x, y, x + w - 1, y); + DC_HIGH(); + CS_LOW(); + fill_color_buffer(self->spi_obj, color, w); + CS_HIGH(); +} + + +STATIC void fast_vline(st7789_ST7789_obj_t *self, uint8_t x, uint8_t y, uint16_t w, uint16_t color) { + set_window(self, x, y, x, y + w - 1); + DC_HIGH(); + CS_LOW(); + fill_color_buffer(self->spi_obj, color, w); + CS_HIGH(); +} + + +STATIC mp_obj_t st7789_ST7789_hline(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t color = mp_obj_get_int(args[4]); + + fast_hline(self, x, y, w, color); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_hline_obj, 5, 5, st7789_ST7789_hline); + + +STATIC mp_obj_t st7789_ST7789_vline(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t color = mp_obj_get_int(args[4]); + + fast_vline(self, x, y, w, color); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_vline_obj, 5, 5, st7789_ST7789_vline); + + +STATIC mp_obj_t st7789_ST7789_rect(size_t n_args, const mp_obj_t *args) { + st7789_ST7789_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_int_t x = mp_obj_get_int(args[1]); + mp_int_t y = mp_obj_get_int(args[2]); + mp_int_t w = mp_obj_get_int(args[3]); + mp_int_t h = mp_obj_get_int(args[4]); + mp_int_t color = mp_obj_get_int(args[5]); + + fast_hline(self, x, y, w, color); + fast_vline(self, x, y, h, color); + fast_hline(self, x, y + h - 1, w, color); + fast_vline(self, x + w - 1, y, h, color); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(st7789_ST7789_rect_obj, 6, 6, st7789_ST7789_rect); + + +STATIC const mp_rom_map_elem_t st7789_ST7789_locals_dict_table[] = { + // Do not expose internal functions to fit iram_0 section +#ifdef EXPOSE_EXTRA_METHODS + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&st7789_ST7789_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_hard_reset), MP_ROM_PTR(&st7789_ST7789_hard_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&st7789_ST7789_soft_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_sleep_mode), MP_ROM_PTR(&st7789_ST7789_sleep_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_inversion_mode), MP_ROM_PTR(&st7789_ST7789_inversion_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_window), MP_ROM_PTR(&st7789_ST7789_set_window_obj) }, +#endif + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&st7789_ST7789_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixel), MP_ROM_PTR(&st7789_ST7789_pixel_obj) }, + { MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&st7789_ST7789_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_blit_buffer), MP_ROM_PTR(&st7789_ST7789_blit_buffer_obj) }, + { MP_ROM_QSTR(MP_QSTR_fill_rect), MP_ROM_PTR(&st7789_ST7789_fill_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_fill), MP_ROM_PTR(&st7789_ST7789_fill_obj) }, + { MP_ROM_QSTR(MP_QSTR_hline), MP_ROM_PTR(&st7789_ST7789_hline_obj) }, + { MP_ROM_QSTR(MP_QSTR_vline), MP_ROM_PTR(&st7789_ST7789_vline_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&st7789_ST7789_rect_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(st7789_ST7789_locals_dict, st7789_ST7789_locals_dict_table); +/* methods end */ + + +const mp_obj_type_t st7789_ST7789_type = { + { &mp_type_type }, + .name = MP_QSTR_ST7789, + .print = st7789_ST7789_print, + .make_new = st7789_ST7789_make_new, + .locals_dict = (mp_obj_dict_t*)&st7789_ST7789_locals_dict, +}; + +mp_obj_t st7789_ST7789_make_new(const mp_obj_type_t *type, + size_t n_args, + size_t n_kw, + const mp_obj_t *all_args ) { + enum { ARG_spi, ARG_width, ARG_height, ARG_reset, ARG_dc, ARG_cs, ARG_backlight }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_spi, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_width, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, + { MP_QSTR_height, MP_ARG_INT | MP_ARG_REQUIRED, {.u_int = 0} }, + { MP_QSTR_reset, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_dc, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_cs, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_backlight, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + }; + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // create new object + st7789_ST7789_obj_t *self = m_new_obj(st7789_ST7789_obj_t); + self->base.type = &st7789_ST7789_type; + + // set parameters + mp_obj_base_t *spi_obj = (mp_obj_base_t*)MP_OBJ_TO_PTR(args[ARG_spi].u_obj); + self->spi_obj = spi_obj; + self->width = args[ARG_width].u_int; + self->height = args[ARG_height].u_int; + + if (args[ARG_reset].u_obj == MP_OBJ_NULL + || args[ARG_dc].u_obj == MP_OBJ_NULL) { + mp_raise_ValueError("must specify all of reset/dc pins"); + } + + self->reset = mp_hal_get_pin_obj(args[ARG_reset].u_obj); + self->dc = mp_hal_get_pin_obj(args[ARG_dc].u_obj); + + if (args[ARG_cs].u_obj != MP_OBJ_NULL) { + self->cs = mp_hal_get_pin_obj(args[ARG_cs].u_obj); + } + if (args[ARG_backlight].u_obj != MP_OBJ_NULL) { + self->backlight = mp_hal_get_pin_obj(args[ARG_backlight].u_obj); + } + + return MP_OBJ_FROM_PTR(self); +} + + +STATIC uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3); +} + + +STATIC mp_obj_t st7789_color565(mp_obj_t r, mp_obj_t g, mp_obj_t b) { + return MP_OBJ_NEW_SMALL_INT(color565( + (uint8_t)mp_obj_get_int(r), + (uint8_t)mp_obj_get_int(g), + (uint8_t)mp_obj_get_int(b) + )); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_3(st7789_color565_obj, st7789_color565); + +STATIC const mp_map_elem_t st7789_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_st7789) }, + { MP_ROM_QSTR(MP_QSTR_color565), (mp_obj_t)&st7789_color565_obj }, + { MP_ROM_QSTR(MP_QSTR_ST7789), (mp_obj_t)&st7789_ST7789_type }, + { MP_ROM_QSTR(MP_QSTR_BLACK), MP_ROM_INT(BLACK) }, + { MP_ROM_QSTR(MP_QSTR_BLUE), MP_ROM_INT(BLUE) }, + { MP_ROM_QSTR(MP_QSTR_RED), MP_ROM_INT(RED) }, + { MP_ROM_QSTR(MP_QSTR_GREEN), MP_ROM_INT(GREEN) }, + { MP_ROM_QSTR(MP_QSTR_CYAN), MP_ROM_INT(CYAN) }, + { MP_ROM_QSTR(MP_QSTR_MAGENTA), MP_ROM_INT(MAGENTA) }, + { MP_ROM_QSTR(MP_QSTR_YELLOW), MP_ROM_INT(YELLOW) }, + { MP_ROM_QSTR(MP_QSTR_WHITE), MP_ROM_INT(WHITE) }, +}; + +STATIC MP_DEFINE_CONST_DICT (mp_module_st7789_globals, st7789_module_globals_table ); + +const mp_obj_module_t mp_module_st7789 = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_st7789_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_st7789, mp_module_st7789, MODULE_ST7789_ENABLED); diff --git a/st7789/st7789.h b/st7789/st7789.h new file mode 100644 index 0000000..b36e39f --- /dev/null +++ b/st7789/st7789.h @@ -0,0 +1,74 @@ +#ifndef __ST7789_H__ +#define __ST7789_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define ST7789_TFTWIDTH_240 240 +#define ST7789_TFTHEIGHT_240 240 + +#define ST7789_240x240_XSTART 0 +#define ST7789_240x240_YSTART 0 + + +// color modes +#define COLOR_MODE_65K 0x50 +#define COLOR_MODE_262K 0x60 +#define COLOR_MODE_12BIT 0x03 +#define COLOR_MODE_16BIT 0x05 +#define COLOR_MODE_18BIT 0x06 +#define COLOR_MODE_16M 0x07 + +// commands +#define ST7789_NOP 0x00 +#define ST7789_SWRESET 0x01 +#define ST7789_RDDID 0x04 +#define ST7789_RDDST 0x09 + +#define ST7789_SLPIN 0x10 +#define ST7789_SLPOUT 0x11 +#define ST7789_PTLON 0x12 +#define ST7789_NORON 0x13 + +#define ST7789_INVOFF 0x20 +#define ST7789_INVON 0x21 +#define ST7789_DISPOFF 0x28 +#define ST7789_DISPON 0x29 +#define ST7789_CASET 0x2A +#define ST7789_RASET 0x2B +#define ST7789_RAMWR 0x2C +#define ST7789_RAMRD 0x2E + +#define ST7789_PTLAR 0x30 +#define ST7789_COLMOD 0x3A +#define ST7789_MADCTL 0x36 + +#define ST7789_MADCTL_MY 0x80 // Page Address Order +#define ST7789_MADCTL_MX 0x40 // Column Address Order +#define ST7789_MADCTL_MV 0x20 // Page/Column Order +#define ST7789_MADCTL_ML 0x10 // Line Address Order +#define ST7789_MADCTL_MH 0x04 // Display Data Latch Order +#define ST7789_MADCTL_RGB 0x00 +#define ST7789_MADCTL_BGR 0x08 + +#define ST7789_RDID1 0xDA +#define ST7789_RDID2 0xDB +#define ST7789_RDID3 0xDC +#define ST7789_RDID4 0xDD + +// Color definitions +#define BLACK 0x0000 +#define BLUE 0x001F +#define RED 0xF800 +#define GREEN 0x07E0 +#define CYAN 0x07FF +#define MAGENTA 0xF81F +#define YELLOW 0xFFE0 +#define WHITE 0xFFFF + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __ST7789_H__ */