Showing posts with label MicroPython. Show all posts
Showing posts with label MicroPython. Show all posts

Thursday, March 31, 2022

Raspberry Pi Pico/MicroPython generate QR Code and display on SSD1306 I2C OLED

Run on Raspberry Pi Pico/MicroPython, to generate QR Code, and display on SSD1306 128x64 I2C OLED.


I2C(0) is used to connect to SSD1306 I2C, scl=9 and sda=8.

For SSD1306 driver, visit https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py to download ssd1306.py, save to Raspberry Pi Pico driver.

For QR Code, JASchilz/uQR is used. Download uQR.py and save to Raspberry Pi Pico driver.


mpyPico_i2c.py, simple verify I2C(0) pins and connection to SSD1306 I2C OLED.
import uos
import usys

print("====================================================")
print(usys.implementation[0],
      str(usys.implementation[1][0]) + "." +
      str(usys.implementation[1][1]) + "." +
      str(usys.implementation[1][2]))
print(uos.uname()[3])
print("run on", uos.uname()[4])
print("====================================================")

i2c0 = machine.I2C(0)
print(i2c0)
print("Available i2c devices: "+ str(i2c0.scan()))

print("~ bye ~")

mpyPico_ssd1306.py, simple test program for SSD1306 I2C OLED.
"""
Run on Raspbery Pi Pico/MicroPython
display on ssd1306 I2C OLED

connec ssd1306 using I2C(0)
scl=9
sda=8

- Libs needed:

ssd1306 library
https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py
"""
import uos 
import usys
from ssd1306 import SSD1306_I2C

print("====================================================")
print(usys.implementation[0],
      str(usys.implementation[1][0]) + "." +
      str(usys.implementation[1][1]) + "." +
      str(usys.implementation[1][2]))
print(uos.uname()[3])
print("run on", uos.uname()[4])
print("====================================================")

i2c0 = machine.I2C(0)
print(i2c0)
print("Available i2c devices: "+ str(i2c0.scan()))

WIDTH = 128
HEIGHT = 64

oled = SSD1306_I2C(WIDTH, HEIGHT, i2c0)
oled.fill(0)

oled.text(usys.implementation[0], 0, 0)

strVersion = str(usys.implementation[1][0]) + "." + \
             str(usys.implementation[1][1]) + "." + \
             str(usys.implementation[1][2])
oled.text(strVersion, 0, 10)
oled.text(uos.uname()[3], 0, 20)
oled.text(uos.uname()[4], 0, 40)
oled.show()

mpyPico_ssd1306_uQR.py, generate QR Code using uQR.py, and display on SSD1306 I2C OLED.
"""
Run on Raspbery Pi Pico/MicroPython
to generate QR code using uQR,
and display on ssd1306 I2C OLED

- Libs needed:

ssd1306 library
https://github.com/micropython/micropython/blob/master/drivers/
display/ssd1306.py

JASchilz/uQR:
https://github.com/JASchilz/uQR

remark:
in the original example on uQR to display on ssd1306, scale of 2 is used.
It's found:
- If the data is too long, the small 128x64 OLED cannot display the whole matrix.
- In my test using my phone, scale of 1 is more easy to recognize.
Such that I use scale of 1 inside the loop to generate  qr code.
"""
from uos import uname
from usys import implementation
from machine import I2C
from time import sleep
from ssd1306 import SSD1306_I2C
from uQR import QRCode

print("====================================================")
print(implementation[0],
      str(implementation[1][0]) + "." +
      str(implementation[1][1]) + "." +
      str(implementation[1][2]))
print(uname()[3])
print("run on", uname()[4])
print("====================================================")

i2c0 = I2C(0)
print(i2c0)
print("Available i2c devices: "+ str(i2c0.scan()))

WIDTH = 128
HEIGHT = 64

oled = SSD1306_I2C(WIDTH, HEIGHT, i2c0)
oled.fill(0)

oled.text("RPi Pico", 0, 0)
oled.text("MicroPython", 0, 10)
oled.text("OLED(ssd1306)", 0, 20)
oled.text("uQR exercise", 0, 40)
oled.show()

sleep(5)
qr = QRCode()

qr.add_data("uQR example")
matrix = qr.get_matrix()
print("version:", qr.version)
print("len of matrix", len(matrix))

oled.fill(1)
for y in range(len(matrix)*2):                   # Scaling the bitmap by 2
    for x in range(len(matrix[0])*2):            # because my screen is tiny.
        value = not matrix[int(y/2)][int(x/2)]   # Inverting the values because
        oled.pixel(x, y, value)                  # black is `True` in the matrix.
oled.show()

while True:
    userinput = input("\nEnter something: ")
    if userinput == "":
        break
    print(userinput)
    qr.clear()
    qr.add_data(userinput)
    matrix = qr.get_matrix()
    print("version:", qr.version)
    print("len of matrix", len(matrix))
    
    oled.fill(1)
    scale = 1
    for y in range(len(matrix)*scale): 
        for x in range(len(matrix[0])*scale): 
            value = not matrix[int(y/scale)][int(x/scale)]
            oled.pixel(x, y, value)
    oled.show()
    
print("~ bye ~")

mpyPico_simpletest_uQR.py, generate QR Code and display on REPL.
"""
Run on Raspbery Pi Pico/MicroPython
to generate QR code using uQR,
and display on screen

- Libs needed:

JASchilz/uQR:
https://github.com/JASchilz/uQR

"""
from uos import uname
from usys import implementation
from usys import stdout

from uQR import QRCode

print("====================================================")
print(implementation[0],
      str(implementation[1][0]) + "." +
      str(implementation[1][1]) + "." +
      str(implementation[1][2]))
print(uname()[3])
print("run on", uname()[4])
print("====================================================")

# For drawing filled rectangles to the console:
stdout = stdout
WHITE = "\x1b[1;47m  \x1b[40m"
BLACK = "  "
NORMAL = '\033[1;37;0m'

def print_QR(uqr):

    qr_matrix = uqr.get_matrix()
    
    print("version:", uqr.version)
    qr_height = len(qr_matrix)
    qr_width = len(qr_matrix[0])
    print("qr_height:  ", qr_height)
    print("qr_width:   ", qr_width)

    for _ in range(4):
        for _ in range(qr_width + 8): #margin on top
            stdout.write(WHITE)
        print()
    for y in range(qr_height):
        stdout.write(WHITE * 4)       #margin on right
        for x in range(len(matrix[0])):
            value = qr_matrix[int(y)][int(x)]
            if value == True:
                stdout.write(BLACK)
            else:
                stdout.write(WHITE)
        stdout.write(WHITE * 4)        #margin on left
        print()
    for _ in range(4):
        for _ in range(qr_width + 8):  #margin on bottom
            stdout.write(WHITE)
        print()
    print(NORMAL)
        
qr = QRCode()

qr.add_data("uQR example")
matrix = qr.get_matrix()

print_QR(qr)

while True:
    userinput = input("\nEnter something: ")
    if userinput == "":
        break
    print(userinput)
    qr.clear()
    qr.add_data(userinput)
    matrix = qr.get_matrix()
    
    print_QR(qr)


print("~ bye ~")
More exercise for Raspberry Pi Pico

Wednesday, February 23, 2022

Raspberry Pi Pico/MicroPython: get MicroPython version programmatically

A simple exercise run on Raspberry Pi Pico/MicroPython to get installed MicroPython version programmatically.


mpyPico_info.py

import uos
import usys

class color:
    BLACK =   '\033[1;30;48m'
    RED =     '\033[1;31;48m'
    GREEN =   '\033[1;32;48m'
    YELLOW =  '\033[1;33;48m'
    BLUE =    '\033[1;34;48m'
    MAGENTA = '\033[1;35;48m'
    CYAN =    '\033[1;36;48m'
    END =    '\033[1;37;0m'

#print(uos.uname())
#print(usys.implementation)

print("====================================================")
print(color.BLUE, usys.implementation[0],
      str(usys.implementation[1][0]) + "." +
      str(usys.implementation[1][1]) + "." +
      str(usys.implementation[1][2]), color.END)
print(uos.uname()[3])
print("run on", color.RED, uos.uname()[4], color.END)
print("====================================================")





~ more exercises for Raspberry Pi Pico

Sunday, May 30, 2021

Raspberry Pi Pico/MicroPython: uasyncio

Raspberry Pi Pico MicroPython exercise of using uasyncio:

"""
MicroPython uasyncio exercise on RP2040
"""
from sys import implementation
from os import uname
import uasyncio as asyncio
from machine import Pin
import utime

led = Pin(25, Pin.OUT)

print(implementation.name)
print(uname()[3])
print(uname()[4])
print()
print(asyncio.__name__)
print(asyncio.__version__)

#Toggle onboard LED every 500ms
async def asyncTask1():
    while True:
        led.toggle()
        await asyncio.sleep_ms(500)

#print time every 1 second
async def asyncTask2():
    while True:
        print(utime.time())
        await asyncio.sleep_ms(1000)
        
loop = asyncio.get_event_loop()
loop.create_task(asyncTask1())
loop.create_task(asyncTask2())
loop.run_forever()

print("- bye -")




~ More exercise on Raspberry Pi Pico

Saturday, May 29, 2021

RPi Pico BOOTSEL button broken! How to enter BOOTLOADER mode using Python code; without BOOTSEL button and un-plug/re-plug USB.


The BOOTSEL button on my Raspberry Pi Pico broken! Here is how to reset Pico and enter BOOTLOADER mode using Python (MicroPython/CircuitPython) code; such that no need touch BOOTSEL button and un-plug/re-plug USB.
 

In case the Raspberry Pi Pico is flashed MicroPython:

import machine
machine.bootloader()

In case of CircuitPython:

import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset()


Thursday, May 6, 2021

Raspberry Pi Pico/MicroPython, read and set CPU Frequency (overclock)

On Raspberry Pi Pico/MicroPython, machine.freq() can be used to read/set CPU frequency, in hertz.

Tested on Raspberry Pi Pico flashed with MicroPython v1.15 on 2021-05-06.




Change CPU Frequency and monitor Temperature, at runtime, with graphical display on ili9341 SPI display.


With 320x240 ILI9341 SPI Display, using jeffmer/micropython-ili9341 library, and modified from former exercise read internal temperature sensor, it's a exercise to change CPU Frequency and monitor Temperature, at runtime.

