{"id":20429877,"url":"https://github.com/antirez/ST77xx-pure-MP","last_synced_at":"2025-05-08T17:32:30.587Z","repository":{"id":220688570,"uuid":"752316947","full_name":"antirez/ST77xx-pure-MP","owner":"antirez","description":"Pure MicroPython driver for ST77xx displays. Low memory requirements.","archived":false,"fork":false,"pushed_at":"2025-03-25T12:26:04.000Z","size":121,"stargazers_count":32,"open_issues_count":3,"forks_count":4,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-03-25T13:32:36.316Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antirez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-03T16:02:47.000Z","updated_at":"2025-03-25T12:26:08.000Z","dependencies_parsed_at":"2024-02-14T20:08:27.825Z","dependency_job_id":"c2db7690-f422-4a18-bbe0-dfab730c3168","html_url":"https://github.com/antirez/ST77xx-pure-MP","commit_stats":null,"previous_names":["antirez/st77xx-pure-mp"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2FST77xx-pure-MP","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2FST77xx-pure-MP/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2FST77xx-pure-MP/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antirez%2FST77xx-pure-MP/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antirez","download_url":"https://codeload.github.com/antirez/ST77xx-pure-MP/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253114713,"owners_count":21856552,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T08:01:13.174Z","updated_at":"2025-05-08T17:32:30.550Z","avatar_url":"https://github.com/antirez.png","language":"Python","readme":"This is a pure-MicroPython driver for the ST7789 / ST7735 / ILI9471 / ILI9472 display drivers, designed in order to use very little memory.\n\n## Motivations\n\nDisplay drivers are more easily implemented by allocating (and subclassing) a MicroPython framebuffer in order to use its drawing primitives. This way drawing happens in the device memory, without any need for I/O. Finally, to show the image on the screen, the framebuffer memory gets transfer to the display memory with a single long SPI write operation. This transfer is usually implemented in the .show() method of the driver.\n\nHowever, even with a relatively small 160x128 display, this way of doing things requires allocating a bytearray of `160*128*2` bytes (2 bytes per pixel in RGB565 mode), which is 40k of total memory: more than what is available to a fresh ESP8266 MicroPython install. Even with more advanced MCUs, and especially if larger displays are used, the percentage of the available memory wasted on the framebuffer would often be prohibitive.\n\nThe alternative to this approach is writing directly to the display memory, which is often slow, since initiating SPI transfers for little data (for instance in the case of a single pixel drawing) is costly, especially in MicroPython.\n\n**This driver's goal is to try to optimize direct memory access as much as possible** in order to have acceptable performances even if it's pure MicroPython code that uses SPI memory access to the display memory. However, when enough memory is available (for instance when using ESP32 S3 devices), this driver also allows to enable the framebuffer and draw much faster graphics, or even mix the two approaches.\n\nWhy don't implement the driver directly in C? Because MicroPython default installs are what most people have access to :) And a C driver requires rebuilding MicroPython, which is not a trivial process involving installing embedded IDEs, cross compiling and so forth.\n\n## Features\n\n* Minimal driver code to communicate with ST77xx. It was initially based on [this driver](https://github.com/devbis/st7789py_mpy). While now the common code is minimal, a big thank you to the original author: it was very useful to get started with a very simple codebase.\n* All the common graphical primitives, with very fast boxes, fill, hline, vline, and text. Other advanced shapes are also implemented trying to squeeze possible speedups: circles, triangles, and so forth.\n* **Very low** memory usage in terms of allocations performed.\n* Optional support for framebuffer graphics. Ability to mix between direct ST77xx memory writing and framebuffer updates.\n* Hopefully clean understandable code.\n\n## Demo\n\nThis demo shows what the driver can do if used with an ESP8266EX.\nPerformances with a modern ESP32 will be much better. Click on the image to see the YouTube video.\n\n[\u003cimg src=\"https://i.ytimg.com/vi/0iNZUMW-uXk/0.jpg\" width=\"50%\"\u003e](https://www.youtube.com/watch?v=0iNZUMW-uXk \"ST77XX driver demo\")\n\n\n*Click the above image to see the video*\n\n## Usage\n\nThis driver works with both ST7789 and ST7735 based displays. Other models are yet to be tested. The driver does not require the display to have any data output available (these devices are often MOSI-only).\n\nFirst of all, you need to create the display object, providing an\nSPI interface to communicate with the display (see below in this README what\npins you could use in your device).\n\nPlease note that phase/polarity sometimes must be set to '1', or the\ndisplay does not work, it depends on the actual display you got.\n\n    import st7789_base, st7789_ext\n    from machine import Pin\n\n    display = st7789_ext.ST7789(\n        SPI(1, baudrate=40000000, phase=0, polarity=0),\n        160, 128,\n        reset=machine.Pin(2, machine.Pin.OUT),\n        dc=machine.Pin(4, machine.Pin.OUT),\n        cs=machine.Pin(10, machine.Pin.OUT),\n        inversion = False,\n    )\n\n*Note: there are two imports to lower the compile-time memory requirements for MicroPython, you may also want to import only the base module if you just need basic primitives and consume less memory, in this case initialize with st7789_base instead of st7789_ext.*\n\nIf colors look inverted, set inversion to True.\n\nThen you need to initialize the display. See \"Rotating the display\"\nsection in this README. Here is just an example in case you want to\nuse a 128x160 display in portrait mode. If you want landscape make also\nsure that the initialization of the object above has the width, height\narguments inverted, 128, 160. To initialize:\n\n    display.init(landscape=True,mirror_y=True)\n\nThen you are likely to require a backlight, if you want to see\nwhat the display is displaying. This depends on the display technology\nused. Here is an example in case the backlight led pin is connected\nto pin 5 of our board:\n\n    backlight = Pin(5,Pin.OUT)\n    backlight.on()\n\nAt this point if everything went well, you can draw on the display.\nCheck `test.py` for an example and to verify your display is working.\nAfter editing `test.py` to put your SPI configuration, pins, display\nsize and so forth, you can run it with:\n\n    mpremote cp st7789*.py :\n    mpremote cp lenna.565 :        # Optional, for image demo.\n    mpremote run test.py\n\n## Graphic primitives\n\nThe following is the list of the graphic primitives available.\n\n    # Fast methods\n\n    def fill(self,color) # Fill entire screen\n    def pixel(self,x,y,color) # Draw pixel\n    def hline(self,x0,x1,y,color) # Draw fast horizontal line\n    def vline(self,y0,y1,x,color) # Draw fast vertical line\n    def rect(self,x,y,w,h,color,fill=False) # Draw full or empty rectangle\n    def text(self,x,y,txt,bgcolor,fgcolor)  # Draw text\n    def image(self,x,y,filename)  # Show image in 565 format\n\n    # Slower methods, they do what they say :)\n\n    def line(self, x0, y0, x1, y1, color)\n    def circle(self, x, y, radius, color, fill=False)\n    def triangle(self, x0, y0, x1, y1, x2, y2, color, fill=False)\n    def upscaled_text(self,x,y,txt,fgcolor,bgcolor=None,upscaling=2)\n\nEverywhere there is to provide a color, you need to create the\ncolor bytes with:\n\n    mycolor = display.color(255,0,255) # RBB\n\nThen use it like that:\n\n    display.rect(10,10,50,50,mycolor,fill=True)\n\n## Writing text\n\nThe main API to write text using an 8x8 font is the following one:\n\n    def text(self,x,y,txt,fgcolor,bgcolor)  # Draw text\n\nThis method is designed to be fast enough, so it use a small 8x8 frame\nbuffer inside the device. When using this method, it is mandatory\nto specify both the background and foreground color. This means that\nwhat is in the 8x8 area where each character will be rendered will\nbe replaced with the background color.\n\nThere is an alternative **slower API** that has two advanced features:\n\n* You can specify None as background color, if you want to leave the current graphics on the screen as text background.\n* It supports upscaling (default 2). So by default this API writes bigger 16x16 characters. If you use 3 they will be 24x24 and so forth. Upscaling of 1 is also supported, in acse you are interested just in preserving the background specifying None, but you want normal sized text of 8x8 pixels.\n\nThis is the method signature:\n\n    def upscaled_text(self,x,y,txt,fgcolor,bgcolor=None,upscaling=2)\n\nExamples:\n\n    # 8x8 text, bg preserved.\n    display.upscaled_text(10,10,\"Hey!\",mycolor,upscaling=1)\n\n    # 16x16 text, fg and bg colors specified.\n    display.upscaled_text(10,10,\"Big text\",mycolor,mybg)\n\n    # 32x32 text, background of target area preserved.\n    display.upscaled_text(30,30,str(temperature),mycolor,upscaling=4)\n\n## Drawing images\n\nThe library is able to display images in a very fast way, transferring\nconverted images from the filesystem inside the device directly to the\nvideo memory, without wasting more than 256 bytes of local buffers.\n\nIn order to do this, images must be converted from PNG to RGB565\nformat. There is a tool to do this, inside the directory `pngto565`.\nCompile it with `make`, then:\n\n    pngto565 file.png file.565\n\nThen transfer the file in the device with:\n\n    mpremote cp file.565 :\n\nAnd display it with:\n\n    display.image(10,10,\"file.565\")\n\nPlease note that in order to be fast, this method can't do bound checking\nso if you display an image at a location where the image will go outside\nthe limits of the display, the rendered image may look odd / corrupted.\n\n## Using the framebuffer\n\nBy default, the driver directly addresses the ST77xx on-chip video memory.\nHowever when more speed is needed (and enough memory is available), it\nis possible to optionally allocate and use a framebuffer:\n\n```\n... initialization of the display here ...\ndisplay.enable_framebuffer()\ndisplay.fb.text(\"Hello world\",10,10,display.fb_color(255,0,0))\ndisplay.show()\n```\n\nTo draw things on the framebuffer, just reference the MicroPython framebuffer\ninstance directly, using the `.fb` attribute as above. The driver offers\na pratical function to convert from r,g,b colors to the framebuffer 16 bit\ninteger RGB565 format. Make sure to call `fb_color` and not `color` as you\nwould do when using the directy memory write primitives.\n\nIt is possible to mix framebuffer and direct memory writing.\nFor instance after composing a complex image in the framebuffer,\nyou may want to call `.show()` to render it on the screen, but then if\nthere are small items to draw over the existing video content, you\ncan just call the native methods documented in the first part of this\nREADME.\n\nThis way you can also use the advaced functions that are not available\nin the framebuffer implementation, like text upscaling, drawing of\nimages from files in rgb565 format, and so forth.\n\n## Mono framebuffer\n\nIf you need fast graphics but don't need colors, it is possible to\nenable the framebuffer in monochromatic mode:\n\n```\ndisplay.enable_framebuffer()\n```\n\nYour only colors will be 0 and 1 (black and white) in this case, but\nyou will be using just `width*height/8` total bytes of memory, and updating\nthe display with the `.show()` method is fast even it this requires a\nconversion, thanks to the Viper native emitter of MicroPython.\n\n## Rotating the display view\n\nThe ST77xx chip is quite able to transparently rotate / mirror the access\nto the video memory, so that it is possible to select different rotations\nand mirroring of the image without having to transform the image at\nsoftware level.\n\nNormally these displays native orientation is portrait (vertical), so\nfor instance if I have a 128x160 display, by default it will show\nits content oriented as a tall rectangle.\n\nThe default behavior may be changed at initialization, by passing\nthe following parameters to the init() method:\n\n    mirror_x: True/False        Mirror pixels horizontally\n    mirror_y: True/False        Mirror pixels vertically\n    landsacpe: True/False       Select landscape mode.\n    ir_bgr: True/False          Display is not RGB but BGR.\n\nNote that if you select landscape, you should no longer initialize the\ndisplay as 128x160, but as 160x128, passing 160 ad width and 128 as\nheight of the display when creating the object.\n\nMirroring will be likely be needed as well, depending on how the display\nis rotated. Also if you see the colors are off, try selecting the\nBGR mode.\n\n## CS pin handling\n\nChanging the state of pins takes a non trivial amount of time. During the\ndevelopment of this driver it was experimentally observed that not\ncommuting the state of chip-select pin improves the performances by a\nmeasurable amount. At the same time, most users will hardly have other\ndevices connected to the same SPI line, so this driver after the initialization\nholds the CS pin off and leave it like that.\n\nIn case you really want to do multiplexing with some other device, once\nyou are no longer using the display you should call:\n\n    dispaly.cs.on()\n\nAnd only then use other devices connected to the same SPI interface.\n\n## Connecting the display to the ESP8266 / ESP32\n\nThe ESP8266 and cheaper/older ESP32 models are probably one of the main\ntargets for this library being a lot more slow and memory constrained than\nthe recent models.\n\nOften they can do hardware SPI only with a specific set of pins, so I\nsuggest you to connect the TFT display in this way:\n\nBacklight led -\u003e Pin 5 (D1 on some board)\nSCK/Clock -\u003e Pin 14    (D5 on some board)\nSDA/MOSI  -\u003e Pin 13    (D7 on some board)\nA0/DC     -\u003e Pin 4     (D2 on some board)\nReset     -\u003e Pin 2     (D4 on some board)\nCS        -\u003e Pin 10    (SD3 on some board)\nGND       -\u003e GND       (one of the many)\nVCC       -\u003e 3V3       (one of the many)\n\nPlease note that this corresponds to the SPI interface 1 (known as\nHSPI), because the SPI 0 is used in order to communicate with\nthe internal flash memory.\n\nMake sure to set 'polarity' in the SPI interface according to your display specification. Sometimes it is 1 sometimes 0.\n\n### Initialization parameters for different displays tested.\n\nPlease note that pins may vary depending on your connection with the MCU.\n\n**AliExpress 1.14\" IPS, 240x135, ver 1.1**\n\n```python\ndisplay = st7789_ext.ST7789(\n    SPI(0, baudrate=40000000, phase=1, polarity=1, sck=Pin(2), mosi=Pin(3), miso=Pin(4)),\n    240, 135,\n    reset=machine.Pin(7, machine.Pin.OUT),\n    dc=machine.Pin(6, machine.Pin.OUT),\n    cs=None,\n)\ndisplay.init(landscape=True,mirror_y=True,inversion=True,xstart=40,ystart=52)\n```\n\n**AliExpress 1.8\" TFT, 128x160, V1.1**\n\n```python\ndisplay = st7789_ext.ST7789(\n    SPI(1, baudrate=40000000, phase=0, polarity=0),\n    160, 128,\n    reset=machine.Pin(2, machine.Pin.OUT),\n    dc=machine.Pin(4, machine.Pin.OUT),\n    cs=machine.Pin(10, machine.Pin.OUT),\n)\n\ndisplay.init(landscape=True,mirror_y=True,inversion=False)\n```\n\n**Lilygo T-WATCH S3 240x240 display**\n\n```python\ndisplay = st7789_ext.ST7789(\n    SPI(1, baudrate=40000000, phase=0, polarity=1, sck=18, mosi=13, miso=37),\n    240, 240,\n    reset=False,\n    dc=machine.Pin(38, machine.Pin.OUT),\n    cs=machine.Pin(12, machine.Pin.OUT),\n)\ndisplay.init(landscape=False,mirror_y=True,mirror_x=True,inversion=True)\n```\n","funding_links":[],"categories":["Libraries","Python"],"sub_categories":["Display"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantirez%2FST77xx-pure-MP","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantirez%2FST77xx-pure-MP","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantirez%2FST77xx-pure-MP/lists"}