A little while ago I bought a TFT display from Waveshare for use with a BBC microbit microcontroller. The display was based on the ST7735 driver. At the time I ported some typescript code provided by Waveshare to Micropython and managed to get it up and running in an okayish kind of way.
Since then I have made quite a bit of headway, using Micropython with the Nano ESP32 which gives me the memory to use the Micropython framebuffer module. I know there are display drivers out there written by professionals but you cant beat the fun of learning and having a go yourself.
I'm not looking for super fast animation but I do like flicker free, the framebuffer does a pretty decent job of this and includes the primitive lines and shapes and a small text object, on top of that I have created some custom fonts and also some methods for decoding and displaying full screen bitmaps.
I want to share some links and some code for anyone interested in playing around with this graphics demo and if requested I can also share some custom font routines and full screen image examples.
There is one small thing with the framebuffer that I don't understand and that is the colors dont display correctly so unless I find out why that is I have a method that adjusts the framebuffer colors to the correct values. For this purpose use the function rgb565().
Framebuffer documentation link
https://docs.micropython.org/en/latest/library/framebuf.html
Wiring diagram for a 1.8 inch 160 x 128 pixel spi tft display connected to a Nano ESP32
https://photos.app.goo.gl/aS86eKpmCsy1Gr5YA
ST7735 Datasheet
https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf
Copy these two code snippets to the ESP32 flash. The first snippet name "test.py" and the second snippet name "sc_ST7735.py", then run test.py to see the results.
test.py
from machine import SPI, Pin, PWM
import time
from sc_ST7735 import LCD
import gc
WHITE = const(0xFFFF)
BLACK = const(0x0000)
RED = const(0xF800)
BLUE = const(0x001F)
GREEN = const(0x07E0)
YELLOW = const(0xFFE0)
MAGENTA = const(0xF81F)
CYAN = const(0x07FF)
ORANGE = const(0xFB80)
LIGHTBLUE = const(0x033F)
PINK = const(0xF80A)
GRAY = const(0x9492)
bl = PWM(Pin(5))
cs = Pin(6,Pin.OUT)
dc = Pin(7,Pin.OUT)
rst = Pin(8,Pin.OUT)
spi=SPI(1, 32000000)
lcd = LCD(spi, cs, dc, bl, rst)
################# User Code ###################################
size=8
(xmax, ymax) = (160-size, 128-size)
(x, y) = (size, size)
(vx, vy) = (4, 4)
while True:
lcd.fill(lcd.rgb565(YELLOW))
lcd.ellipse(x, y, size, size, lcd.rgb565(RED), True)
x += vx
if x == xmax or x == size:
vx = -vx
y += vy
if y == ymax or y == size:
vy = -vy
lcd.write_data(lcd.buffer)
sc_ST7735.py
from time import sleep_ms
import framebuf
import gc
LCD_WIDTH = const(160)
LCD_HEIGHT = const(128)
class LCD(framebuf.FrameBuffer):
def __init__(self, spi, cs, dc, bl, rst):
self.spi = spi
self.cs = cs
self.dc = dc
self.bl = bl
self.rst = rst
self.height = LCD_HEIGHT
self.width = LCD_WIDTH
mode = framebuf.RGB565
gc.collect()
self.buffer = bytearray(self.height * self.width * 2)
super().__init__(self.buffer, self.width, self.height, mode)
self.init_lcd()
def init_lcd(self):
self.rst(1)
sleep_ms(1)
self.rst(0)
sleep_ms(1)
self.rst(1)
# ST7735R Frame Rate
self.write_reg(0xB1) #FRMCTR1
self.write_cmd(0x01)
self.write_cmd(0x2C)
self.write_cmd(0x2D)
self.write_reg(0xB2) #FRMCTR2
self.write_cmd(0x01)
self.write_cmd(0x2C)
self.write_cmd(0x2D)
self.write_reg(0xB3) #FRMCTR3
self.write_cmd(0x01)
self.write_cmd(0x2C)
self.write_cmd(0x2D)
self.write_cmd(0x01)
self.write_cmd(0x2C)
self.write_cmd(0x2D)
self.write_reg(0xB4) # INVCTR
self.write_cmd(0x07)
# ST7735 Power Sequence
self.write_reg(0xC0) #PWCTR1
self.write_cmd(0xA2)
self.write_cmd(0x02)
self.write_cmd(0x84)
self.write_reg(0xC1) #PWCTR2
self.write_cmd(0xC5)
self.write_reg(0xC2) #PWCTR3
self.write_cmd(0x0A)
self.write_cmd(0x00)
self.write_reg(0xC3) #PWCTR4
self.write_cmd(0x8A)
self.write_cmd(0x2A)
self.write_reg(0xC4) #PWCTR5
self.write_cmd(0x8A)
self.write_cmd(0xEE)
self.write_reg(0xC5) #VMCTR1
self.write_cmd(0x0E)
# ST7735 Gamma Sequence
self.write_reg(0xE0) #GMCTRP1
self.write_cmd(0x0F)
self.write_cmd(0x1A)
self.write_cmd(0x0F)
self.write_cmd(0x18)
self.write_cmd(0x2F)
self.write_cmd(0x28)
self.write_cmd(0x20)
self.write_cmd(0x22)
self.write_cmd(0x1F)
self.write_cmd(0x1B)
self.write_cmd(0x23)
self.write_cmd(0x37)
self.write_cmd(0x00)
self.write_cmd(0x07)
self.write_cmd(0x02)
self.write_cmd(0x10)
self.write_reg(0xE1) #GMCTRN1
self.write_cmd(0x0F)
self.write_cmd(0x1B)
self.write_cmd(0x0F)
self.write_cmd(0x17)
self.write_cmd(0x33)
self.write_cmd(0x2C)
self.write_cmd(0x29)
self.write_cmd(0x2E)
self.write_cmd(0x30)
self.write_cmd(0x30)
self.write_cmd(0x39)
self.write_cmd(0x3F)
self.write_cmd(0x00)
self.write_cmd(0x07)
self.write_cmd(0x03)
self.write_cmd(0x10)
self.write_reg(0xF0) # Enable test command
self.write_cmd(0x01)
self.write_reg(0xF6) # Disable ram power save mode
self.write_cmd(0x00)
self.write_reg(0x3A) #COLMOD (RGB 565)
self.write_cmd(0x05)
self.write_reg(0x36) #MADCTL
self.write_cmd(0xF7 & 0xA0) # RGB color filter panel (result = A0)
self.write_reg(0x11) #SLPOUT
sleep_ms(1)
self.write_reg(0x29) #DISPON
self.clear(0)
############### Start of user methods ###############
def clear(self, color=0xFFFF):
self.set_window(0, 0, LCD_WIDTH, LCD_HEIGHT)
_col = bytearray(3)
_col = color.to_bytes(2, "big")
pixel_data=(_col[:2] * 5120)
self.dc(1) # EN LCD data
self.cs(0) # EN LCD CS
for i in range(4):
self.spi.write(pixel_data)
self.cs(1) # remove LCD CS EN
pixel_data = bytearray(0)
def write_reg(self, register):
self.dc(0) # EN LCD command
self.cs(0) # EN LCD CS
self.spi.write(bytearray([register]))
self.cs(1) # remove LCD CS EN
def write_cmd(self, data):
self.dc(1) # EN LCD data
self.cs(0) # EN LCD CS
self.spi.write(bytearray([data]))
self.cs(1) # remove LCD CS
def write_data(self, data):
self.dc(1) # EN LCD data
self.cs(0) # EN LCD CS
self.spi.write(data)
self.cs(1) # remove LCD CS
def set_backlight(self, level=500):
self.bl.freq(5000)
self.bl.duty(level)
def set_window(self, Xstart, Ystart, Xend, Yend):
# set the X coordinates
self.write_reg(0x2A)
self.write_cmd(0x00)
self.write_cmd((Xstart & 0xFF) + 1)
self.write_cmd(0x00)
self.write_cmd(((Xend - 1) & 0xFF) + 1)
# set the Y coordinates
self.write_reg(0x2B)
self.write_cmd(0x00)
self.write_cmd((Ystart & 0xFF) + 2)
self.write_cmd(0x00)
self.write_cmd(((Yend - 1) & 0xFF) + 2)
self.write_reg(0x2C)
def draw_image(self,filename):
self.set_window(0, 0, LCD_WIDTH, LCD_HEIGHT)
with open(filename, 'rb') as f:
self.dc(1) # EN LCD data
self.cs(0) # EN LCD CS
pixel_data = f.read()
self.spi.write(pixel_data)
self.cs(1) # remove LCD CS EN
pixel_data = bytearray(0)
def rgb565(self,c):
b = c & 0x1f
g = c >> 5 & 0x3f
r = c >> 11 & 0x1f
new_color = b << 11 | r << 5 | g
return new_color
@Jeff_T - I hope you don't mind me fixing a broken link in the message. ;)
I'll have to see which displays I have. I have several, but don't think any use that chipset.
Hi Chris, you might be able to find a driver online. The ST7735 (160 x 128) and also the ILI9341 (320 x 240) are just two that I have the initialization code for and I enjoy creating my own driver functions. I prefer to use a display as a status indicator but I have had a ball bouncing around at 72 frames a second on the smaller display, of course that rate gets harder to achieve with a bigger display using the same methods.
Okay, these are the displays I have purchased over the last few years:
ROADOM Raspberry Pi Screen, 10.1'' Touchscreen Monitor (https://a.co/d/7ug0wSM)
ELEGOO UNO R3 2.8 Inches TFT Touch Screen with SD Card Socket (https://a.co/d/6ouNqZM)
UCTRONICS 0.96" OLED Display Module for Arduino, 128x64 Pixel SSD1306 I2C Serial Mini Screen (https://a.co/d/7TqEgHR)
MakerFocus I2C OLED Display Module 0.91 Inch I2C SSD1306 OLED Display Module (https://a.co/d/0n49z8r)
ELECROW 5 Inch Touchscreen for Raspberry Pi Screen 800x480 TFT LCD Screen Monitor (https://a.co/d/5x9qeyZ)
0.95 inch RGB OLED (A) 96×64 SPI Interface Horizontal Pinheader Display Panel Module (https://a.co/d/djlXPKu)
I also have two Waveshare 400x300, 4.2inch E-Ink display modules (https://www.waveshare.com/4.2inch-e-paper-module.htm)
This one would be ideal Chris, https://a.co/d/djlXPKu
This repository has a driver and the author is a superstar on the micropython forums
https://github.com/peterhinch/micropython-nano-gui/blob/master/drivers/ssd1331/ssd1331.py
The connections are practically identical to my original post and should only take a few minutes to get up and running.
If you can install micropython on one of your controllers I can help with the setup.
I would like to recommend and point you at a simple micropython tutorial I wrote a while ago on the Arduino forum, it explains a few basic principals. I'm not a technical writer but I think I get the point across and you might find it helpful.
EDIT I forgot a link
https://forum.arduino.cc/t/micropython-simple-gpio-control/1158187
This would be the connections between the Nano ESP32 and the display, 5 GPIO plus a ground and positive.
Nano Display
3.3v ------------------ VCC
GND ------------------ GND
NC ------------------ NC
SPI mosi GPIO38 ------- DIN
SPI clock GPIO48 ------- CLK
GPIO6 ---------------- CS
GPIO7 ----------------- D/C
GPIO8 ----------------- RES
The driver in the link has has two drivers for the ssd1331 display, the difference is bit depth one is 8 bit rgb and the other is 16 bit rgb and they are both orientated in the landscape position. 16 bit should be fine for us.
I don't have one of these displays so I have not tested anything but I feel sure this driver will be good.
I worked overtime today, so I didn't get home in time to do anything. I checked and it looks like I purchased that display on December 2, 2022. It appears I bought the display by itself, so now I have to find it. I never used it, but not sure where it ended up. Here's a good laugh, while I try to locate this display...
Because the seller listed it as a motherboard:
XYGStudy_01.jpg
It recommends buying this display WITH an AMD Ryzen 7 CPU and Corsair DDR5 memory:
XYGStudy_02.jpg
Quote from: Jeff_T on Apr 16, 2024, 04:08 PMThis would be the connections between the Nano ESP32 and the display, 5 GPIO plus a ground and positive.
I don't have one of these displays so I have not tested anything but I feel sure this driver will be good.
Okay, I will locate this display ASAP and post my results.
Hi Chris, I've seen them labelled motherboards before it's weird.
If you can't find the display and you decide to buy a display the following is cheaper and better IMHO. It's a 1.8 inch 160 x 128 pixels with ST7735 driver chip.
https://www.amazon.com/Display-Module-4-wires-interface-128x160/dp/B09HCSV72V/ref=sr_1_6?crid=I08JO8G9I9OY&dib=eyJ2IjoiMSJ9.2Nf0s4_Dx-QOeM-XNnsHZ1YtbkiFFI37BOQ47OKGpslZwgGdkbbBREEnduy-y_knMy6n8xduMXWOPeRA0i8J6gdF_50PGtaggPklVyD33Ovs4U3L_jRimR-AnMPaIu-QhOPLSwjAHhm78JE-OPfJ9SMU5-XFwQC7DaY6UfZmqXu1LPe4JVZyuqzyitGl3WClR_6ZCcCY2_uzo2Rr2HWwpKdCOmmZxnlH-XLiwBx4PpPPcmETDd50JEkkvUAFYdYI0-XpeUJ7B15IFa3z83zHUrxdg-KuqUPsnMtuCD6S_gk.czpyFoz4a9oOVrfA1lMouUc-Dwr-CPb-Dj8hiL0U0sc&dib_tag=se&keywords=st7735&qid=1713362438&s=electronics&sprefix=st7735%2Celectronics%2C89&sr=1-6
It also has the on board SD card which is something else to consider, using the SD alone is a useful exercise. I also have a driver for this display that I know works.
Okay, I ordered that display. It said 1 left, so let's hope I got it in time! I should have it by the 19th and then we'll see what's up. Until then, I will still look for the other display. I'm also creating a folder structure for these experiments on my NAS Drive Engineering directory. More to come...
The 19th that's great, I saw several on Amazon below $10 so if they let you down then at least there are alternatives.
The 1.8 inch display is is a pretty good size and gives good performance from a SPI driver.
I think the folder for keeping a record is an awesome idea, there are many resources and references on the internet but it is invaluable to have written notes of your own success and failures to look back on.
Talking of references and resources the official Micropython docs. are usually my first go to. Here is a link to one of those docs. and it has a reference to many of the hardware interfaces you might use at some time, UART,SPI,SD, Pins, Network, Neopixel and many more.
https://docs.micropython.org/en/latest/esp32/quickref.html
This is the best discussion forum with some really smart contributors
https://github.com/orgs/micropython/discussions
Quote from: Jeff_T on Apr 15, 2024, 03:05 PMThis one would be ideal Chris, https://a.co/d/djlXPKu
Guess what I found tonight?!? :o It was in a box of Arduino Accessories I got from Amazon, but haven't yet opened or used, including some multi-channel D/A and A/D boards.
This means tomorrow I can test some code, prior to getting the other displays.
Waveshare_095_01.jpg
Waveshare_095_02.jpg
That's great Chris, the connections are as in post #5.
I adjusted the driver to work with the Nano ESP32. The driver came with a simple "test file".
If we can get this to work then we can expand on the graphic capabilities, I feel more confident about the new display you just ordered but lets have a go with this one.
So there are two files, I'm going to post the code for both. The first is the driver and must be saved to the flash of the microcontroller and must have the name sc_1331.py.
sc_1331.py
import framebuf
import time
import gc
class SSD1331(framebuf.FrameBuffer):
def rgb(self,r, g, b):
return ((b & 0xf8) << 5) | ((g & 0x1c) << 11) | (r & 0xf8) | ((g & 0xe0) >> 5)
def __init__(self, spi, cs, dc, res, height=64, width=96, init_spi=False):
self.spi = spi
self.cs = cs
self.dc = dc # 1 = data 0 = cmd
self.height = height # Required by Writer class
self.width = width
self.spi_init = init_spi
mode = framebuf.RGB565
gc.collect()
self.buffer = bytearray(self.height * self.width * 2)
super().__init__(self.buffer, self.width, self.height, mode)
res(0) # Pulse the reset line
time.sleep_ms(1)
res(1)
time.sleep_ms(1)
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
self.write(b'\xae\xa0\x72\xa1\x00\xa2\x00\xa4\xa8\x3f\xad\x8e\xb0'\
b'\x0b\xb1\x31\xb3\xf0\x8a\x64\x8b\x78\x8c\x64\xbb\x3a\xbe\x3e\x87'\
b'\x06\x81\x91\x82\x50\x83\x7d\xaf', 0)
gc.collect()
self.show()
def write(self, buf, dc):
self.cs(1)
self.dc(dc)
self.cs(0)
self.spi.write(buf)
self.cs(1)
def show(self, cmd=b'\x15\x00\x5f\x75\x00\x3f'): # Pre-allocate
if self.spi_init: # A callback was passed
self.spi_init(spi) # Bus may be shared
self.write(cmd, 0)
self.write(self.buffer, 1)
The second file is the test file and can be named anything but must have the .py extension. The author called it test_colors.py. Copy the code into the IDE and press F5 to run, if all is well the display will fill with a red, green and blue bands with a gradient. If that does not happen then we will have to do some troubleshooting.
test_colors.py
import machine
from machine import Pin,SPI,PWM
from sc_1331 import SSD1331 as SSD
def setup():
cs = Pin(6,Pin.OUT,value=1)
dc = Pin(7,Pin.OUT)
rst = Pin(8,Pin.OUT)
spi = machine.SPI(1,10000000)
ssd = SSD(spi, cs, dc, rst)
return ssd
ssd = setup()
ssd.fill(0)
x = 0
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(round(255*y/96), 0, 0))
x += 20
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(0, round(255*y/96), 0))
x += 20
for y in range(96):
ssd.line(y, x, y, x+20, ssd.rgb(0, 0, round(255*y/96)))
ssd.show()
If you think it needs a little more explanation I can always put it down on a vid real quick.
P.S. I'll give a little insight to what these files do when we are up and running.
Quote from: Jeff_T on Apr 17, 2024, 02:40 PMI think the folder for keeping a record is an awesome idea, there are many resources and references on the internet but it is invaluable to have written notes of your own success and failures to look back on.
On my NAS drive is a mapped drive (S:) which is for Savage (Savage///Circuits). There is a directory called, "Projects" and within that directory are each project, sorted by date. Within each project directory are common folders, such as, "Images", "PCB Files", "Schematics" and "Source Code". Not every project uses the same directories, but you get the idea.
Source code is stored by version. On microcontrollers I use a major and minor version to track previous versions, such as V1.2. I save all the old versions.
On PC code, I use major, minor and build, such as V1.2.12. My parts cabinets are sorted quite orderly with labels containing the name of the part, form factor and number of pins.
Just as I posted my Office Evolution, I plan on posting some photos of my storage system for parts, though I am going to be moving soon, so things will change just a bit, eventually.
NAS-Path_01.jpg
The 'S' drive makes sense, I am modifying files and saving them with a prefix of 'sc' to distinguish them from the original. Most of it right now is just small test files so I'm not attaching version numbers.
So your moving to Tennessee? that would be cool. Nashville is booming its one of the fastest growing regions in the country. ;D
Quote from: Jeff_T on Apr 18, 2024, 09:40 AMSo your moving to Tennessee? that would be cool. Nashville is booming its one of the fastest growing regions in the country. ;D
Tennessee? My sister use to live in Nashville, but she didn't stick around. I'm not sure why. She said the same thing to me before she moved there, convincing me to move there. This is when I was travelling in my RV. But she didn't stay, so I ended up not going there to visit and check stuff out.
I'm in NY. I'm moving 30 minutes from here onto some farm land from my wife's farm. We're subdividing a one acre lot for us to live on. My new office will be two of the three extra bedrooms. She gets the other.
As for the code, as I save the examples you posted, I will save those as V1.0 so that as I make changes, I can track them.
Chris I have a video that gives a demo of converting a bmp to a binary file for use with microcontrollers and tft displays, the demo uses python but the resulting file could be used with any programming language that you wanted.
If you think it's useful I can post the bmp to bin sketch.
At the end of the video the image is displayed on a 1.8 inch display with a ST7735 driver chip, I will modify the code in my original post of this thread to fall in line with this demonstration video.