Tested on Raspberry Pi Pico with MicroPython v1.15 on 2021-04-18.


mpyPico_ClockVsTemp_ili9341_20210509a.py
"""
Exercise on Raspberry Pi Pico/MicroPython
with 320x240 ILI9341 SPI Display

Pico plot internal temperature graphically
vs CPU Clock
"""
from ili934xnew import ILI9341, color565
from machine import Pin, SPI, Timer
from micropython import const
import os
import glcdfont
import tt24
import tt32
#import freesans20fixed
import utime

sensor_temp = machine.ADC(4) #internal temperature sensor
conversion_factor = 3.3/(65535)

SCR_WIDTH = const(320)
SCR_HEIGHT = const(240)
SCR_ROT = const(0)
#SCR_ROT = const(2)

dispW = 240
dispH = 320
marginX = const(20)
marginY = const(10)
TempFrame_W = dispW-(2*marginX)
TempFrame_H = 100
TempFrame_X0 = marginX
TempFrame_Y0 = marginY
TempFrame_Y1 = marginY+TempFrame_H

TempPanel_X0 = TempFrame_X0
TempPanel_Y0 = TempFrame_Y1 + 10
TempPanel_W = TempFrame_W
TempPanel_H = 32

sampleSize = TempFrame_W      #200
tempValueLim = TempFrame_H  #100

FreqFrame_W = dispW-(2*marginX)
FreqFrame_H = 100
FreqFrame_X0 = marginX
FreqFrame_Y0 = marginY + 150

FreqFrame_Y1 = marginY+FreqFrame_H + 150

FreqPanel_X0 = FreqFrame_X0
FreqPanel_Y0 = FreqFrame_Y1 + 10

led = Pin(25, Pin.OUT)

#set default CPU freq
machine.freq(125000000)
FreqChangeInterval = 15  #change freq in 15 sec interval
freqSet = [50000000, 250000000, 40000000, 260000000]
freqCnt = FreqChangeInterval
freqIdx = 0

print("MicroPython:")
print(os.uname()[3])
print(os.uname()[4])
print()

TFT_CLK_PIN = const(6)
TFT_MOSI_PIN = const(7)
TFT_MISO_PIN = const(4)

TFT_CS_PIN = const(13)
TFT_RST_PIN = const(14)
TFT_DC_PIN = const(15)

spi = SPI(
    0,
    baudrate=40000000,
    miso=Pin(TFT_MISO_PIN),
    mosi=Pin(TFT_MOSI_PIN),
    sck=Pin(TFT_CLK_PIN))
print(spi)

display = ILI9341(
    spi,
    cs=Pin(TFT_CS_PIN),
    dc=Pin(TFT_DC_PIN),
    rst=Pin(TFT_RST_PIN),
    w=SCR_WIDTH,
    h=SCR_HEIGHT,
    r=SCR_ROT)

def drawHLine(x, y, w, color):
    for i in range(w):
        display.pixel(x+i,y,color)
        
def drawVLine(x, y, h, color):
    for i in range(h):
        display.pixel(x,y+i,color)
        
def drawVLineUp(x, y, h, color):
    for i in range(h):
        display.pixel(x,y-i,color)
      
frameBGcolor = color565(150, 150, 150)
TempPanel = color565(0, 0, 0)

def drawFrame(x, y, w, h):
    display.fill_rectangle(x, y, w, h, frameBGcolor)

    l1x = x-1
    l2x = x+w
    l1y = y-1
    l2y = y+h+1
    lcolor = color565(250, 250, 250)
    
    for i in range(w+2):
        display.pixel(l1x+i,l1y,lcolor)
        display.pixel(l1x+i,l2y,lcolor)
 
    for i in range(h+2):
        display.pixel(l1x,l1y+i,lcolor)
        display.pixel(l2x,l1y+i,lcolor)
      
display.erase()

display.set_font(tt24)
display.set_pos(0, 0)
display.set_color(color565(250, 250, 0), color565(0, 0, 0))
display.print("Pico plot internal temperature vs CPU Clock")
display.print("")
display.set_color(color565(250, 250, 250), color565(0, 0, 0))
display.print("MicroPython:")
display.print(os.uname()[3])
display.print("")
display.print(os.uname()[4])
utime.sleep(3)

timReached = False
tim = Timer()
def TimerTick(timer):
    global led
    led.toggle()
    global timReached
    timReached = True
    
tim.init(freq=1, mode=Timer.PERIODIC, callback=TimerTick)
    
display.set_font(tt32)
#display.set_font(freesans20fixed)

display.set_color(color565(250, 250, 0), color565(0, 0,0))
display.erase()

drawFrame(TempFrame_X0, TempFrame_Y0, TempFrame_W, TempFrame_H)
drawFrame(FreqFrame_X0, FreqFrame_Y0, FreqFrame_W, FreqFrame_H)

utime.sleep(1)

#sampleTemp=[0]*sampleSize
sampleIndex=0
tempString = "0"
while True:
    if timReached:
        
        timReached = False
        
        reading = sensor_temp.read_u16()*conversion_factor
        tempValue = 27-(reading-0.706)/0.001721
        
        print(tempValue)
        
        tempString=str(tempValue)
        if tempValue >= tempValueLim:
            display.set_color(color565(250, 0, 0), color565(0, 0, 0))
        else:
            display.set_color(color565(0, 0, 250), color565(0, 0, 0))
        display.set_pos(TempPanel_X0, TempPanel_Y0)
        display.print(tempString)
        
        if sampleIndex == 0:
            #clear frame
            display.fill_rectangle(TempFrame_X0,
                                   TempFrame_Y0,
                                   TempFrame_W,
                                   TempFrame_H,
                                   frameBGcolor)
            
            display.fill_rectangle(FreqFrame_X0,
                                   FreqFrame_Y0,
                                   FreqFrame_W,
                                   FreqFrame_H,
                                   frameBGcolor)
        
        #plot temperature
        if tempValue >= tempValueLim:
            drawVLineUp(TempFrame_X0+sampleIndex,
                        TempFrame_Y1,
                        tempValueLim,
                        color565(250, 0, 0))
        else:
            drawVLineUp(TempFrame_X0+sampleIndex,
                        TempFrame_Y1,
                        tempValue,
                        color565(0, 0, 250))
            
        #plot CPU freq
        freqValue = machine.freq()
        print(freqValue)
        
        freqBar = freqValue/3000000  #freqBar in range 0~100
        drawVLineUp(FreqFrame_X0+sampleIndex,
                    FreqFrame_Y1,
                    freqBar,
                    color565(250, 250, 0))
        
        freqString=str(freqValue/1000000) + " (MHz)"
        display.set_color(color565(0, 0, 250), color565(0, 0, 0))
        display.set_pos(FreqPanel_X0, FreqPanel_Y0)
        display.print(freqString)
        
        sampleIndex = sampleIndex+1
        if(sampleIndex>=sampleSize):
            sampleIndex = 0
            
        
        
        #check if frequency changeinterval reached
        freqCnt = freqCnt-1
        if freqCnt == 0:
            freqCnt = FreqChangeInterval
            newFreq = freqSet[freqIdx]
            print(newFreq)
            machine.freq(newFreq)
            freqIdx = freqIdx + 1
            if freqIdx == len(freqSet):
                freqIdx = 0;
print("- bye-")

Thursday, April 8, 2021

ESP32/MicroPython server + Raspberry Pi/Python client, transmit image via WiFi TCP socket.

In this exercise, ESP32 (ESP32-DevKitC V4)/MicroPython play the role of AP, act as socket server. Raspberry Pi connect to ESP32 WiFi network, run Python code to load image, act as client and transmit the image to ESP32 server. The ESP32 server display the image on a 240*240 IPS (ST7789 SPI) LCD. It's is role reversed version of my previous exercise "Raspberry Pi/Python Server send image to ESP32/MicroPython Client via WiFi TCP socket".



protocol:

Client			|	    |	Server
(Raspberry Pi/Python)	|	    |	(ESP32/MicroPython)
			|	    |
			|	    |	Reset
			|           |	Setup AP
			|	    |	Setup socket server
(connect to ESP32 WiFi)	|	    |
			|	    |
connect to ESP32 server	|	    |	accepted
			|<-- ACK ---|	
send the 0th line	|---------->|	display the 0th line
			|<-- ACK ---|	send ACK
send the 1st line	|---------->|	display the 1st line
			|<-- ACK ---|	send ACK
			    .
			    .
			    .
send the 239th line	|---------->|	display the 239th line
			|<-- ACK ---|	send ACK
close socket		|           |	close socket
			|	    |
	
Server side:
(ESP32/MicroPython)

The ESP32 used is a ESP32-DevKitC V4, display is a 240*240 IPS (ST7789 SPI) LCD. Library setup and connection, refer to former post "ESP32 (ESP32-DevKitC V4)/MicroPython + 240*240 IPS (ST7789 SPI) using russhughes/st7789py_mpy lib".

upyESP32_ImgServer_AP_20210408a.py, MicroPython code run on ESP32. Save to ESP32 named main.py, such that it can run on power-up without host connected.
from os import uname
from sys import implementation
import machine
import network
import socket
import ubinascii
import utime
import st7789py as st7789
from fonts import vga1_16x32 as font
import ustruct as struct
"""
ST7789 Display  ESP32-DevKitC (SPI2)
SCL             GPIO18
SDA             GPIO23
                GPIO19  (miso not used)

ST7789_rst      GPIO5
ST7789_dc       GPIO4
"""
#ST7789 use SPI(2)

st7789_res = 5
st7789_dc  = 4
pin_st7789_res = machine.Pin(st7789_res, machine.Pin.OUT)
pin_st7789_dc = machine.Pin(st7789_dc, machine.Pin.OUT)

disp_width = 240
disp_height = 240

ssid = "ssid"
AP_ssid  = "ESP32"
password = "password"

serverIP = '192.168.1.30'
serverPort = 80

print(implementation.name)
print(uname()[3])
print(uname()[4])
print()

#spi2 = machine.SPI(2, baudrate=40000000, polarity=1)
pin_spi2_sck = machine.Pin(18, machine.Pin.OUT)
pin_spi2_mosi = machine.Pin(23, machine.Pin.OUT)
pin_spi2_miso = machine.Pin(19, machine.Pin.IN)
spi2 = machine.SPI(2, sck=pin_spi2_sck, mosi=pin_spi2_mosi, miso=pin_spi2_miso,
                   baudrate=40000000, polarity=1)
