#aqee, 240310
#ref: https://github.com/rdagger/micropython-ili9341/blob/master/ili9341.py
# framebuf for full screen
# full color, 65K (16-bit)
# Pico (without wireless) firmware only, or screen size is too big to allocate mem for framebuf
# MultiThread:
# Can't provide flip() with Double-Buffer, not enough mem for one more buf.
# 40 fps with 62.5MHz SPI, or 16 fps with machine.freq() called
# mem used 161KB = 218 - 57
from machine import Pin,PWM,SPI
import framebuf
import machine
import rp2
from time import sleep,sleep_ms
class LCD_ILI9341_P8b(framebuf.FrameBuffer):
# Command constants from ILI9341 datasheet
NOP = const(0x00) # No-op
SWRESET = const(0x01) # Software reset
RDDID = const(0x04) # Read display ID info
RDDST = const(0x09) # Read display status
SLPIN = const(0x10) # Enter sleep mode
SLPOUT = const(0x11) # Exit sleep mode
PTLON = const(0x12) # Partial mode on
NORON = const(0x13) # Normal display mode on
RDMODE = const(0x0A) # Read display power mode
RDMADCTL = const(0x0B) # Read display MADCTL
RDPIXFMT = const(0x0C) # Read display pixel format
RDIMGFMT = const(0x0D) # Read display image format
RDSELFDIAG = const(0x0F) # Read display self-diagnostic
INVOFF = const(0x20) # Display inversion off
INVON = const(0x21) # Display inversion on
GAMMASET = const(0x26) # Gamma set
DISPLAY_OFF = const(0x28) # Display off
DISPLAY_ON = const(0x29) # Display on
SET_COLUMN = const(0x2A) # Column address set
SET_PAGE = const(0x2B) # Page address set
WRITE_RAM = const(0x2C) # Memory write
READ_RAM = const(0x2E) # Memory read
PTLAR = const(0x30) # Partial area
VSCRDEF = const(0x33) # Vertical scrolling definition
MADCTL = const(0x36) # Memory access control
VSCRSADD = const(0x37) # Vertical scrolling start address
PIXFMT = const(0x3A) # COLMOD: Pixel format set
WRITE_DISPLAY_BRIGHTNESS = const(0x51) # Brightness hardware dependent!
READ_DISPLAY_BRIGHTNESS = const(0x52)
WRITE_CTRL_DISPLAY = const(0x53)
READ_CTRL_DISPLAY = const(0x54)
WRITE_CABC = const(0x55) # Write Content Adaptive Brightness Control
READ_CABC = const(0x56) # Read Content Adaptive Brightness Control
WRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum Brightness
READ_CABC_MINIMUM = const(0x5F) # Read CABC Minimum Brightness
FRMCTR1 = const(0xB1) # Frame rate control (In normal mode/full colors)
FRMCTR2 = const(0xB2) # Frame rate control (In idle mode/8 colors)
FRMCTR3 = const(0xB3) # Frame rate control (In partial mode/full colors)
INVCTR = const(0xB4) # Display inversion control
DFUNCTR = const(0xB6) # Display function control
PWCTR1 = const(0xC0) # Power control 1
PWCTR2 = const(0xC1) # Power control 2
PWCTRA = const(0xCB) # Power control A
PWCTRB = const(0xCF) # Power control B
VMCTR1 = const(0xC5) # VCOM control 1
VMCTR2 = const(0xC7) # VCOM control 2
RDID1 = const(0xDA) # Read ID 1
RDID2 = const(0xDB) # Read ID 2
RDID3 = const(0xDC) # Read ID 3
RDID4 = const(0xDD) # Read ID 4
GMCTRP1 = const(0xE0) # Positive gamma correction
GMCTRN1 = const(0xE1) # Negative gamma correction
DTCA = const(0xE8) # Driver timing control A
DTCB = const(0xEA) # Driver timing control B
POSC = const(0xED) # Power on sequence control
ENABLE3G = const(0xF2) # Enable 3 gamma control
PUMPRC = const(0xF7) # Pump ratio control
ROTATE = {
0: 0x88,
90: 0xE8,
180: 0x48,
270: 0x28
}
#TODO, add INTON option for IPS screen
def __init__(self, basePin, CS, DC, RST, BL, invertColor=False, rotation=90):
self.width = 320
self.height = 240
self.buffer = bytearray(self.height * self.width * 2)
# self.buf= self.buffer[:]
super().__init__(self.buffer, self.width, self.height, framebuf.RGB565)
if rotation not in self.ROTATE.keys():
raise RuntimeError('Rotation must be 0, 90, 180 or 270.')
else:
self.rotation = self.ROTATE[rotation]
self.basePin=basePin
self.WR=Pin(8)
self.WR(1)
self.RD=Pin(10)
self.RD(1)
self.cs = Pin(CS,Pin.OUT)
self.rst = Pin(RST,Pin.OUT)
self.cs(1)
self.dc = Pin(DC,Pin.OUT)
self.dc(1)
self.initPIO(basePin)
# self.init_display(invertColor)
self.init_display_IPS()
# if(sys.implementation._machine.index('ESP')):
# self.BL= PWM(Pin(BL))
# else:
# self.BL= PWM(BL)
# self.BL.freq(1000)
# self.BL.duty_u16(0xFFFF)
self.WHITE = 0xFFFF
self.BLACK = 0x0000
self.GREEN = 0x001F
self.BLUE = 0xF800
self.RED = 0x07E0
self.fps=0;self.fps_counter=0
from machine import Timer
# import sys
# if(sys.implementation._machine.find('ESP')>=0):
# self.fpsTimer= Timer(2)
# self.fpsTimer.init(period=1000, mode=Timer.PERIODIC, callback=lambda t:self._fpsWriter())
# else:
# self.fpsTimer= Timer(freq=1, mode=Timer.PERIODIC, callback=lambda t:self._fpsWriter())
def initPIO(self, basePin):
@rp2.asm_pio(
# set_init=(rp2.PIO.OUT_LOW),
out_init=(rp2.PIO.OUT_LOW,)*8,
sideset_init=(rp2.PIO.OUT_HIGH),
# autopull=True,
# pull_thresh=8,
fifo_join = rp2.PIO.JOIN_TX,
)
def pio_send():
T=1
wrap_target()
pull()
out(pins, 8) .side(0) [T]
nop() .side(1) [T]
nop() .side(1) [T]
wrap()
# Instantiate a state machine with the blink program, at 2000Hz, with set bound to Pin(25) (LED on the Pico board)
self.sm = rp2.StateMachine(0, pio_send,
# freq=(machine.freq()>>16)+1,
freq=1_000000,
# set_base=Pin(0),
out_base=Pin(basePin),
sideset_base=Pin(basePin+8),
out_shiftdir=rp2.PIO.SHIFT_RIGHT,
)
self.sm.active(1)
def send_PIO(self, data):
for d in data:
print(hex(d), f"cs={self.cs.value()} ")
self.sm.put(d)
# while(self.sm.tx_fifo):
# pass
def _fpsWriter(self):
self.fps=self.fps_counter
self.fps_counter=0
print(self.fps)
def setBackLight(self, percent):
self.BL.duty_u16(percent*0xFFFF//100)
def write_cmd(self, cmd, *args):
# self.cs(1)
self.dc(0)
self.cs(0)
self.send_PIO(bytearray([cmd]))
self.cs(1)
# Handle any passed data
if len(args) > 0:
self.write_data(bytearray(args))
def write_data(self, data):
# self.cs(1)
self.dc(1)
self.cs(0)
self.send_PIO(data)
self.cs(1)
def reset(self):
self.rst(0)
sleep(.05)
self.rst(1)
sleep(.05)
def reset_IPS(self):
self.rst(1)
sleep_ms(1)
self.rst(0)
sleep_ms(10)
self.rst(1)
sleep_ms(120)
#for 3.2" IPS ,OK on both IPS and TFT, except IPS requires INVON cmd
def init_display_IPS(self):
"""Initialize dispaly"""
self.reset()
sleep_ms(120)
self.write_cmd(0xCF,0x00,0x89,0x30)
self.write_cmd(0xED,0x67,0x03,0x12,0x81)
self.write_cmd(0xE8,0x85,0x01,0x78)
self.write_cmd(0xCB,0x39,0x2C,0x00,0x34,0x02)
self.write_cmd(0xF7,0x20)
self.write_cmd(0xEA,0x00,0x00)
self.write_cmd(0xC0,0x25)
self.write_cmd(0xC1,0x10)
self.write_cmd(0xC5,0x55,0x50)
self.write_cmd(0xC7,0xB0)
self.write_cmd(0xB6) # Display Function Control,0x0A,0x82
self.write_cmd(0x36, self.rotation)#,0x48
self.write_cmd(0x3A,0x55)
self.write_cmd(0xF2,0x00)
self.write_cmd(0x26,0x01)
#Set Gamma
self.write_cmd(0xE0,0x0F,0x27,0x23,0x0B,0x0F,0x05,0x54,0x74,0x45,0x0A,0x17,0x0A,0x1C,0x0E,0x08)
self.write_cmd(0xE1,0x08,0x1A,0x1E,0x03,0x0F,0x05,0x2E,0x25,0x3B,0x01,0x06,0x05,0x25,0x33,0x0F)
self.write_cmd(0x21) # Invt On
self.write_cmd(0x11) # Sleep out
sleep_ms(120)
self.write_cmd(0x29) # Display on
#works on both TFT and IPS
def init_display(self, invertColor):
"""Initialize dispaly"""
self.reset()
# Send initialization commands
self.write_cmd(self.SWRESET) # Software reset
sleep(.1)
#test
self.write_cmd(self.RDSELFDIAG, 0x03, 0x80, 0x02)
self.write_cmd(self.PWCTRB, 0x00, 0xC1, 0x30) # Pwr ctrl B
self.write_cmd(self.POSC, 0x64, 0x03, 0x12, 0x81) # Pwr on seq. ctrl
self.write_cmd(self.DTCA, 0x85, 0x00, 0x78) # Driver timing ctrl A
self.write_cmd(self.PWCTRA, 0x39, 0x2C, 0x00, 0x34, 0x02) # Pwr ctrl A
self.write_cmd(self.PUMPRC, 0x20) # Pump ratio control
self.write_cmd(self.DTCB, 0x00, 0x00) # Driver timing ctrl B
self.write_cmd(self.PWCTR1, 0x23) # Pwr ctrl 1
self.write_cmd(self.PWCTR2, 0x10) # Pwr ctrl 2
self.write_cmd(self.VMCTR1, 0x3E, 0x28) # VCOM ctrl 1
self.write_cmd(self.VMCTR2, 0x86) # VCOM ctrl 2
self.write_cmd(self.MADCTL, self.rotation) # Memory access ctrl
# self.write_cmd(self.VSCRSADD, 0x00) # Vertical scrolling start address
self.write_cmd(self.PIXFMT, 0x55) # COLMOD: Pixel format
self.write_cmd(self.FRMCTR1, 0x00, 0x18) # Frame rate ctrl
self.write_cmd(self.DFUNCTR, 0x08, 0x82, 0x27)
self.write_cmd(self.ENABLE3G, 0x00) # Enable 3 gamma ctrl
self.write_cmd(self.GAMMASET, 0x01) # Gamma curve selected
self.write_cmd(self.GMCTRP1, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E,
0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00)
self.write_cmd(self.GMCTRN1, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31,
0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F)
#test for 3.2" IPS
if(invertColor):
self.write_cmd(0x21) # Invt On
self.write_cmd(self.SLPOUT) # Exit sleep
sleep(.1)
self.write_cmd(self.DISPLAY_ON) # Display on
sleep(.1)
# self.clear()
# #aqee
# def flip(self):
# self.buf[:] = self.buffer
def block(self, x0, y0, x1, y1):
"""Prepare for writing a block to display, ready but not yet transfer data.
Args:
x0 (int): Starting X position.
y0 (int): Starting Y position.
x1 (int): Ending X position.
y1 (int): Ending Y position.
"""
self.write_cmd(self.SET_COLUMN, x0 >> 8, x0 & 0xff, x1 >> 8, x1 & 0xff)
self.write_cmd(self.SET_PAGE, y0 >> 8, y0 & 0xff, y1 >> 8, y1 & 0xff)
self.write_cmd(self.WRITE_RAM)
# self.write_data(data)
self.dc(1)
self.cs(0)
def show(self):
self.block(0,0,self.width-1,self.height-1)
self.send_PIO(self.buffer)
self.fps_counter+=1
def showAreas(self, areas:list):
mv=memoryview(self.buffer)
W2=self.width<<1
for (x1,y1,x2,y2) in areas:
self.block(x1,y1,x2, y2)
w2=(x2-x1+1)<<1
for p in range(y1*W2+(x1<<1), W2*y2, W2):
self.send_PIO(mv[p:(p+w2)])
areas.clear()
self.fps_counter+=1
#get letter mod from font, which get from cache, or file if not cached yet.
@micropython.viper # 2.1ms, draw text in-place with font properties
def textFont(self, text, x0:int, y0:int, color:int, font, background:int, spacing:int):
x00=x0
fontStart=font.start_letter
fontBPL = font.bytes_per_letter
W=int(self.width)
dest=ptr16(self.buffer)
posDest_step = W
h:int=int(font.height)
for letter in text:
buf=ptr8(font.get_letter_mod(letter))
offset = 0 # int((ord(letter) - fontStart) * fontBPL)
w=int(buf[(offset)])
posDest0= (y0*W+ x0)
# log(f'xy={x0,y0} pDest={posDest}')
xMax=int(min(x0+w, W))
bottom=int(min(y0+h, self.height))-1
offset+=1
mask=1
if background>=0:
self.fill_rect(x0,y0,w+spacing,h,background)
for x in range(x0,xMax):
posDest=posDest0
# colorValue=(color<<bitDest)
for y in range(y0,bottom+1):
# log(x1,y1,pos, bin(mod[pos]),bin(mask),True if mod[pos]&mask else False)
if(buf[offset]&mask):
# self.pixel(x,y,color)
dest[posDest] = color
if (mask==0x80) or y == bottom:
offset+=1
mask=1
else:
mask<<=1
posDest+=posDest_step
posDest0+=1
x0 += (w + spacing)
return (x00, y0, x0-1, y0+h-1)
#draw font in dedicate viper function
def text(self, text, x0:int, y0:int, color:int, font=None, background:int=-1, spacing:int=1):
"""Draw text.
Args:
x (int): Starting X position
y (int): Starting Y position
text (string): Text to draw
font (XglcdFont object): Font
color (int): color value
background (int): background color (default: -1=transparent)
spacing (int): Pixels between letters (default: 1)
"""
if font:
return self.textFont(text, x0, y0, color, font, background, spacing)
else:
width=8*(int(len(text))+spacing)-spacing
if background>=0:
self.fill_rect(x0,y0,width,8,background)
super().text(text, x0,y0, color)
return (x0, y0, x0+width-1, y0+8)
if __name__=='__main__':
def test():
print(micropython.mem_info())
counter=0;fps=0
ms=0
while True:
from time import ticks_us
counter+=1
LCD.fill(LCD.RED)
LCD.text(str(counter), 10,10,LCD.WHITE)
LCD.text(str(fps), 10,20,LCD.WHITE)
LCD.text(str(ms), 0, 30, LCD.GREEN)
ms=ticks_us()
LCD.show()
ms=ticks_us()-ms
fps= 1000000/ms
print(f"{fps=}")
def testBackLight(LCD):
from time import sleep_ms
while True:
for i in range(100):
LCD.setBackLight(i)
sleep_ms(10)
for i in range(100, 0, -1):
LCD.setBackLight(i)
sleep_ms(10)
#aqee
# SPI max run at 62.5MHz, while 31.25MHz once called machine.freq() , why?
# ref:https://github.com/Bodmer/TFT_eSPI/discussions/2432
# machine.freq(133_000_000)
# machine.freq(260_000_000)
import micropython
print(micropython.mem_info())
#data pin0~7, WR pin8
LCD = LCD_ILI9341_P8b(0, 11, 12, 9, 22, False, 90)
# LCD = LCD_ILI9341_SPI(spi, CS, DC, RST)
# LCD = LCD_ILI9341_SPI(2, 1, 2, 3, 4, False, 90) # ESP32S3 temp
# LCD = LCD_ILI9341_SPI(0, 9, 8, 12, 13, True, 90) # #PicoHat
# print(LCD.spi)
# LCD.show()
print("testing...")
# test()
# testFont(LCD)
# testFonts(LCD)
# testBackLight(LCD)