From fb42294e91828b8784a23081b7dcbe1974c94e69 Mon Sep 17 00:00:00 2001 From: Ivan Belokobylskiy Date: Thu, 11 Apr 2019 12:57:04 +0300 Subject: [PATCH] Initial commit --- .gitignore | 5 + LICENSE | 21 ++ README.md | 179 ++++++++++++++ VERSION | 1 + docs/ST7789.jpg | Bin 0 -> 41222 bytes st7789/micropython.mk | 6 + st7789/st7789.c | 559 ++++++++++++++++++++++++++++++++++++++++++ st7789/st7789.h | 74 ++++++ 8 files changed, 845 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 VERSION create mode 100644 docs/ST7789.jpg create mode 100644 st7789/micropython.mk create mode 100644 st7789/st7789.c create mode 100644 st7789/st7789.h 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 0000000000000000000000000000000000000000..9e5c32f63b92412d5d248c8914ff5d56c95c70a3 GIT binary patch literal 41222 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�`$+3Ylw`2l2Cq_Fg$2~p zN-c>9*>XbN42DuwP7YH&?kf$wZgbvA;4CPOZ5KKscHay>ry81_CmzcoVzf2B!2Wxk z3v)dcCiS;UNPAZP+5wlla6KIjRj*+`Rp%B2_>_$e^VeGJtCxVqVvxxYwqxl*Ttg=zwFQ( z6rjjrCjShtr*JNN>wl9Mw^Msf%Fn|v*DNV<1;6;;Qkj?1C0ioQB3>85uSKjP<;%{@ znesJs<|F1Jswu3C`e-#1wTIhk~%s^JAW7bEZ7%6c#%w=>6=Zd$n55Qzu zWb8_~oIZw*x>l$b7)tyPq7dyMRdg5!eWAHZR3c)7}YVd^(M^jUpTBtO#&nRxR(eHFgpd{-Y1C;!_NyACMdv zO=V6{h)i0(zrn_ef@k1RnfjiG^|z^m&PV5t&;j*S3Pn|1C{rc=+2}PL5$4MWU>VPK z@kM+KnM^=42fj63ZfJLRj2VVX1Z1j%4HRzAEmOT;ay6r_L*x`YqZkN&0{$j7{^|Oy z6CTY_P&a2mHc7t;PoGi6xA*0zIX&Nrz5m<~xHotArE^o$2)Libnv(Jd94 zI((ntFBO#f8dn=Yeft1TTnM0G;q#3TO<=DhkTv|YlW@sgTJ+aQJ?>TM_{!*vZn7-g z<>?K{!G!ee12FXg*n2}VG9g>Y5zo;}8TVLf3%C;&(|cT&W%9+<=zYJz=jR|5qx& zSLH-wY-5P71YaVEPjOKK=jlDP=c)nW6-YhGB$bs)9W;z++vdDa?764#0jPS$8*b%@ zRLlCTmDJ45%`HrRElhU9NG{)v#P-M`_jj5(h)~*5u}H_x1W8u6Nq8FUcQ>oSu%8_>w_Cxu4EU?m}AVH1JDR1sA+#v+GhS9Q7s<@HW~3#|JO@`wC~ z{NKh82l0X1-_})R&chO&c4fZo`3;+uY1qoqNeX0iB#d{Vk0eQ(&UFwP94er1qn}3e z?xdQ~mD#=EU_OZXd|@tWEAq`C7ylOaB^E7@!YZOjbC;we!(?rCZz*&nT5or0{QND_ zIaqxF(en6rsh!}sZ1{e$o2q+QI!{IjNBX=v!*N-*h637Hl4Yu*UL(+Rr=H!X2k7@S z$^aBNOBh=mY7Lc&44|MYS^d+I&i~&3ay^{6g{&?|3d0Z!B0{EL8@^2L#O*Vl-Zx2F z_y9ng7FK!Id%Q(tDH_0sB_$1&BH?v77WZrourWpqtWkwLQF<#X3rDa<=c+84@Jk zu}INFjv9r-)VC&~6N*~Hp67U(YA(yflw10epjkQ^RRZcu(qizuYi}1WF?)@Fw(T|* z`yO=er1pb!HV}CO*7RaBB)OcYnoRfhYpj0l6RYVlJzG_O3?-%&rUF%>Na2(fPF8eL z$Z8hEq_(@o<{o7($4ebKLMxN!X)ddhUzY zMGCbVb)52+1UyyXSMb|95;ja7VeB_-ujFuU@hiu=T36OwK&T zF12(z{Oe%wJmUUI>&T3QN&C`tv=MFQ5Exq!^&EEC@2JltGg`Kq$o6osoL(ozp_rp> zCxsNNTXmgE&!D=yY)TxYtfoHM`1)llPlZxxHr0hmB9<>*h{}s%&y=Tf>V7!QzomcY z{InOm5W`siC)_4Q?vV&l?E>vhIMi>O2`V)12$LMe>MlaY?1((%+3%_Tvue>KHP<)q zJPCxPWF$;2x}|EB-6t)2cII3~!<}Q^a{~?~%sedG&nY^$M|V7)a+z7xAAm!~9SaN? zCel&x_zMnAmhD6*rrR@%nJ(3VV+`jfWt6~_(XCPR-=`?)pM8xA>Z2**S!OP}Y&sRr zzf5?feka<>HJ`wN+`3{=dBnh-enl|h;Mnyi$R&b4T58kY3`ED2`Dz_r`daq~O|)wtKM@8OY-0x(=_+BBiVc@m+w2urCq+vdL7fz{MLK6aQ3e zn4r9coRewoM(SWDw<8xRRy8JOgj%CPyLcQ3%8XD}?|3Y^+S)4Wa&{P`S2A%Kld%

@pQF&*?W|Hop3d3JT`aQ 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 {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__ */