print(spi2)
display = st7789.ST7789(spi2, disp_width, disp_width,
                          reset=pin_st7789_res,
                          dc=pin_st7789_dc,
                          xstart=0, ystart=0, rotation=0)
display.fill(st7789.BLACK)

mac = ubinascii.hexlify(network.WLAN().config('mac'),':').decode()
print("MAC: " + mac)
print()

#init ESP32 as STA
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
utime.sleep(1)

def do_connect():
    global wlan
    print('connect to network...')
    display.fill(st7789.BLACK)
    display.text(font, "connect...", 10, 10)
    
    wlan.active(True)
    if not wlan.isconnected():
        print('...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    
    print()
    print('network config:')
    print("interface's IP/netmask/gw/DNS addresses")
    #print(wlan.ifconfig())
    myIP = wlan.ifconfig()[0]
    print(myIP)
    
    display.fill(st7789.BLACK)
    display.text(font, myIP, 10, 10)
    
def do_setupAP():
    global wlan
    print('setup AP...')
    display.fill(st7789.BLACK)
    display.text(font, "setup AP...", 10, 10)
    utime.sleep(1)
        
    ap = network.WLAN(network.AP_IF)
    ap.active(True)
    ap.config(essid=AP_ssid, password=password)

    while ap.active() == False:
      pass
    
    print(ap.active())
    print()
    print('network config:')
    myIP = ap.ifconfig()
    print(myIP)
    
    display.fill(st7789.BLACK)
    display.text(font, myIP[0], 10, 10)

def do_setupServer():
    global wlan
    global display
    
    addr = socket.getaddrinfo('0.0.0.0', serverPort)[0][-1]
    s = socket.socket()
    s.bind(addr)
    s.listen(1)
    
    print('listening on', addr)
    display.text(font, ':'+str(serverPort), 10, 50)
    
    while True:
        print('waiting connection...')
        cl, addr = s.accept()
        cl.settimeout(5)
        print('client connected from:', addr)
        
        display.fill(st7789.BLACK)
        display.text(font, addr[0], 10, 10)
    
        cl.sendall('ACK')
        print('Firt ACK sent')
        
        display.set_window(0, 0, disp_width-1, disp_height-1)
        pin_st7789_dc.on()
        for j in range(disp_height):
            
            try:
                buff = cl.recv(disp_width*3)
                #print('recv ok: ' + str(j))
            except:
                print('except: ' + str(j))
            
            for i in range(disp_width):
                offset= i*3
                spi2.write(struct.pack(st7789._ENCODE_PIXEL,
                                       (buff[offset] & 0xf8) << 8 |
                                       (buff[offset+1] & 0xfc) << 3 |
                                       buff[offset+2] >> 3))
            #print('send ACK : ' + str(j))
            cl.sendall(bytes("ACK","utf-8"))
            #print('ACK -> : ' + str(j))
        utime.sleep(1)
        cl.close()
    print('socket closed')
    
#do_connect()
do_setupAP()

try:
    do_setupServer()
except:
    print('error')
    display.text(font, "Error", 10, 200)
finally:
    print('wlan.disconnect()')
    wlan.disconnect()
    
print('\n- bye -')
Client Side:
(Raspberry Pi/Python)

Connect Raspberry Pi to ESP32 WiFi network before run the Python code.

Load and send images with fixed resolution 240x240 (match with the display in client side) to server. My former post "min. version of RPi/Python Code to control Camera Module with preview on local HDMI" is prepared for this purpose to capture using Raspberry Pi Camera Module .

pyqTCP_ImgClient_20210408a.py
import sys
from pkg_resources import require
import time
import matplotlib.image as mpimg
import socket

#HOST = '192.168.1.34'   # The server's hostname or IP address
HOST = '192.168.4.1'
PORT = 80               # The port used by the server

from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel,
                             QFileDialog, QHBoxLayout, QVBoxLayout)
from PyQt5.QtGui import QPixmap, QImage

print(sys.version)

class AppWindow(QWidget):

    camPreviewState = False  #not in Preview
    fileToUpload = ""

    def __init__(self):
        super().__init__()

        lbSysInfo = QLabel('Python:\n' + sys.version)
        vboxInfo = QVBoxLayout()
        vboxInfo.addWidget(lbSysInfo)

        #setup UI

        btnOpenFile = QPushButton("Open File", self)
        btnOpenFile.clicked.connect(self.evBtnOpenFileClicked)
        self.btnUpload = QPushButton("Upload", self)
        self.btnUpload.clicked.connect(self.evBtnUploadClicked)
        self.btnUpload.setEnabled(False)

        vboxCamControl = QVBoxLayout()
        vboxCamControl.addWidget(btnOpenFile)
        vboxCamControl.addWidget(self.btnUpload)
        vboxCamControl.addStretch()

        self.lbImg = QLabel(self)
        self.lbImg.resize(240, 240)
        self.lbImg.setStyleSheet("border: 1px solid black;")

        hboxCam = QHBoxLayout()
        hboxCam.addWidget(self.lbImg)
        hboxCam.addLayout(vboxCamControl)

        self.lbPath = QLabel(self)

        vboxMain = QVBoxLayout()
        vboxMain.addLayout(vboxInfo)
        vboxMain.addLayout(hboxCam)
        vboxMain.addWidget(self.lbPath)
        vboxMain.addStretch()
        self.setLayout(vboxMain)

        self.setGeometry(100, 100, 500,400)
        self.show()

    #wait client response in 3 byte len
    def wait_RESP(self, sock):
        #sock.settimeout(10)
        res = str()
        data = sock.recv(4)
        return data.decode("utf-8")

    def sendImageToServer(self, imgFile):
        print(imgFile)
        imgArray = mpimg.imread(imgFile)

        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((HOST, PORT))
            print(self.wait_RESP(s))

            for j in range(240):
                time.sleep(0.1)
                b = bytes(imgArray[j])

                #print('send img : ' + str(j))
                s.sendall(bytes(b))
                #print('img sent : ' + str(j))

                ans = self.wait_RESP(s)
                #print(ans + " : " + str(j))

            print('image sent finished')
            s.close()

    def evBtnUploadClicked(self):
        print("evBtnUploadClicked()")
        print(self.fileToUpload)
        self.sendImageToServer(self.fileToUpload)

    def evBtnOpenFileClicked(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        targetPath, _ = QFileDialog.getOpenFileName(
            self, 'Open file', '/home/pi/Desktop',
            'Image files (*.jpg)', options=options)

        if targetPath:
            print(targetPath)
            self.lbPath.setText(targetPath)

            with open(targetPath):
                pixmap = QPixmap(targetPath)

                #accept 240x240 image only
                if pixmap.width()==240 and pixmap.height()==240:
                    self.lbImg.setPixmap(pixmap)
                    self.btnUpload.setEnabled(True)
                    self.fileToUpload = targetPath
                else:
                    self.btnUpload.setEnabled(False)


    def evBtnOpenFileClickedX(self):

        targetPath="/home/pi/Desktop/image.jpg"

        print(targetPath)
        self.lbPath.setText(targetPath)

        try:
            with open(targetPath):
                pixmap = QPixmap(targetPath)
                self.lbImg.setPixmap(pixmap)

                #as a exercise, get some info from pixmap
                print('\npixmap:')
                print(pixmap)
                print(type(pixmap))
                print(str(pixmap.width()) + " : " + str(pixmap.height()))
                print()

                print('convert to Image')
                qim = pixmap.toImage()
                print(qim)
                print(type(qim))
                print()

                print('read a pixel from image')
                qrgb = qim.pixel(0, 0)
                print(hex(qrgb))
                print(type(qrgb))

                r, g, b = qRed(qrgb), qGreen(qrgb), qBlue(qrgb)
                print([hex(r), hex(g), hex(b)])
                print()

        except FileNotFoundError:
            print('File Not Found Error')

if __name__ == '__main__':
    print('run __main__')
    app = QApplication(sys.argv)
    window = AppWindow()
    sys.exit(app.exec_())

print("- bye -")
Remark:
At beginning, I tried to set ESP32 as STA, by calling do_connect(), connect to my home/mobile WiFi network. But the result is very unstable in: Pi client send 240 pixel but ESP32 server can't receive all. That's why you can find some commented debug code (using print()) in the program.

After then, set ESP32 as AP, Pi connect to ESP32 WiFi network. The result have great improved and very stable.




Wednesday, March 24, 2021

Raspberry Pi/Python Server send image to ESP32/MicroPython Client via WiFi TCP socket


In this exercise, Raspberry Pi/Python3 act as socket server, ESP32/MicroPython act as client connect to server via WiFi TCP. Once received, server (Pi/Python) send a image (240x240) to client (ESP32/MicroPython), then the client display the image on a 240*240 IPS (ST7789 SPI) LCD.


To make it more flexible, the image is in 240 batch of 240 pixel x 3 color (r, g, b).

protocol:

Server			|	    |	Client
(Raspberry Pi/Python)	|	    |	(ESP32/MicroPython)
			|	    |
Start			|	    |	Reset
			|	    |
Setup as 		|	    |
socketserver.TCPServer	|	    |
			|	    |
			|	    |	Join the WiFi network
		        |	    |	Connect to server with socket
			|	    |	
			|<-- ACK ---|	send ACK
send the 0th line	|---------->|	display the 0th line
			|<-- ACK ---|	send ACK
send the 1st line	|---------->|	display the 1st line
			    .
			    .
			    .
send the 239th line	|---------->|	display the 239th line
			|<-- ACK ---|	send ACK
close socket		|	    |	close socket
			|	    |
wait next		|	    |	bye	


Client side:

(ESP32/MicroPython)

The ESP32 used is a ESP32-DevKitC V4, display is a 240*240 IPS (ST7789 SPI) LCD. Library setup and connection, refer to former post "ESP32 (ESP32-DevKitC V4)/MicroPython + 240*240 IPS (ST7789 SPI) using russhughes/st7789py_mpy lib".

upyESP32_ImgClient_20210324c.py, MicroPython code run on ESP32. Modify ssid/password and serverIP for your WiFi network.
from os import uname
from sys import implementation
import machine
import network
import socket
import ubinascii
import utime
import st7789py as st7789
from fonts import vga1_16x32 as font
import ustruct as struct
"""
ST7789 Display  ESP32-DevKitC (SPI2)
SCL             GPIO18
SDA             GPIO23
                GPIO19  (miso not used)

ST7789_rst      GPIO5
ST7789_dc       GPIO4
"""
#ST7789 use SPI(2)

st7789_res = 5
st7789_dc  = 4
pin_st7789_res = machine.Pin(st7789_res, machine.Pin.OUT)
pin_st7789_dc = machine.Pin(st7789_dc, machine.Pin.OUT)

disp_width = 240
disp_height = 240

ssid = "your ssid"
password = "your password"

serverIP = '192.168.1.30'
serverPort = 9999

print(implementation.name)
print(uname()[3])
print(uname()[4])
print()

spi2 = machine.SPI(2, baudrate=40000000, polarity=1)
print(spi2)
display = st7789.ST7789(spi2, disp_width, disp_width,
                          reset=pin_st7789_res,
                          dc=pin_st7789_dc,
                          xstart=0, ystart=0, rotation=0)
display.fill(st7789.BLACK)

mac = ubinascii.hexlify(network.WLAN().config('mac'),':').decode()
print("MAC: " + mac)
print()

#init ESP32 as STA
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.disconnect()
utime.sleep(1)

def do_connect():
    global wlan
    print('connect to network...')
    display.fill(st7789.BLACK)
    display.text(font, "connect...", 10, 10)
    
    wlan.active(True)
    if not wlan.isconnected():
        print('...')
        wlan.connect(ssid, password)
        while not wlan.isconnected():
            pass
    
    print()
    print('network config:')
    print("interface's IP/netmask/gw/DNS addresses")
    print(wlan.ifconfig())
    
    display.fill(st7789.BLACK)
    display.text(font, "connected", 10, 10)
    
def do_scan():
    global wlan
    print('scan network...')
    wlan.active(True)
    for network in wlan.scan():
        print(network)
        
def do_connectServer():
    global wlan
    global display
    
    addr = socket.getaddrinfo(serverIP, serverPort)[0][-1]
    print(addr)
    s = socket.socket()
    s.connect(addr)
    
    print('---')
    display.fill(st7789.BLACK)
    display.text(font, "waiting...", 10, 10)

    print('Send ACK')
    s.sendall(bytes("ACK","utf-8"))
        
    display.set_window(0, 0, disp_width-1, disp_height-1)
    pin_st7789_dc.on()
    for j in range(disp_height):
        
        buff = s.recv(disp_width*3)
        for i in range(disp_width):
            offset= i*3
            spi2.write(struct.pack(st7789._ENCODE_PIXEL,
                                   (buff[offset] & 0xf8) << 8 |
                                   (buff[offset+1] & 0xfc) << 3 |
                                   buff[offset+2] >> 3))
            
        s.sendall(bytes("ACK","utf-8"))

    s.close()
    print('socket closed')
    
do_connect()
try:
    do_connectServer()
except:
    print('error')
    display.text(font, "Error", 10, 200)
finally:
    print('wlan.disconnect()')
    wlan.disconnect()
    
print('\n- bye -')

Server Side:
(Raspberry Pi/Python)

The server will send Desktop/image.jpg with fixed resolution 240x240 (match with the display in client side). My former post "min. version of RPi/Python Code to control Camera Module with preview on local HDMI" is prepared for this purpose to capture using Raspberry Pi Camera Module .

pyMyTCP_ImgServer_20210324c.py, Python3 code run on Raspberry Pi.
import socketserver
import platform
import matplotlib.image as mpimg

imageFile = '/home/pi/Desktop/image.jpg'

print("sys info:")
for info in platform.uname():
    print(info)

class MyTCPHandler(socketserver.BaseRequestHandler):

    #wait client response in 3 byte len
    def wait_RESPONSE(self, client):
        client.settimeout(10)
        res = str()
        data = client.recv(4)
        return data.decode("utf-8")

    def handle(self):
        msocket = self.request

        print("{} connected:".format(self.client_address[0]))
        imgArray = mpimg.imread(imageFile)

        self.wait_RESPONSE(msocket)     #dummy assume 'ACK' received
        print('first RESPONSE received')

        for j in range(240):
            b = bytes(imgArray[j])
            msocket.sendall(bytes(b))
            self.wait_RESPONSE(msocket)  #dummy assume 'ACK' received

        print('image sent finished')

        msocket.close()


if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    # Create the server, binding to localhost on port 9999
    #with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
    with socketserver.TCPServer(('', PORT), MyTCPHandler) as server:
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C

        server.serve_forever()
This socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server example is modify from Python 3 socketserver.TCPServer Example. I assume socketserver.TCPServer will handle Ctrl-C with port close. But in my test, SOMETIMES throw OSError of "Address already in use". In my practice, try pressing Ctrl-C in REPL/restart repeatedly.


Next:

Saturday, March 20, 2021

ESP32 (ESP32-DevKitC V4)/MicroPython + 240*240 IPS (ST7789 SPI) using russhughes/st7789py_mpy lib

To display with 240*240 IPS (ST7789 SPI) LCD on ESP32 (ESP32-DevKitC V4)/MicroPython using russhughes/st7789py_mpy lib.

Connection:

In my exercise, SPI2 is used to send command to ST7789.

ST7789		ESP32-DevKitC
GND		GND
VCC		3V3
SCL		GPIO18
SDA		GPIO23
RES		GPIO5
DC		GPIO4
BLK		3V3


Install library:

Visit https://github.com/russhughes/st7789py_mpy, download lib/st7789py.py, save to ESP32.

In my test, have to edit st7789py.py littlle bit, otherwise it will be reported with error:
AttributeError: 'ST7789' object has no attribute 'xstart'

Edit st7789py.py, to add the line under  def __init__():
	self.xstart = xstart
	self.ystart = ystart

Download fonts/vga1_16x32.py (or any font files you want) to ESP32 under new directory "fonts".

Example code:

upyESP32_st7789.py
"""
ESP32-DevKitC V4/MicroPython exercise
240x240 ST7789 SPI LCD
using MicroPython library:
https://github.com/russhughes/st7789py_mpy

"""

import uos
import machine
import st7789py as st7789
from fonts import vga1_16x32 as font
import random
import ustruct as struct
import utime

"""
ST7789 Display  ESP32-DevKitC (SPI2)
SCL             GPIO18
SDA             GPIO23
                GPIO19  (miso not used)

ST7789_rst      GPIO5
ST7789_dc       GPIO4
"""
#ST7789 use SPI(2)

st7789_res = 5
st7789_dc  = 4
pin_st7789_res = machine.Pin(st7789_res, machine.Pin.OUT)
pin_st7789_dc = machine.Pin(st7789_dc, machine.Pin.OUT)

disp_width = 240
disp_height = 240
CENTER_Y = int(disp_width/2)
CENTER_X = int(disp_height/2)

print(uos.uname())
spi2 = machine.SPI(2, baudrate=40000000, polarity=1)
print(spi2)
display = st7789.ST7789(spi2, disp_width, disp_width,
                          reset=pin_st7789_res,
                          dc=pin_st7789_dc,
                          xstart=0, ystart=0, rotation=0)

display.fill(st7789.BLACK)
display.text(font, "Hello!", 10, 10)
display.text(font, "ESP32", 10, 40)
display.text(font, "MicroPython", 10, 70)
display.text(font, "ST7789 SPI", 10, 100)
display.text(font, "240*240 IPS", 10, 130)

for i in range(1000):
    display.pixel(random.randint(0, disp_width),
          random.randint(0, disp_height),
          st7789.color565(random.getrandbits(8),random.getrandbits(8),random.getrandbits(8)))

# Helper function to draw a circle from a given position with a given radius
# This is an implementation of the midpoint circle algorithm,
# see https://en.wikipedia.org/wiki/Midpoint_circle_algorithm#C_example 
# for details
def draw_circle(xpos0, ypos0, rad, col=st7789.color565(255, 255, 255)):
    x = rad - 1
    y = 0
    dx = 1
    dy = 1
    err = dx - (rad << 1)
    while x >= y:
        display.pixel(xpos0 + x, ypos0 + y, col)
        display.pixel(xpos0 + y, ypos0 + x, col)
        display.pixel(xpos0 - y, ypos0 + x, col)
        display.pixel(xpos0 - x, ypos0 + y, col)
        display.pixel(xpos0 - x, ypos0 - y, col)
        display.pixel(xpos0 - y, ypos0 - x, col)
        display.pixel(xpos0 + y, ypos0 - x, col)
        display.pixel(xpos0 + x, ypos0 - y, col)
        if err <= 0:
            y += 1
            err += dy
            dy += 2
        if err > 0:
            x -= 1
            dx += 2
            err += dx - (rad << 1)
            
draw_circle(CENTER_X, CENTER_Y, 100, st7789.color565(255, 255, 255))
draw_circle(CENTER_X, CENTER_Y, 97, st7789.color565(255, 0, 0))
draw_circle(CENTER_X, CENTER_Y, 94, st7789.color565(0, 255, 0))
draw_circle(CENTER_X, CENTER_Y, 91, st7789.color565(0, 0, 255))
utime.sleep(2)

display.fill(st7789.BLACK)
display.text(font, "Test various", 20, 10)
display.text(font, "approach to", 20, 50)
display.text(font, "fill pixels", 20, 90)
utime.sleep(2)

#test various approach to fill pixels
display.fill(st7789.BLACK)
display.text(font, "pixel()", 20, 10)
display.text(font, "optimized", 20, 70)
display.text(font, "blit_buffer()", 20, 130)
display.text(font, "fill_rect()", 20, 190)
utime.sleep(1)

# fill area with display.pixel()
ms_start = utime.ticks_ms()
for y in range(60):
    for x in range(240):
        display.pixel(x, y, st7789.color565(x, 0, 0))
ms_now = utime.ticks_ms()
display.text(font, str(utime.ticks_diff(ms_now,ms_start))+" ms", 50, 10)

# fill area optimized
#!!! may be NOT suit your setup
ms_start = utime.ticks_ms()
display.set_window(0, 60, 239, 119)
pin_st7789_dc.on()
for y in range(60, 120):
    for x in range(240):
        spi2.write(struct.pack(st7789._ENCODE_PIXEL,
                               (0 & 0xf8) << 8 | (x & 0xfc) << 3 | 0 >> 3))
ms_now = utime.ticks_ms()
display.text(font, str(utime.ticks_diff(ms_now,ms_start))+" ms", 50, 70)

# fill with blit_buffer(buffer, x, y, width, height)
buffer = bytearray(240*60*2)

ms_pre = utime.ticks_ms()
#prepare buffer
for y in range(60):
    for x in range(240):
        idx = ((y*240) + x)*2
        pxCol = (y & 0xf8) << 8 | (0 & 0xfc) << 3 | x >> 3
        packedPx = struct.pack(st7789._ENCODE_PIXEL, pxCol)
        buffer[idx] = packedPx[0]
        buffer[idx+1] = packedPx[1]

ms_start = utime.ticks_ms()
display.blit_buffer(buffer, 0, 120, 240, 60)
ms_now = utime.ticks_ms()
strToDisp = str(utime.ticks_diff(ms_start,ms_pre)) + \
    "/" + str(utime.ticks_diff(ms_now,ms_start)) + " ms"
display.text(font, strToDisp, 50, 130)

# fill area with display.fill_rect()
ms_start = utime.ticks_ms()
display.fill_rect(0, 180, 240, 60, st7789.color565(0, 150, 150))
ms_now = utime.ticks_ms()
display.text(font, str(utime.ticks_diff(ms_now,ms_start))+" ms", 50, 190)

print("- bye-")
Next:
Raspberry Pi/Python send image to ESP32/MicroPython via WiFi TCP socket, display on this 240*240 IPS (ST7789 SPI) LCD.


Remark about SPI@2021-04-07:

The above exercise run on version MicroPython version 'v1.14 on 2021-03-17', SPI2 defined with default pin assignment.

Recently, I tested it on 'MicroPython v1.14 2021-02-02' (stable version esp32spiram-idf4-20210202-v1.14.bin). It's founded there are NO default pin assigned to SPI.


So you have to define the pins in your code,like this:
pin_spi2_sck = machine.Pin(18, machine.Pin.OUT)
pin_spi2_mosi = machine.Pin(23, machine.Pin.OUT)
pin_spi2_miso = machine.Pin(19, machine.Pin.IN)
spi2 = machine.SPI(2, sck=pin_spi2_sck, mosi=pin_spi2_mosi, miso=pin_spi2_miso,
                   baudrate=40000000, polarity=1)
On current latest unstable version esp32spiram-20210407-unstable-v1.14-142-gcb396827f.bin, default pins are defined. Pins can be omitted using default assignment.




Saturday, March 13, 2021

RPi Pico + ESP32-S remote control ESP32-DevKitC via WiFi TCP, using MicroPython.

This exercise program Raspberry Pi Pico/MicroPython + ESP32-S (ESP-AT) as WiFi TCP client to remote control ESP32-DevKitC V4/MicroPython WiFi TCP server onboard LED.

In Client side:

ESP32-S is a wireless module based on ESP32. It's flashed with AT-command firmware ESP-AT. It's act as a WiFi co-processor. Raspberry Pi Pico/MicroPython send AT-command to ESP32-S via UART. Please note that for ESP32 flashed with ESP-AT: UART1 (IO16/IO17) is used to send AT commands and receive AT responses, connected to GP0/GP1 of Pico.

Pico GP15 connected to ESP32-S EN pin, to reset it in power up.

Pico GP16 is used control remote LED.

In Server side:

ESP32-DevKitC V4 (with ESP32-WROVER-E module)/MicroPython is programed as WiFi server, receive command from client to turn ON/OFF LED accordingly.

Connection:


MicroPython code:

Client side, mpyPico_ESP32S_remoteCli_.py run on Raspberry Pi Pico.
import uos
import machine
import utime
"""
Raspberry Pi Pico/MicroPython + ESP32-S exercise

ESP32-S with AT-command firmware (ESP-AT):
---------------------------------------------------
AT version:2.1.0.0(883f7f2 - Jul 24 2020 11:50:07)
SDK version:v4.0.1-193-ge7ac221
compile time(0ad6331):Jul 28 2020 02:47:21
Bin version:2.1.0WROM-3)
---------------------------------------------------

Pico send AT command to ESP32-S via UART,
Send command to server (ESP32/MicroPython)
to turn ON/OFF LED on server.
---------------------------------------------------
Connection:
powered by separated power supply
Pico                  ESP32-S
GND                   GND
GP0 (TX) (pin 1)      IO16 (RXD)
GP1 (RX) (pin 2)      IO17 (TXD)
GP15 (pin 20)         EN

GP16 (pin 21) button
---------------------------------------------------
"""
#server port & ip hard-coded,
#have to match with server side setting
server_ip="192.168.4.1"
server_port=8000

network_ssid = "ESP32-ssid"
network_password = "password"

ESP_EN = 15
PIN_ESP_EN = machine.Pin(ESP_EN, machine.Pin.IN, machine.Pin.PULL_UP)
DIn = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)


print()
print("Machine: \t" + uos.uname()[4])
print("MicroPython: \t" + uos.uname()[3])
#indicate program started visually
led_onboard = machine.Pin(25, machine.Pin.OUT)
led_onboard.value(0)     # Toggle onboard LED 
utime.sleep(0.5)         # to indiacte program start
led_onboard.value(1)
utime.sleep(1)
led_onboard.value(0)

#Reset ESP
PIN_ESP_EN = machine.Pin(ESP_EN, machine.Pin.OUT)
PIN_ESP_EN.value(1)
utime.sleep(0.5)
PIN_ESP_EN.value(0)
utime.sleep(0.5)
PIN_ESP_EN.value(1)
PIN_ESP_EN = machine.Pin(ESP_EN, machine.Pin.IN, machine.Pin.PULL_UP)

uart = machine.UART(0, baudrate=115200)
print(uart)

RES_OK = b'OK\r\n'
len_OK = len(RES_OK)

RESULT_OK      = '0'
RESULT_TIMEOUT = '1'
def sendCMD_waitResult(cmd, timeout=2000):
    print("CMD: " + cmd)
    uart.write(cmd)

    prvMills = utime.ticks_ms()
    result = RESULT_TIMEOUT
    resp = b""
    
    while (utime.ticks_ms()-prvMills)<timeout:
        if uart.any():
            resp = b"".join([resp, uart.read(1)])
            resp_len = len(resp)
            if resp[resp_len-len_OK:]==RES_OK:
                print(RES_OK + " found!")
                result = RESULT_OK
                break
    
    print("resp:")
    try:
        print(resp.decode())
    except UnicodeError:
        print(resp)

    return result

#to make it simple to detect, RMCMD & RMSTA designed same length
#Remote Command from client to serve
RMCMD_len = 6
RMCMD_ON  = "LEDONN"   #turn LED ON
RMCMD_OFF = "LEDOFF"   #turn LED OFF
#Remote status from server to client
RMSTA_len = 6
RMSTA_timeout = "timeout" #time out without/unknown reply
RMSTA_LEDON  = "LedOnn"
RMSTA_LEDOFF = "LedOff"
"""
#Expected flow to send command to wifi is:
Pico (client) to ESP-01S    response from ESP-01S to Pico
AT+CIPSEND=<cmd len>\r\n
                            AT+CIPSEND=<cmd len>\r\n
                            OK\r\n
                            >\r\n
<cmd>
                            Recv x bytes\r\n
                            SEND OK\r\n           ---> ESP (server)
                                                  <--- ESP (server) end with OK\r\n
                            +IPD,10:<RMSTA>OK\r\n
                            +IPD,2:\r\n
"""
def sendRemoteCmd(rmcmd, timeout=2000):
    result = RMSTA_timeout
    
    if sendCMD_waitResult('AT+CIPSEND=' + str(len(rmcmd)) + '\r\n', timeout)==RESULT_OK:
        
        #dummy read '>'
        while not uart.any():
            pass
        print(uart.read(1))
        
        print("Remote CMD: " + rmcmd)
        if sendCMD_waitResult(rmcmd) == RESULT_OK:
            
            endMills = utime.ticks_ms() + timeout
            resp = b""
            
            while utime.ticks_ms()<endMills:
                if uart.any():
                    resp = b"".join([resp, uart.read(1)])
                    resp_len = len(resp)
                    if resp[resp_len-len_OK:]==RES_OK:
                        print(RES_OK + " found!")
                        rmSta=resp[resp_len-len_OK-RMSTA_len:resp_len-len_OK]
                        strRmSta=rmSta.decode()   #convert bytes to string
                        print(strRmSta)
                        if strRmSta == RMSTA_LEDON:
                            result = strRmSta
                        elif strRmSta == RMSTA_LEDOFF:
                            result = strRmSta
                        break
            print("resp:")
            try:
                print(resp.decode())
            except UnicodeError:
                print(resp)
    
    return result

def sendCMD_waitResp(cmd, timeout=2000):
    print("CMD: " + cmd)
    uart.write(cmd)
    waitResp(timeout)
    print()
    
def waitResp(timeout=2000):
    prvMills = utime.ticks_ms()
    resp = b""
    while (utime.ticks_ms()-prvMills)<timeout:
        if uart.any():
            resp = b"".join([resp, uart.read(1)])
    print("resp:")
    try:
        print(resp.decode())
    except UnicodeError:
        print(resp)
        
"""
everytimes send command to server:
- join ESP32 network
- connect to ESP32 socket
- send command to server and receive status
"""
def connectRemoteSendCmd(cmdsend):
    clearRxBuf()
    print("join wifi network: " + "ESP32-ssid")
    while sendCMD_waitResult('AT+CWJAP="' + network_ssid + '","'
                       + network_password + '"\r\n') != RESULT_OK:
        pass
    
    sendCMD_waitResp('AT+CIFSR\r\n')    #Obtain the Local IP Address
    sendCMD_waitResult('AT+CIPSTATUS\r\n')
    
    print("wifi network joint")
    print("connect socket")

    if sendCMD_waitResult('AT+CIPSTART="TCP","'
                      + server_ip
                      + '",'
                      + str(server_port) + '\r\n', timeout=5000) == RESULT_OK:
        sendCMD_waitResult('AT+CIPSTATUS\r\n')
        print("RMST: " + sendRemoteCmd(rmcmd=cmdsend))

    clearRxBuf()
    sendCMD_waitResult('AT+CIPSTATUS\r\n')
    sendCMD_waitResult('AT+CWQAP\r\n')

            
def clearRxBuf():
    print("--- clear Rx buffer ---")
    buf = b""
    while uart.any():
        buf = b"".join([buf, uart.read(1)])
    print(buf)
    print("-----------------------")
    
led_onboard.value(0)
clearRxBuf()
sendCMD_waitResult('AT\r\n')          #Test AT startup
sendCMD_waitResult('AT+CWMODE=1\r\n') #Set the Wi-Fi mode 1 = Station mode
sendCMD_waitResult('AT+CIPMUX=0\r\n') #single connection.

led_onboard.value(1)
connectRemoteSendCmd(cmdsend=RMCMD_ON)
utime.sleep(1)
connectRemoteSendCmd(cmdsend=RMCMD_OFF)

#fast toggle led 5 times to indicate startup finished
for i in range(5):
    led_onboard.value(0)
    utime.sleep(0.2)
    led_onboard.value(1)
    utime.sleep(0.2)
    led_onboard.value(0)

print("Started")
print("waiting for button")
#read digital input every 10ms
dinMills = utime.ticks_ms() + 30
prvDin = 1
debounced = False
while True:
    if utime.ticks_ms() > dinMills:
        dinMills = utime.ticks_ms() + 30
        curDin = DIn.value()
        if curDin != prvDin:
            #Din changed
            prvDin = curDin
            debounced = False
        else:
            if not debounced:
                #DIn changed for > 30ms
                debounced = True
                if curDin:
                    connectRemoteSendCmd(cmdsend=RMCMD_OFF)
                else:
                    connectRemoteSendCmd(cmdsend=RMCMD_ON)
                    
                
    

Server side, upyESP32_AP_RemoteSvr_.py run on ESP32-DevKitC V4.
import utime
import uos
import network
import usocket
from machine import Pin

"""
ESP32/MicroPython exercise:
ESP32 act as Access Point,
and setup a simple TCP server

receive command from client and turn ON/OFF LED,
and send back status.
"""

ssid= "ESP32-ssid"
password="password"

led=Pin(13,Pin.OUT)

print("----- MicroPython -----")
for u in uos.uname():
    print(u)
print("-----------------------")

for i in range(3):
    led.on()
    utime.sleep(0.5)
    led.off()
    utime.sleep(0.5)
    

ap = network.WLAN(network.AP_IF) # Access Point
ap.config(essid=ssid,
          password=password,
          authmode=network.AUTH_WPA_WPA2_PSK) 
ap.config(max_clients=1)  # max number of client
ap.active(True)           # activate the access point

print(ap.ifconfig())
print(dir(ap))

mysocket = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM)
mysocket.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR, 1)

port = 8000
mysocket.bind(('',8000))
print("bind: " + str(port))
mysocket.listen(1)

#tomake it simple to detect, RMCMD & RMSTA designed same length
#Remote Command from client to serve
RMCMD_len = 6
RMCMD_ON  = "LEDONN"    #turn LED ON
RMCMD_OFF = "LEDOFF"   #turn LED OFF
#Remote status from server to client
RMSTA_len = 6
RMSTA_timeout = "timeout" #time out without/unknown reply
RMSTA_LEDON  = "LedOnn"
RMSTA_LEDOFF = "LedOff"

while True:
  conn, addr = mysocket.accept()
  print('Connected from: %s' % str(addr))
  print()
  request = conn.recv(1024)
  print('request: %s' % str(request))
  print()
  
  strRqs = request.decode()
  print("strRqs: "+strRqs)
  if strRqs==RMCMD_ON:
      conn.send(RMSTA_LEDON+'OK\r\n')
      led.on()
  elif strRqs==RMCMD_OFF:
      conn.send(RMSTA_LEDOFF+'OK\r\n')
      led.off()
  else:
      #unknown command
      conn.send(request.upper())
  conn.send('\r\n')
  conn.close()


Saturday, March 6, 2021

RPi Pico/MicroPython + ILI9341 SPI Display with Touch, using rdagger/micropython-ili9341

The display used in this exercise is a 2.4-inch 65K color using ili9341 driver with touch, 2.4inch_SPI_Module_ILI9341_SKU:MSP2402. I have other exercises using jeffmer/micropython-ili9341 library. This exercise using another lib rdagger/micropython-ili9341.

rdagger/micropython-ili9341 is a MicroPython ILI9341 Display and XPT2046 Touch Screen Drivers.

Connection:



Library:

Visit rdagger/micropython-ili9341, copy ili9341.py, xglcd_font.py and fonts/Unispace12x24.c to your Raspberry Pi Pico device.

Exercise code:

To make it easy to update the lib's examples, I define the hardware connection in separated code, mySetup.py. Save it to Raspberry Pi Pico device.
from ili9341 import Display
from machine import Pin, SPI

TFT_CLK_PIN = const(6)
TFT_MOSI_PIN = const(7)
TFT_MISO_PIN = const(4)

TFT_CS_PIN = const(13)
TFT_RST_PIN = const(14)
TFT_DC_PIN = const(15)

def createMyDisplay():
    #spi = SPI(0, baudrate=40000000, sck=Pin(TFT_CLK_PIN), mosi=Pin(TFT_MOSI_PIN))
    spiTFT = SPI(0, baudrate=51200000,
                 sck=Pin(TFT_CLK_PIN), mosi=Pin(TFT_MOSI_PIN))
    display = Display(spiTFT,
                      dc=Pin(TFT_DC_PIN), cs=Pin(TFT_CS_PIN), rst=Pin(TFT_RST_PIN))
    return display
        


My exercise code, mPico_ili9341_test.py.
"""
Raspperry Pi Pico exercise display on ili9341 SPI Display
using rdagger/micropython-ili9341,
MicroPython ILI9341 Display and XPT2046 Touch Screen Drivers
https://github.com/rdagger/micropython-ili9341
"""
from machine import Pin, SPI
from sys import implementation
from os import uname
import utime

import ili9341
from xglcd_font import XglcdFont

import mySetup

print(implementation.name)
print(uname()[3])
print(uname()[4])

print(SPI(0))
print(SPI(1))

display = mySetup.createMyDisplay()

print('Loading fonts...')
print('Loading unispace')
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)

display.draw_text(0, 0, ili9341.__name__, unispace,
                  ili9341.color565(255, 128, 0))
display.draw_text(0, 25, ili9341.implementation.name, unispace,
                  ili9341.color565(0, 0, 200))
display.draw_text(0, 50, str(ili9341.implementation.version), unispace,
                  ili9341.color565(0, 0, 200))

display.draw_text(0, 100, "https://github.com/", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 125, "rdagger/micropython-ili9341", unispace,
                  ili9341.color565(200, 200, 200))

display.draw_text(0, 175, "ABCDEFGHIJKLMNOPQRS", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 200, "TUVWXYZ", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 225, "abcdefghijklmnopqrs", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 250, "tuvwxyz", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 275, "01234567890", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 300, "~!@#$%^&*()_+`-={}[]", unispace,
                  ili9341.color565(200, 200, 200))
display.draw_text(0, 325, "\|;:'<>,.?/", unispace,
                  ili9341.color565(200, 200, 200))
    
for i in range(320):
    display.scroll(i)
    utime.sleep(0.02)
    
for i in range(320, 0, -1):
    display.scroll(i)
    utime.sleep(0.02)

utime.sleep(0.5)
# Display inversion on
display.write_cmd(display.INVON)
utime.sleep(2)
# Display inversion off
display.write_cmd(display.INVOFF)

while True:
    pass

print("- bye -")


I also modify some of the examples in the library.

demo_bouncing_boxes_.py
"""ILI9341 demo (bouncing boxes)."""
from machine import Pin, SPI
from random import random, seed
from ili9341 import Display, color565
from utime import sleep_us, ticks_cpu, ticks_us, ticks_diff
import mySetup

class Box(object):
    """Bouncing box."""

    def __init__(self, screen_width, screen_height, size, display, color):
        """Initialize box.

        Args:
            screen_width (int): Width of screen.
            screen_height (int): Width of height.
            size (int): Square side length.
            display (ILI9341): display object.
            color (int): RGB565 color value.
        """
        self.size = size
        self.w = screen_width
        self.h = screen_height
        self.display = display
        self.color = color
        # Generate non-zero random speeds between -5.0 and 5.0
        seed(ticks_cpu())
        r = random() * 10.0
        self.x_speed = 5.0 - r if r < 5.0 else r - 10.0
        r = random() * 10.0
        self.y_speed = 5.0 - r if r < 5.0 else r - 10.0

        self.x = self.w / 2.0
        self.y = self.h / 2.0
        self.prev_x = self.x
        self.prev_y = self.y

    def update_pos(self):
        """Update box position and speed."""
        x = self.x
        y = self.y
        size = self.size
        w = self.w
        h = self.h
        x_speed = abs(self.x_speed)
        y_speed = abs(self.y_speed)
        self.prev_x = x
        self.prev_y = y

        if x + size >= w - x_speed:
            self.x_speed = -x_speed
        elif x - size <= x_speed + 1:
            self.x_speed = x_speed

        if y + size >= h - y_speed:
            self.y_speed = -y_speed
        elif y - size <= y_speed + 1:
            self.y_speed = y_speed

        self.x = x + self.x_speed
        self.y = y + self.y_speed

    def draw(self):
        """Draw box."""
        x = int(self.x)
        y = int(self.y)
        size = self.size
        prev_x = int(self.prev_x)
        prev_y = int(self.prev_y)
        self.display.fill_hrect(prev_x - size,
                                prev_y - size,
                                size, size, 0)
        self.display.fill_hrect(x - size,
                                y - size,
                                size, size, self.color)


def test():
    """Bouncing box."""
    try:
        # Baud rate of 40000000 seems about the max
        #spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(13))
        #display = Display(spi, dc=Pin(4), cs=Pin(16), rst=Pin(17))
        display = mySetup.createMyDisplay()
        
        display.clear()

        colors = [color565(255, 0, 0),
                  color565(0, 255, 0),
                  color565(0, 0, 255),
                  color565(255, 255, 0),
                  color565(0, 255, 255),
                  color565(255, 0, 255)]
        sizes = [12, 11, 10, 9, 8, 7]
        boxes = [Box(239, 319, sizes[i], display,
                 colors[i]) for i in range(6)]

        while True:
            timer = ticks_us()
            for b in boxes:
                b.update_pos()
                b.draw()
            # Attempt to set framerate to 30 FPS
            timer_dif = 33333 - ticks_diff(ticks_us(), timer)
            if timer_dif > 0:
                sleep_us(timer_dif)

    except KeyboardInterrupt:
        display.cleanup()


test()


demo_colored_squares_.py
"""ILI9341 demo (colored squares)."""
from time import sleep
from ili9341 import Display
from machine import Pin, SPI
from sys import modules
import mySetup

RED = const(0XF800)  # (255, 0, 0)
GREEN = const(0X07E0)  # (0, 255, 0)
BLUE = const(0X001F)  # (0, 0, 255)
YELLOW = const(0XFFE0)  # (255, 255, 0)
FUCHSIA = const(0XF81F)  # (255, 0, 255)
AQUA = const(0X07FF)  # (0, 255, 255)
MAROON = const(0X8000)  # (128, 0, 0)
DARKGREEN = const(0X0400)  # (0, 128, 0)
NAVY = const(0X0010)  # (0, 0, 128)
TEAL = const(0X0410)  # (0, 128, 128)
PURPLE = const(0X8010)  # (128, 0, 128)
OLIVE = const(0X8400)  # (128, 128, 0)
ORANGE = const(0XFC00)  # (255, 128, 0)
DEEP_PINK = const(0XF810)  # (255, 0, 128)
CHARTREUSE = const(0X87E0)  # (128, 255, 0)
SPRING_GREEN = const(0X07F0)  # (0, 255, 128)
INDIGO = const(0X801F)  # (128, 0, 255)
DODGER_BLUE = const(0X041F)  # (0, 128, 255)
CYAN = const(0X87FF)  # (128, 255, 255)
PINK = const(0XFC1F)  # (255, 128, 255)
LIGHT_YELLOW = const(0XFFF0)  # (255, 255, 128)
LIGHT_CORAL = const(0XFC10)  # (255, 128, 128)
LIGHT_GREEN = const(0X87F0)  # (128, 255, 128)
LIGHT_SLATE_BLUE = const(0X841F)  # (128, 128, 255)
WHITE = const(0XFFF)  # (255, 255, 255)

colors = [RED,
          GREEN,
          BLUE,
          YELLOW,
          FUCHSIA,
          AQUA,
          MAROON,
          DARKGREEN,
          NAVY,
          TEAL,
          PURPLE,
          OLIVE,
          ORANGE,
          DEEP_PINK,
          CHARTREUSE,
          SPRING_GREEN,
          INDIGO,
          DODGER_BLUE,
          CYAN,
          PINK,
          LIGHT_YELLOW,
          LIGHT_CORAL,
          LIGHT_GREEN,
          LIGHT_SLATE_BLUE,
          WHITE ]

def test():
    """Test code."""
    """
    # Baud rate of 40000000 seems about the max
    spi = SPI(0, baudrate=40000000, sck=Pin(TFT_CLK_PIN), mosi=Pin(TFT_MOSI_PIN))
    display = Display(spi, dc=Pin(TFT_DC_PIN), cs=Pin(TFT_CS_PIN), rst=Pin(TFT_RST_PIN))
    """
    display = mySetup.createMyDisplay()

    """
    # Build color list from all upper case constants (lazy approach)
    colors = [getattr(modules[__name__], name) for name in dir(
        modules[__name__]) if name.isupper() and name is not 'SPI']
    """

    colors.sort()
    c = 0
    for x in range(0, 240, 48):
        for y in range(0, 320, 64):
            display.fill_rectangle(x, y, 47, 63, colors[c])
            c += 1
    sleep(9)
    display.cleanup()


test()


demo_color_palette_.py
"""ILI9341 demo (color palette)."""
from time import sleep
from ili9341 import Display, color565
from machine import Pin, SPI
import mySetup

def hsv_to_rgb(h, s, v):
    """
    Convert HSV to RGB (based on colorsys.py).

        Args:
            h (float): Hue 0 to 1.
            s (float): Saturation 0 to 1.
            v (float): Value 0 to 1 (Brightness).
    """
    if s == 0.0:
        return v, v, v
    i = int(h * 6.0)
    f = (h * 6.0) - i
    p = v * (1.0 - s)
    q = v * (1.0 - s * f)
    t = v * (1.0 - s * (1.0 - f))
    i = i % 6

    v = int(v * 255)
    t = int(t * 255)
    p = int(p * 255)
    q = int(q * 255)

    if i == 0:
        return v, t, p
    if i == 1:
        return q, v, p
    if i == 2:
        return p, v, t
    if i == 3:
        return p, q, v
    if i == 4:
        return t, p, v
    if i == 5:
        return v, p, q


def test():
    """Test code."""
    # Baud rate of 40000000 seems about the max
    #spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(13))
    #display = Display(spi, dc=Pin(4), cs=Pin(16), rst=Pin(17))
    display = mySetup.createMyDisplay()

    c = 0
    for x in range(0, 240, 20):
        for y in range(0, 320, 20):
            color = color565(*hsv_to_rgb(c / 192, 1, 1))
            display.fill_circle(x + 9, y + 9, 9, color)
            c += 1
    sleep(9)
    display.cleanup()


test()


demo_color_wheel_.py
"""ILI9341 demo (color wheel)."""
from time import sleep
from ili9341 import Display, color565
from machine import Pin, SPI
from math import cos, pi, sin
import mySetup

HALF_WIDTH = const(120)
HALF_HEIGHT = const(160)
CENTER_X = const(119)
CENTER_Y = const(159)
ANGLE_STEP_SIZE = 0.05  # Decrease step size for higher resolution
PI2 = pi * 2


def hsv_to_rgb(h, s, v):
    """
    Convert HSV to RGB (based on colorsys.py).

        Args:
            h (float): Hue 0 to 1.
            s (float): Saturation 0 to 1.
            v (float): Value 0 to 1 (Brightness).
    """
    if s == 0.0:
        return v, v, v
    i = int(h * 6.0)
    f = (h * 6.0) - i
    p = v * (1.0 - s)
    q = v * (1.0 - s * f)
    t = v * (1.0 - s * (1.0 - f))
    i = i % 6

    v = int(v * 255)
    t = int(t * 255)
    p = int(p * 255)
    q = int(q * 255)

    if i == 0:
        return v, t, p
    if i == 1:
        return q, v, p
    if i == 2:
        return p, v, t
    if i == 3:
        return p, q, v
    if i == 4:
        return t, p, v
    if i == 5:
        return v, p, q


def test():
    """Test code."""
    # Baud rate of 40000000 seems about the max
    #spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(13))
    #display = Display(spi, dc=Pin(4), cs=Pin(16), rst=Pin(17))
    display = mySetup.createMyDisplay()

    x, y = 0, 0
    angle = 0.0
    #  Loop all angles from 0 to 2 * PI radians
    while angle < PI2:
        # Calculate x, y from a vector with known length and angle
        x = int(CENTER_X * sin(angle) + HALF_WIDTH)
        y = int(CENTER_Y * cos(angle) + HALF_HEIGHT)
        color = color565(*hsv_to_rgb(angle / PI2, 1, 1))
        display.draw_line(x, y, CENTER_X, CENTER_Y, color)
        angle += ANGLE_STEP_SIZE

    sleep(5)

    for r in range(CENTER_X, 0, -1):
        color = color565(*hsv_to_rgb(r / HALF_WIDTH, 1, 1))
        display.fill_circle(CENTER_X, CENTER_Y, r, color)

    sleep(9)
    display.cleanup()


test()


demo_shapes_.py
"""ILI9341 demo (shapes)."""
from time import sleep
from ili9341 import Display, color565
from machine import Pin, SPI
import mySetup


def test():
    """Test code."""
    # Baud rate of 40000000 seems about the max
    #spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(13))
    #display = Display(spi, dc=Pin(4), cs=Pin(16), rst=Pin(17))
    display = mySetup.createMyDisplay()

    display.clear(color565(64, 0, 255))
    sleep(1)

    display.clear()

    display.draw_hline(10, 319, 229, color565(255, 0, 255))
    sleep(1)

    display.draw_vline(10, 0, 319, color565(0, 255, 255))
    sleep(1)

    display.fill_hrect(23, 50, 30, 75, color565(255, 255, 255))
    sleep(1)

    display.draw_hline(0, 0, 222, color565(255, 0, 0))
    sleep(1)

    display.draw_line(127, 0, 64, 127, color565(255, 255, 0))
    sleep(2)

    display.clear()

    coords = [[0, 63], [78, 80], [122, 92], [50, 50], [78, 15], [0, 63]]
    display.draw_lines(coords, color565(0, 255, 255))
    sleep(1)

    display.clear()
    display.fill_polygon(7, 120, 120, 100, color565(0, 255, 0))
    sleep(1)

    display.fill_rectangle(0, 0, 15, 227, color565(255, 0, 0))
    sleep(1)

    display.clear()

    display.fill_rectangle(0, 0, 163, 163, color565(128, 128, 255))
    sleep(1)

    display.draw_rectangle(0, 64, 163, 163, color565(255, 0, 255))
    sleep(1)

    display.fill_rectangle(64, 0, 163, 163, color565(128, 0, 255))
    sleep(1)

    display.draw_polygon(3, 120, 286, 30, color565(0, 64, 255), rotate=15)
    sleep(3)

    display.clear()

    display.fill_circle(132, 132, 70, color565(0, 255, 0))
    sleep(1)

    display.draw_circle(132, 96, 70, color565(0, 0, 255))
    sleep(1)

    display.fill_ellipse(96, 96, 30, 16, color565(255, 0, 0))
    sleep(1)

    display.draw_ellipse(96, 256, 16, 30, color565(255, 255, 0))

    sleep(5)
    display.cleanup()


test()




Detect Touch:


To detect touch, visit rdagger/micropython-ili9341, download xpt2046.py to Raspberry Pi Pico.

Save mySetupX.py to Raspberry Pi Pico, with setup for xpd2046. Connect extra pins accordingly.

mySetupX.py
from ili9341 import Display
from machine import Pin, SPI
from xpt2046 import Touch

TFT_CLK_PIN = const(6)
TFT_MOSI_PIN = const(7)
TFT_MISO_PIN = const(4)

TFT_CS_PIN = const(13)
TFT_RST_PIN = const(14)
TFT_DC_PIN = const(15)

XPT_CLK_PIN = const(10)
XPT_MOSI_PIN = const(11)
XPT_MISO_PIN = const(8)

XPT_CS_PIN = const(12)
XPT_INT = const(0)

def createMyDisplay():
    spiTFT = SPI(0, baudrate=40000000, sck=Pin(TFT_CLK_PIN), mosi=Pin(TFT_MOSI_PIN))
    display = Display(spiTFT,
                      dc=Pin(TFT_DC_PIN), cs=Pin(TFT_CS_PIN), rst=Pin(TFT_RST_PIN))
    return display

def createXPT(touch_handler):
    spiXPT = SPI(1, baudrate=1000000,
                 sck=Pin(XPT_CLK_PIN), mosi=Pin(XPT_MOSI_PIN), miso=Pin(XPT_MISO_PIN))

    xpt = Touch(spiXPT, cs=Pin(XPT_CS_PIN), int_pin=Pin(XPT_INT),
                int_handler=touch_handler)

    return xpt

Run xpt_cal_.py, touch the corners to found the min/max of x/y. The detected min/max of x/y will be shown on screen. (Please check the video, y_max=2047 may be mis-detected, but very close. It apear before touched.)

xpt_cal_.py
from machine import Pin, SPI
from sys import implementation
from os import uname
import ili9341
from xglcd_font import XglcdFont
import mySetupX

print(implementation.name)
print(uname()[3])
print(uname()[4])

print(SPI(0))
print(SPI(1))

minX = maxX = minY = maxY = 500

def xpt_touch(x, y):
    global xptTouch
    global minX, maxX, minY, maxY
    
    touchXY = xptTouch.get_touch()
    rawX = xptTouch.send_command(xptTouch.GET_X)
    rawY = xptTouch.send_command(xptTouch.GET_Y)
    
    if rawX != 0:
        if rawX > maxX:
            maxX = rawX
        elif rawX < minX:
            minX = rawX
    if rawY != 0:    
        if rawY > maxY:
            maxY = rawY
        elif rawY < minY:
            minY = rawY
    
    display.fill_circle(x, y, 2, ili9341.color565(0, 255, 0))
    print(str(x) + ":" + str(y) + " / " + str(rawX) + ":" + str(rawY))
    
    if touchXY != None:
        touchX = touchXY[0]
        touchY = touchXY[1]
        display.fill_circle(touchX, touchY, 2, ili9341.color565(255, 0, 0))
        print(str(touchX) + ":" + str(touchY))
        
    xReading = "X: " + str(minX) + " - " + str(maxX) + "       "
    yReading = "Y: " + str(minY) + " - " + str(maxY) + "       "
        
    display.draw_text(0, 100, xReading, unispace,
                  ili9341.color565(255, 128, 0))
    display.draw_text(0, 125, yReading, unispace,
                  ili9341.color565(255, 128, 0))

display = mySetupX.createMyDisplay()
xptTouch = mySetupX.createXPT(xpt_touch)

print('Loading fonts...')
print('Loading unispace')
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)

display.draw_text(0, 0, ili9341.__name__, unispace,
                  ili9341.color565(255, 128, 0))
display.draw_text(0, 25, ili9341.implementation.name, unispace,
                  ili9341.color565(0, 0, 200))
display.draw_text(0, 50, str(ili9341.implementation.version), unispace,
                  ili9341.color565(0, 0, 200))

while True:
    pass

print("- bye -")

Update x_min, x_max, y_min and y_max in mySetupX.py accordingly.
    xpt = Touch(spiXPT, cs=Pin(XPT_CS_PIN), int_pin=Pin(XPT_INT),
                int_handler=touch_handler,
                x_min=64, x_max=1847, y_min=148, y_max=2047)

Run my exercise code, xpt_moveII_2.4.py.

xpt_moveII_2.4.py
from machine import Pin, SPI, Timer
from sys import implementation
from os import uname
import ili9341
from xglcd_font import XglcdFont
import mySetupX

print(implementation.name)
print(uname()[3])
print(uname()[4])

print(SPI(0))
print(SPI(1))

led = Pin(25, Pin.OUT)

#=== variable share btween ISR and main loop ===
x_passedTo_ISR = 0
y_passwsTo_ISR = 0

EVT_NO = const(0)
EVT_PenDown = const(1)
EVT_PenUp   = const(2)
event = EVT_NO

TimerReached = False
#===============================================

def xpt_touch(x, y):
    global event, x_passedTo_ISR, y_passedTo_ISR
    event = EVT_PenDown
    x_passedTo_ISR = x
    y_passedTo_ISR = y

display = mySetupX.createMyDisplay()
xptTouch = mySetupX.createXPT(xpt_touch)

print('Loading fonts...')
print('Loading unispace')
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)

display.clear()
display.draw_text(0, 0, ili9341.__name__, unispace,
                  ili9341.color565(255, 128, 0))
display.draw_text(0, 25, ili9341.implementation.name, unispace,
                  ili9341.color565(0, 0, 200))
display.draw_text(0, 50, str(ili9341.implementation.version), unispace,
                  ili9341.color565(0, 0, 200))

tim = Timer()
def TimerTick(timer):
    global TimerReached
    TimerReached = True

tim.init(freq=50, mode=Timer.PERIODIC, callback=TimerTick)

touching = False

lastX = lastY = 0

while True:
    curEvent = event
    event = EVT_NO
    if curEvent!= EVT_NO:
        if curEvent == EVT_PenDown:
            print("Pen Down")
            touching = True
            lastX = x_passedTo_ISR
            lastY = y_passedTo_ISR
    
            touchXY = xptTouch.get_touch()
            rawX = xptTouch.send_command(xptTouch.GET_X)
            rawY = xptTouch.send_command(xptTouch.GET_Y)
    
            display.clear()
            display.fill_circle(x_passedTo_ISR, y_passedTo_ISR,
                                8, ili9341.color565(0, 255, 0))
            print(str(x_passedTo_ISR) + ":" + str(y_passedTo_ISR) +
                  " / " + str(rawX) + ":" + str(rawY))
    
            if touchXY != None:
                touchX = touchXY[0]
                touchY = touchXY[1]
                display.fill_circle(touchX, touchY, 5, ili9341.color565(255, 0, 0))
                print(str(touchX) + ":" + str(touchY))
            
        elif curEvent == EVT_PenUp:
            print("Pen Up")
            pass
        else:
            print("unknown event!!!")
            
    if TimerReached:
        TimerReached = False
        
        if touching:
            led.toggle()
            buff = xptTouch.raw_touch()
            if buff is not None:
                x, y = xptTouch.normalize(*buff)
                lastX = x
                lastY = y
                display.fill_circle(x, y, 1, ili9341.color565(255, 255, 255))
                print("... " + str(x) + " : " + str(y))
            else:
                event = EVT_PenUp
                touching = False
                led.off()
                display.fill_circle(lastX, lastY, 5, ili9341.color565(0, 0, 255))

print("- bye -")

Tested on my another unit of 2.8 inch version, 2.8" TFT SPI 240*320. X-position is in reverse order, so edit for 2.8 inch version.

xpt_moveII_2.8.py
from machine import Pin, SPI, Timer
from sys import implementation
from os import uname
import ili9341
from xglcd_font import XglcdFont
import mySetupX

print(implementation.name)
print(uname()[3])
print(uname()[4])

print(SPI(0))
print(SPI(1))

led = Pin(25, Pin.OUT)

#=== variable share btween ISR and main loop ===
x_passedTo_ISR = 0
y_passwsTo_ISR = 0

EVT_NO = const(0)
EVT_PenDown = const(1)
EVT_PenUp   = const(2)
event = EVT_NO

TimerReached = False
#===============================================

def xpt_touch(x, y):
    global event, x_passedTo_ISR, y_passedTo_ISR
    event = EVT_PenDown
    x_passedTo_ISR = x
    y_passedTo_ISR = y

display = mySetupX.createMyDisplay()
xptTouch = mySetupX.createXPT(xpt_touch)

print('Loading fonts...')
print('Loading unispace')
unispace = XglcdFont('fonts/Unispace12x24.c', 12, 24)

display.clear()
display.draw_text(0, 0, ili9341.__name__, unispace,
                  ili9341.color565(255, 128, 0))
display.draw_text(0, 25, ili9341.implementation.name, unispace,
                  ili9341.color565(0, 0, 200))
display.draw_text(0, 50, str(ili9341.implementation.version), unispace,
                  ili9341.color565(0, 0, 200))

tim = Timer()
def TimerTick(timer):
    global TimerReached
    TimerReached = True

tim.init(freq=50, mode=Timer.PERIODIC, callback=TimerTick)

touching = False

lastX = lastY = 0

while True:
    curEvent = event
    event = EVT_NO
    if curEvent!= EVT_NO:
        if curEvent == EVT_PenDown:
            print("Pen Down")
            touching = True
            lastX = x_passedTo_ISR
            lastY = y_passedTo_ISR
    
            touchXY = xptTouch.get_touch()
            rawX = xptTouch.send_command(xptTouch.GET_X)
            rawY = xptTouch.send_command(xptTouch.GET_Y)
    
            display.clear()
            display.fill_circle(240-x_passedTo_ISR, y_passedTo_ISR,
                                8, ili9341.color565(0, 255, 0))
            print(str(x_passedTo_ISR) + ":" + str(y_passedTo_ISR) +
                  " / " + str(rawX) + ":" + str(rawY))
    
            if touchXY != None:
                touchX = touchXY[0]
                touchY = touchXY[1]
                display.fill_circle(240-touchX, touchY, 5, ili9341.color565(255, 0, 0))
                print(str(touchX) + ":" + str(touchY))
            
        elif curEvent == EVT_PenUp:
            print("Pen Up")
            pass
        else:
            print("unknown event!!!")
            
    if TimerReached:
        TimerReached = False
        
        if touching:
            led.toggle()
            buff = xptTouch.raw_touch()
            if buff is not None:
                x, y = xptTouch.normalize(*buff)
                lastX = x
                lastY = y
                display.fill_circle(240-x, y, 1, ili9341.color565(255, 255, 255))
                print("... " + str(x) + " : " + str(y))
            else:
                event = EVT_PenUp
                touching = False
                led.off()
                display.fill_circle(240-lastX, lastY, 5, ili9341.color565(0, 0, 255))

print("- bye -")


Related: