【micropython】SPI触摸屏开发
背景:
最近买了几块ESP32模块,看了下mircopython支持还不错,所以买了个SPI触摸屏试试水,记录一下使用过程。
硬件相关:
SPI触摸屏
使用2.4寸屏幕,常见淘宝均可买到,驱动为ILI9341,具体参数如下图:
![](/assets/blank.gif)
引脚描述:
![](/assets/blank.gif)
ESP32模块
依旧轻松购买于淘宝,由于ESP32被很多家封装,因此模块各有不同,我的如下:
![](/assets/blank.gif)
接口描述,网上找了个,差异点在于图中使用GIPOxx,而开发板印刷使用Dxx,例如,开发板上,D12,对应下图的GPIO12,其他的不需要关注:
![](/assets/blank.gif)
接线
由于ESP32模块有两个可用SPI,而触摸屏显示和触摸都是使用SPI协议,刚好用完资源,
SPI |
SCK |
MOSI |
MISO |
CS |
用途 |
SPI1(HSPI) |
GPIO14(D14) |
GPIO13(D13) |
GPIO12(D12) |
GPIO15(D15) |
分配给触摸接口 |
SPI2(VSPI) |
GPIO18(D18) |
GPIO23(D23) |
GPIO19(D19) |
GPIO5(D5) |
分配给显示接口 |
图上还会看到VSPI 和 HSPI
具体接线如下:
触摸屏 |
分配(ESP32引脚名) |
归属 |
|
显示设备 |
VCC |
3V3 |
ESP-IO |
GND |
GND |
ESP-IO |
|
CS |
D5 |
SPI2 |
|
RESET |
D2 |
ESP-IO |
|
DC/RS |
D21 |
ESP-IO |
|
SDI |
D23 |
SPI2 |
|
SCK |
D18 |
SPI2 |
|
LED |
D4 |
ESP-IO |
|
SDO |
D19 |
SPI2 |
|
触摸设备 |
T_CLK |
D14 |
SPI1 |
T_CS |
D15 |
SPI1 |
|
T_DIN |
D13 |
SPI1 |
|
T_DO |
D12 |
SPI1 |
|
T_IRQ |
D33 |
ESP-IO |
注意(不建议使用的引脚):
不建议使用或限制使用的引脚
不建议使用 Strapping引脚 ,SPI flash 引脚 以及 仅输入的引脚
Strapping 引脚
GPIO 0
GPIO 2
GPIO 4
GPIO 5 (启动时必须为高电平)
GPIO 12 (启动时必须为低电平)
GPIO 15 (启动时必须为高电平)
注意:
在硬件上要注意使用外接模块时不能将GPIO12拉高,否则将导致ESP32启动异常。还有一些GPIO在启动或重置时其状态更改为高或者输出PWM信号,在使用时需要注意。
集成在ESP-WROOM-32 的 SPI flash 引脚
GPIO 6 到 GPIO 11 在一些 ESP32 开发板中公开。但是,这些引脚连接到 ESP-WROOM-32 芯片上的集成 SPI 闪存,不推荐用于其他用途。所以,不要在你的项目中使用这些引脚:
GPIO 6 (SCK/CLK)
GPIO 7 (SDO/SD0)
GPIO 8 (SDI/SD1)
GPIO 9 (SHD/SD2)
GPIO 10 (SWP/SD3)
GPIO 11 (CSC/CMD)
仅输入引脚
GPIO 34 到 39 是 GPI – 仅输入引脚。这些引脚没有内部上拉或下拉电阻。它们不能用作输出,因此只能将这些引脚用作输入:
GPIO 34
GPIO 35
GPIO 36
GPIO 39
这些引脚都是ESP32用于引导加载程序或者烧录模式/在大多数内置USB/Serial的开发板上,不需要担心这些引脚的状态,开发板会把这些引脚设置为正确的状态,以便使用烧录或启动模式。
但是,如果你有外设连接到这些引脚上,当你在尝试上传新代码、用新固件烧写ESP32或重置电路板时可能会遇到麻烦,例如不明原因的错误和失败。可能是因为这些外设阻止ESP32进入正确的模式。
所以以上的引脚 不建议在项目中使用。
软件相关
目录结构
其中core/screen 文件夹,lib/ili9341.py 和 lib/xpt2046.py 以及main.py是我们本次测试文件.
![](/assets/blank.gif)
效果
显示测试:
![](/assets/blank.gif)
触摸测试:
![](/assets/blank.gif)
代码
lib/ili9341.py
"""ILI9341 LCD/Touch module."""
from time import sleep
from math import cos, sin, pi, radians
from sys import implementation
from framebuf import FrameBuffer, RGB565 # type: ignore
import ustruct # type: ignoredef color565(r, g, b):"""Return RGB565 color value.Args:r (int): Red value.g (int): Green value.b (int): Blue value."""return (r & 0xf8) << 8 | (g & 0xfc) << 3 | b >> 3class Display(object):"""Serial interface for 16-bit color (5-6-5 RGB) IL9341 display.Note: All coordinates are zero based."""# Command constants from ILI9341 datasheetNOP = const(0x00) # No-opSWRESET = const(0x01) # Software resetRDDID = const(0x04) # Read display ID infoRDDST = const(0x09) # Read display statusSLPIN = const(0x10) # Enter sleep modeSLPOUT = const(0x11) # Exit sleep modePTLON = const(0x12) # Partial mode onNORON = const(0x13) # Normal display mode onRDMODE = const(0x0A) # Read display power modeRDMADCTL = const(0x0B) # Read display MADCTLRDPIXFMT = const(0x0C) # Read display pixel formatRDIMGFMT = const(0x0D) # Read display image formatRDSELFDIAG = const(0x0F) # Read display self-diagnosticINVOFF = const(0x20) # Display inversion offINVON = const(0x21) # Display inversion onGAMMASET = const(0x26) # Gamma setDISPLAY_OFF = const(0x28) # Display offDISPLAY_ON = const(0x29) # Display onSET_COLUMN = const(0x2A) # Column address setSET_PAGE = const(0x2B) # Page address setWRITE_RAM = const(0x2C) # Memory writeREAD_RAM = const(0x2E) # Memory readPTLAR = const(0x30) # Partial areaVSCRDEF = const(0x33) # Vertical scrolling definitionMADCTL = const(0x36) # Memory access controlVSCRSADD = const(0x37) # Vertical scrolling start addressPIXFMT = const(0x3A) # COLMOD: Pixel format setWRITE_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 ControlREAD_CABC = const(0x56) # Read Content Adaptive Brightness ControlWRITE_CABC_MINIMUM = const(0x5E) # Write CABC Minimum BrightnessREAD_CABC_MINIMUM = const(0x5F) # Read CABC Minimum BrightnessFRMCTR1 = 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 controlDFUNCTR = const(0xB6) # Display function controlPWCTR1 = const(0xC0) # Power control 1PWCTR2 = const(0xC1) # Power control 2PWCTRA = const(0xCB) # Power control APWCTRB = const(0xCF) # Power control BVMCTR1 = const(0xC5) # VCOM control 1VMCTR2 = const(0xC7) # VCOM control 2RDID1 = const(0xDA) # Read ID 1RDID2 = const(0xDB) # Read ID 2RDID3 = const(0xDC) # Read ID 3RDID4 = const(0xDD) # Read ID 4GMCTRP1 = const(0xE0) # Positive gamma correctionGMCTRN1 = const(0xE1) # Negative gamma correctionDTCA = const(0xE8) # Driver timing control ADTCB = const(0xEA) # Driver timing control BPOSC = const(0xED) # Power on sequence controlENABLE3G = const(0xF2) # Enable 3 gamma controlPUMPRC = const(0xF7) # Pump ratio controlROTATE = {0: 0x88,90: 0xE8,180: 0x48,270: 0x28}def __init__(self, spi, cs, dc, rst,width=240, height=320, rotation=0):"""Initialize OLED.Args:spi (Class Spi): SPI interface for OLEDcs (Class Pin): Chip select pindc (Class Pin): Data/Command pinrst (Class Pin): Reset pinwidth (Optional int): Screen width (default 240)height (Optional int): Screen height (default 320)rotation (Optional int): Rotation must be 0 default, 90. 180 or 270"""self.spi = spiself.cs = csself.dc = dcself.rst = rstself.width = widthself.height = heightif rotation not in self.ROTATE.keys():raise RuntimeError('Rotation must be 0, 90, 180 or 270.')else:self.rotation = self.ROTATE[rotation]# Initialize GPIO pins and set implementation specific methodsif implementation.name == 'circuitpython':self.cs.switch_to_output(value=True)self.dc.switch_to_output(value=False)self.rst.switch_to_output(value=True)self.reset = self.reset_cpyself.write_cmd = self.write_cmd_cpyself.write_data = self.write_data_cpyelse:self.cs.init(self.cs.OUT, value=1)self.dc.init(self.dc.OUT, value=0)self.rst.init(self.rst.OUT, value=1)self.reset = self.reset_mpyself.write_cmd = self.write_cmd_mpyself.write_data = self.write_data_mpyself.reset()# Send initialization commandsself.write_cmd(self.SWRESET) # Software resetsleep(.1)self.write_cmd(self.PWCTRB, 0x00, 0xC1, 0x30) # Pwr ctrl Bself.write_cmd(self.POSC, 0x64, 0x03, 0x12, 0x81) # Pwr on seq. ctrlself.write_cmd(self.DTCA, 0x85, 0x00, 0x78) # Driver timing ctrl Aself.write_cmd(self.PWCTRA, 0x39, 0x2C, 0x00, 0x34, 0x02) # Pwr ctrl Aself.write_cmd(self.PUMPRC, 0x20) # Pump ratio controlself.write_cmd(self.DTCB, 0x00, 0x00) # Driver timing ctrl Bself.write_cmd(self.PWCTR1, 0x23) # Pwr ctrl 1self.write_cmd(self.PWCTR2, 0x10) # Pwr ctrl 2self.write_cmd(self.VMCTR1, 0x3E, 0x28) # VCOM ctrl 1self.write_cmd(self.VMCTR2, 0x86) # VCOM ctrl 2self.write_cmd(self.MADCTL, self.rotation) # Memory access ctrlself.write_cmd(self.VSCRSADD, 0x00) # Vertical scrolling start addressself.write_cmd(self.PIXFMT, 0x55) # COLMOD: Pixel formatself.write_cmd(self.FRMCTR1, 0x00, 0x18) # Frame rate ctrlself.write_cmd(self.DFUNCTR, 0x08, 0x82, 0x27)self.write_cmd(self.ENABLE3G, 0x00) # Enable 3 gamma ctrlself.write_cmd(self.GAMMASET, 0x01) # Gamma curve selectedself.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)self.write_cmd(self.SLPOUT) # Exit sleepsleep(.1)self.write_cmd(self.DISPLAY_ON) # Display onsleep(.1)self.clear()def block(self, x0, y0, x1, y1, data):"""Write a block of data to display.Args:x0 (int): Starting X position.y0 (int): Starting Y position.x1 (int): Ending X position.y1 (int): Ending Y position.data (bytes): Data buffer to write."""self.write_cmd(self.SET_COLUMN, *ustruct.pack(">HH", x0, x1))self.write_cmd(self.SET_PAGE, *ustruct.pack(">HH", y0, y1))self.write_cmd(self.WRITE_RAM)self.write_data(data)def cleanup(self):"""Clean up resources."""self.clear()self.display_off()self.spi.deinit()print('display off')def clear(self, color=0):"""Clear display.Args:color (Optional int): RGB565 color value (Default: 0 = Black)."""w = self.widthh = self.height# Clear display in 1024 byte blocksif color:line = color.to_bytes(2, 'big') * (w * 8)else:line = bytearray(w * 16)for y in range(0, h, 8):self.block(0, y, w - 1, y + 7, line)def display_off(self):"""Turn display off."""self.write_cmd(self.DISPLAY_OFF)def display_on(self):"""Turn display on."""self.write_cmd(self.DISPLAY_ON)def draw_circle(self, x0, y0, r, color):"""Draw a circle.Args:x0 (int): X coordinate of center point.y0 (int): Y coordinate of center point.r (int): Radius.color (int): RGB565 color value."""f = 1 - rdx = 1dy = -r - rx = 0y = rself.draw_pixel(x0, y0 + r, color)self.draw_pixel(x0, y0 - r, color)self.draw_pixel(x0 + r, y0, color)self.draw_pixel(x0 - r, y0, color)while x < y:if f >= 0:y -= 1dy += 2f += dyx += 1dx += 2f += dxself.draw_pixel(x0 + x, y0 + y, color)self.draw_pixel(x0 - x, y0 + y, color)self.draw_pixel(x0 + x, y0 - y, color)self.draw_pixel(x0 - x, y0 - y, color)self.draw_pixel(x0 + y, y0 + x, color)self.draw_pixel(x0 - y, y0 + x, color)self.draw_pixel(x0 + y, y0 - x, color)self.draw_pixel(x0 - y, y0 - x, color)def draw_ellipse(self, x0, y0, a, b, color):"""Draw an ellipse.Args:x0, y0 (int): Coordinates of center point.a (int): Semi axis horizontal.b (int): Semi axis vertical.color (int): RGB565 color value.Note:The center point is the center of the x0,y0 pixel.Since pixels are not divisible, the axes are integer roundedup to complete on a full pixel. Therefore the major andminor axes are increased by 1."""a2 = a * ab2 = b * btwoa2 = a2 + a2twob2 = b2 + b2x = 0y = bpx = 0py = twoa2 * y# Plot initial pointsself.draw_pixel(x0 + x, y0 + y, color)self.draw_pixel(x0 - x, y0 + y, color)self.draw_pixel(x0 + x, y0 - y, color)self.draw_pixel(x0 - x, y0 - y, color)# Region 1p = round(b2 - (a2 * b) + (0.25 * a2))while px < py:x += 1px += twob2if p < 0:p += b2 + pxelse:y -= 1py -= twoa2p += b2 + px - pyself.draw_pixel(x0 + x, y0 + y, color)self.draw_pixel(x0 - x, y0 + y, color)self.draw_pixel(x0 + x, y0 - y, color)self.draw_pixel(x0 - x, y0 - y, color)# Region 2p = round(b2 * (x + 0.5) * (x + 0.5) +a2 * (y - 1) * (y - 1) - a2 * b2)while y > 0:y -= 1py -= twoa2if p > 0:p += a2 - pyelse:x += 1px += twob2p += a2 - py + pxself.draw_pixel(x0 + x, y0 + y, color)self.draw_pixel(x0 - x, y0 + y, color)self.draw_pixel(x0 + x, y0 - y, color)self.draw_pixel(x0 - x, y0 - y, color)def draw_hline(self, x, y, w, color):"""Draw a horizontal line.Args:x (int): Starting X position.y (int): Starting Y position.w (int): Width of line.color (int): RGB565 color value."""if self.is_off_grid(x, y, x + w - 1, y):returnline = color.to_bytes(2, 'big') * wself.block(x, y, x + w - 1, y, line)def draw_image(self, path, x=0, y=0, w=320, h=240):"""Draw image from flash.Args:path (string): Image file path.x (int): X coordinate of image left. Default is 0.y (int): Y coordinate of image top. Default is 0.w (int): Width of image. Default is 320.h (int): Height of image. Default is 240."""x2 = x + w - 1y2 = y + h - 1if self.is_off_grid(x, y, x2, y2):returnwith open(path, "rb") as f:chunk_height = 1024 // wchunk_count, remainder = divmod(h, chunk_height)chunk_size = chunk_height * w * 2chunk_y = yif chunk_count:for c in range(0, chunk_count):buf = f.read(chunk_size)self.block(x, chunk_y,x2, chunk_y + chunk_height - 1,buf)chunk_y += chunk_heightif remainder:buf = f.read(remainder * w * 2)self.block(x, chunk_y,x2, chunk_y + remainder - 1,buf)def draw_letter(self, x, y, letter, font, color, background=0,landscape=False):"""Draw a letter.Args:x (int): Starting X position.y (int): Starting Y position.letter (string): Letter to draw.font (XglcdFont object): Font.color (int): RGB565 color value.background (int): RGB565 background color (default: black).landscape (bool): Orientation (default: False = portrait)"""buf, w, h = font.get_letter(letter, color, background, landscape)# Check for errors (Font could be missing specified letter)if w == 0:return w, hif landscape:y -= wif self.is_off_grid(x, y, x + h - 1, y + w - 1):return 0, 0self.block(x, y,x + h - 1, y + w - 1,buf)else:if self.is_off_grid(x, y, x + w - 1, y + h - 1):return 0, 0self.block(x, y,x + w - 1, y + h - 1,buf)return w, hdef draw_line(self, x1, y1, x2, y2, color):"""Draw a line using Bresenham's algorithm.Args:x1, y1 (int): Starting coordinates of the linex2, y2 (int): Ending coordinates of the linecolor (int): RGB565 color value."""# Check for horizontal lineif y1 == y2:if x1 > x2:x1, x2 = x2, x1self.draw_hline(x1, y1, x2 - x1 + 1, color)return# Check for vertical lineif x1 == x2:if y1 > y2:y1, y2 = y2, y1self.draw_vline(x1, y1, y2 - y1 + 1, color)return# Confirm coordinates in boundaryif self.is_off_grid(min(x1, x2), min(y1, y2),max(x1, x2), max(y1, y2)):return# Changes in x, ydx = x2 - x1dy = y2 - y1# Determine how steep the line isis_steep = abs(dy) > abs(dx)# Rotate lineif is_steep:x1, y1 = y1, x1x2, y2 = y2, x2# Swap start and end points if necessaryif x1 > x2:x1, x2 = x2, x1y1, y2 = y2, y1# Recalculate differentialsdx = x2 - x1dy = y2 - y1# Calculate errorerror = dx >> 1ystep = 1 if y1 < y2 else -1y = y1for x in range(x1, x2 + 1):# Had to reverse HW ????if not is_steep:self.draw_pixel(x, y, color)else:self.draw_pixel(y, x, color)error -= abs(dy)if error < 0:y += ysteperror += dxdef draw_lines(self, coords, color):"""Draw multiple lines.Args:coords ([[int, int],...]): Line coordinate X, Y pairscolor (int): RGB565 color value."""# Starting pointx1, y1 = coords[0]# Iterate through coordinatesfor i in range(1, len(coords)):x2, y2 = coords[i]self.draw_line(x1, y1, x2, y2, color)x1, y1 = x2, y2def draw_pixel(self, x, y, color):"""Draw a single pixel.Args:x (int): X position.y (int): Y position.color (int): RGB565 color value."""if self.is_off_grid(x, y, x, y):returnself.block(x, y, x, y, color.to_bytes(2, 'big'))def draw_polygon(self, sides, x0, y0, r, color, rotate=0):"""Draw an n-sided regular polygon.Args:sides (int): Number of polygon sides.x0, y0 (int): Coordinates of center point.r (int): Radius.color (int): RGB565 color value.rotate (Optional float): Rotation in degrees relative to origin.Note:The center point is the center of the x0,y0 pixel.Since pixels are not divisible, the radius is integer roundedup to complete on a full pixel. Therefore diameter = 2 x r + 1."""coords = []theta = radians(rotate)n = sides + 1for s in range(n):t = 2.0 * pi * s / sides + thetacoords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)])# Cast to python float first to fix rounding errorsself.draw_lines(coords, color=color)def draw_rectangle(self, x, y, w, h, color):"""Draw a rectangle.Args:x (int): Starting X position.y (int): Starting Y position.w (int): Width of rectangle.h (int): Height of rectangle.color (int): RGB565 color value."""x2 = x + w - 1y2 = y + h - 1self.draw_hline(x, y, w, color)self.draw_hline(x, y2, w, color)self.draw_vline(x, y, h, color)self.draw_vline(x2, y, h, color)def draw_sprite(self, buf, x, y, w, h):"""Draw a sprite (optimized for horizontal drawing).Args:buf (bytearray): Buffer to draw.x (int): Starting X position.y (int): Starting Y position.w (int): Width of drawing.h (int): Height of drawing."""x2 = x + w - 1y2 = y + h - 1if self.is_off_grid(x, y, x2, y2):returnself.block(x, y, x2, y2, buf)def draw_text(self, x, y, text, font, color, background=0,landscape=False, spacing=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): RGB565 color value.background (int): RGB565 background color (default: black).landscape (bool): Orientation (default: False = portrait)spacing (int): Pixels between letters (default: 1)"""for letter in text:# Get letter array and letter dimensionsw, h = self.draw_letter(x, y, letter, font, color, background,landscape)# Stop on errorif w == 0 or h == 0:print('Invalid width {0} or height {1}'.format(w, h))returnif landscape:# Fill in spacingif spacing:self.fill_hrect(x, y - w - spacing, h, spacing, background)# Position y for next lettery -= (w + spacing)else:# Fill in spacingif spacing:self.fill_hrect(x + w, y, spacing, h, background)# Position x for next letterx += (w + spacing)# # Fill in spacing# if spacing:# self.fill_vrect(x + w, y, spacing, h, background)# # Position x for next letter# x += w + spacingdef draw_text8x8(self, x, y, text, color, background=0,rotate=0):"""Draw text using built-in MicroPython 8x8 bit font.Args:x (int): Starting X position.y (int): Starting Y position.text (string): Text to draw.color (int): RGB565 color value.background (int): RGB565 background color (default: black).rotate(int): 0, 90, 180, 270"""w = len(text) * 8h = 8# Confirm coordinates in boundaryif self.is_off_grid(x, y, x + 7, y + 7):return# Rearrange colorr = (color & 0xF800) >> 8g = (color & 0x07E0) >> 3b = (color & 0x1F) << 3buf = bytearray(w * 16)fbuf = FrameBuffer(buf, w, h, RGB565)if background != 0:bg_r = (background & 0xF800) >> 8bg_g = (background & 0x07E0) >> 3bg_b = (background & 0x1F) << 3fbuf.fill(color565(bg_b, bg_r, bg_g))fbuf.text(text, 0, 0, color565(b, r, g))if rotate == 0:self.block(x, y, x + w - 1, y + (h - 1), buf)elif rotate == 90:buf2 = bytearray(w * 16)fbuf2 = FrameBuffer(buf2, h, w, RGB565)for y1 in range(h):for x1 in range(w):fbuf2.pixel(y1, x1,fbuf.pixel(x1, (h - 1) - y1))self.block(x, y, x + (h - 1), y + w - 1, buf2)elif rotate == 180:buf2 = bytearray(w * 16)fbuf2 = FrameBuffer(buf2, w, h, RGB565)for y1 in range(h):for x1 in range(w):fbuf2.pixel(x1, y1,fbuf.pixel((w - 1) - x1, (h - 1) - y1))self.block(x, y, x + w - 1, y + (h - 1), buf2)elif rotate == 270:buf2 = bytearray(w * 16)fbuf2 = FrameBuffer(buf2, h, w, RGB565)for y1 in range(h):for x1 in range(w):fbuf2.pixel(y1, x1,fbuf.pixel((w - 1) - x1, y1))self.block(x, y, x + (h - 1), y + w - 1, buf2)def draw_vline(self, x, y, h, color):"""Draw a vertical line.Args:x (int): Starting X position.y (int): Starting Y position.h (int): Height of line.color (int): RGB565 color value."""# Confirm coordinates in boundaryif self.is_off_grid(x, y, x, y + h - 1):returnline = color.to_bytes(2, 'big') * hself.block(x, y, x, y + h - 1, line)def fill_circle(self, x0, y0, r, color):"""Draw a filled circle.Args:x0 (int): X coordinate of center point.y0 (int): Y coordinate of center point.r (int): Radius.color (int): RGB565 color value."""f = 1 - rdx = 1dy = -r - rx = 0y = rself.draw_vline(x0, y0 - r, 2 * r + 1, color)while x < y:if f >= 0:y -= 1dy += 2f += dyx += 1dx += 2f += dxself.draw_vline(x0 + x, y0 - y, 2 * y + 1, color)self.draw_vline(x0 - x, y0 - y, 2 * y + 1, color)self.draw_vline(x0 - y, y0 - x, 2 * x + 1, color)self.draw_vline(x0 + y, y0 - x, 2 * x + 1, color)def fill_ellipse(self, x0, y0, a, b, color):"""Draw a filled ellipse.Args:x0, y0 (int): Coordinates of center point.a (int): Semi axis horizontal.b (int): Semi axis vertical.color (int): RGB565 color value.Note:The center point is the center of the x0,y0 pixel.Since pixels are not divisible, the axes are integer roundedup to complete on a full pixel. Therefore the major andminor axes are increased by 1."""a2 = a * ab2 = b * btwoa2 = a2 + a2twob2 = b2 + b2x = 0y = bpx = 0py = twoa2 * y# Plot initial pointsself.draw_line(x0, y0 - y, x0, y0 + y, color)# Region 1p = round(b2 - (a2 * b) + (0.25 * a2))while px < py:x += 1px += twob2if p < 0:p += b2 + pxelse:y -= 1py -= twoa2p += b2 + px - pyself.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color)self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color)# Region 2p = round(b2 * (x + 0.5) * (x + 0.5) +a2 * (y - 1) * (y - 1) - a2 * b2)while y > 0:y -= 1py -= twoa2if p > 0:p += a2 - pyelse:x += 1px += twob2p += a2 - py + pxself.draw_line(x0 + x, y0 - y, x0 + x, y0 + y, color)self.draw_line(x0 - x, y0 - y, x0 - x, y0 + y, color)def fill_hrect(self, x, y, w, h, color):"""Draw a filled rectangle (optimized for horizontal drawing).Args:x (int): Starting X position.y (int): Starting Y position.w (int): Width of rectangle.h (int): Height of rectangle.color (int): RGB565 color value."""if self.is_off_grid(x, y, x + w - 1, y + h - 1):returnchunk_height = 1024 // wchunk_count, remainder = divmod(h, chunk_height)chunk_size = chunk_height * wchunk_y = yif chunk_count:buf = color.to_bytes(2, 'big') * chunk_sizefor c in range(0, chunk_count):self.block(x, chunk_y,x + w - 1, chunk_y + chunk_height - 1,buf)chunk_y += chunk_heightif remainder:buf = color.to_bytes(2, 'big') * remainder * wself.block(x, chunk_y,x + w - 1, chunk_y + remainder - 1,buf)def fill_rectangle(self, x, y, w, h, color):"""Draw a filled rectangle.Args:x (int): Starting X position.y (int): Starting Y position.w (int): Width of rectangle.h (int): Height of rectangle.color (int): RGB565 color value."""if self.is_off_grid(x, y, x + w - 1, y + h - 1):returnif w > h:self.fill_hrect(x, y, w, h, color)else:self.fill_vrect(x, y, w, h, color)def fill_polygon(self, sides, x0, y0, r, color, rotate=0):"""Draw a filled n-sided regular polygon.Args:sides (int): Number of polygon sides.x0, y0 (int): Coordinates of center point.r (int): Radius.color (int): RGB565 color value.rotate (Optional float): Rotation in degrees relative to origin.Note:The center point is the center of the x0,y0 pixel.Since pixels are not divisible, the radius is integer roundedup to complete on a full pixel. Therefore diameter = 2 x r + 1."""# Determine side coordinatescoords = []theta = radians(rotate)n = sides + 1for s in range(n):t = 2.0 * pi * s / sides + thetacoords.append([int(r * cos(t) + x0), int(r * sin(t) + y0)])# Starting pointx1, y1 = coords[0]# Minimum Maximum X dictxdict = {y1: [x1, x1]}# Iterate through coordinatesfor row in coords[1:]:x2, y2 = rowxprev, yprev = x2, y2# Calculate perimeter# Check for horizontal sideif y1 == y2:if x1 > x2:x1, x2 = x2, x1if y1 in xdict:xdict[y1] = [min(x1, xdict[y1][0]), max(x2, xdict[y1][1])]else:xdict[y1] = [x1, x2]x1, y1 = xprev, yprevcontinue# Non horizontal side# Changes in x, ydx = x2 - x1dy = y2 - y1# Determine how steep the line isis_steep = abs(dy) > abs(dx)# Rotate lineif is_steep:x1, y1 = y1, x1x2, y2 = y2, x2# Swap start and end points if necessaryif x1 > x2:x1, x2 = x2, x1y1, y2 = y2, y1# Recalculate differentialsdx = x2 - x1dy = y2 - y1# Calculate errorerror = dx >> 1ystep = 1 if y1 < y2 else -1y = y1# Calcualte minimum and maximum x valuesfor x in range(x1, x2 + 1):if is_steep:if x in xdict:xdict[x] = [min(y, xdict[x][0]), max(y, xdict[x][1])]else:xdict[x] = [y, y]else:if y in xdict:xdict[y] = [min(x, xdict[y][0]), max(x, xdict[y][1])]else:xdict[y] = [x, x]error -= abs(dy)if error < 0:y += ysteperror += dxx1, y1 = xprev, yprev# Fill polygonfor y, x in xdict.items():self.draw_hline(x[0], y, x[1] - x[0] + 2, color)def fill_vrect(self, x, y, w, h, color):"""Draw a filled rectangle (optimized for vertical drawing).Args:x (int): Starting X position.y (int): Starting Y position.w (int): Width of rectangle.h (int): Height of rectangle.color (int): RGB565 color value."""if self.is_off_grid(x, y, x + w - 1, y + h - 1):returnchunk_width = 1024 // hchunk_count, remainder = divmod(w, chunk_width)chunk_size = chunk_width * hchunk_x = xif chunk_count:buf = color.to_bytes(2, 'big') * chunk_sizefor c in range(0, chunk_count):self.block(chunk_x, y,chunk_x + chunk_width - 1, y + h - 1,buf)chunk_x += chunk_widthif remainder:buf = color.to_bytes(2, 'big') * remainder * hself.block(chunk_x, y,chunk_x + remainder - 1, y + h - 1,buf)def is_off_grid(self, xmin, ymin, xmax, ymax):"""Check if coordinates extend past display boundaries.Args:xmin (int): Minimum horizontal pixel.ymin (int): Minimum vertical pixel.xmax (int): Maximum horizontal pixel.ymax (int): Maximum vertical pixel.Returns:boolean: False = Coordinates OK, True = Error."""if xmin < 0:print('x-coordinate: {0} below minimum of 0.'.format(xmin))return Trueif ymin < 0:print('y-coordinate: {0} below minimum of 0.'.format(ymin))return Trueif xmax >= self.width:print('x-coordinate: {0} above maximum of {1}.'.format(xmax, self.width - 1))return Trueif ymax >= self.height:print('y-coordinate: {0} above maximum of {1}.'.format(ymax, self.height - 1))return Truereturn Falsedef load_sprite(self, path, w, h):"""Load sprite image.Args:path (string): Image file path.w (int): Width of image.h (int): Height of image.Notes:w x h cannot exceed 2048"""buf_size = w * h * 2with open(path, "rb") as f:return f.read(buf_size)def reset_cpy(self):"""Perform reset: Low=initialization, High=normal operation.Notes: CircuitPython implemntation"""self.rst.value = Falsesleep(.05)self.rst.value = Truesleep(.05)def reset_mpy(self):"""Perform reset: Low=initialization, High=normal operation.Notes: MicroPython implemntation"""self.rst(0)sleep(.05)self.rst(1)sleep(.05)def scroll(self, y):"""Scroll display vertically.Args:y (int): Number of pixels to scroll display."""self.write_cmd(self.VSCRSADD, y >> 8, y & 0xFF)def set_scroll(self, top, bottom):"""Set the height of the top and bottom scroll margins.Args:top (int): Height of top scroll marginbottom (int): Height of bottom scroll margin"""if top + bottom <= self.height:middle = self.height - (top + bottom)print(top, middle, bottom)self.write_cmd(self.VSCRDEF,top >> 8,top & 0xFF,middle >> 8,middle & 0xFF,bottom >> 8,bottom & 0xFF)def sleep(self, enable=True):"""Enters or exits sleep mode.Args:enable (bool): True (default)=Enter sleep mode, False=Exit sleep"""if enable:self.write_cmd(self.SLPIN)else:self.write_cmd(self.SLPOUT)def write_cmd_mpy(self, command, *args):"""Write command to OLED (MicroPython).Args:command (byte): ILI9341 command code.*args (optional bytes): Data to transmit."""self.dc(0)self.cs(0)self.spi.write(bytearray([command]))self.cs(1)# Handle any passed dataif len(args) > 0:self.write_data(bytearray(args))def write_cmd_cpy(self, command, *args):"""Write command to OLED (CircuitPython).Args:command (byte): ILI9341 command code.*args (optional bytes): Data to transmit."""self.dc.value = Falseself.cs.value = False# Confirm SPI locked before writingwhile not self.spi.try_lock():passself.spi.write(bytearray([command]))self.spi.unlock()self.cs.value = True# Handle any passed dataif len(args) > 0:self.write_data(bytearray(args))def write_data_mpy(self, data):"""Write data to OLED (MicroPython).Args:data (bytes): Data to transmit."""self.dc(1)self.cs(0)self.spi.write(data)self.cs(1)def write_data_cpy(self, data):"""Write data to OLED (CircuitPython).Args:data (bytes): Data to transmit."""self.dc.value = Trueself.cs.value = False# Confirm SPI locked before writingwhile not self.spi.try_lock():passself.spi.write(data)self.spi.unlock()self.cs.value = True
lib/xpt2046.py
"""XPT2046 Touch module."""
from time import sleepclass Touch(object):"""Serial interface for XPT2046 Touch Screen Controller."""# Command constants from ILI9341 datasheetGET_X = const(0b11010000) # X positionGET_Y = const(0b10010000) # Y positionGET_Z1 = const(0b10110000) # Z1 positionGET_Z2 = const(0b11000000) # Z2 positionGET_TEMP0 = const(0b10000000) # Temperature 0GET_TEMP1 = const(0b11110000) # Temperature 1GET_BATTERY = const(0b10100000) # Battery monitorGET_AUX = const(0b11100000) # Auxiliary input to ADCdef __init__(self, spi, cs, int_pin=None, int_handler=None,width=240, height=320,x_min=100, x_max=1962, y_min=100, y_max=1900):"""Initialize touch screen controller.Args:spi (Class Spi): SPI interface for OLEDcs (Class Pin): Chip select pinint_pin (Class Pin): Touch controller interrupt pinint_handler (function): Handler for screen interruptwidth (int): Width of LCD screenheight (int): Height of LCD screenx_min (int): Minimum x coordinatex_max (int): Maximum x coordinatey_min (int): Minimum Y coordinatey_max (int): Maximum Y coordinate"""self.spi = spiself.cs = csself.cs.init(self.cs.OUT, value=1)self.rx_buf = bytearray(3) # Receive bufferself.tx_buf = bytearray(3) # Transmit bufferself.width = widthself.height = height# Set calibrationself.x_min = x_minself.x_max = x_maxself.y_min = y_minself.y_max = y_maxself.x_multiplier = width / (x_max - x_min)self.x_add = x_min * -self.x_multiplierself.y_multiplier = height / (y_max - y_min)self.y_add = y_min * -self.y_multiplierif int_pin is not None:self.int_pin = int_pinself.int_pin.init(int_pin.IN)self.int_handler = int_handlerself.int_locked = Falseint_pin.irq(trigger=int_pin.IRQ_FALLING | int_pin.IRQ_RISING,handler=self.int_press)def get_touch(self):"""Take multiple samples to get accurate touch reading."""timeout = 2 # set timeout to 2 secondsconfidence = 5buff = [[0, 0] for x in range(confidence)]buf_length = confidence # Require a confidence of 5 good samplesbuffptr = 0 # Track current buffer positionnsamples = 0 # Count sampleswhile timeout > 0:if nsamples == buf_length:meanx = sum([c[0] for c in buff]) // buf_lengthmeany = sum([c[1] for c in buff]) // buf_lengthdev = sum([(c[0] - meanx)**2 +(c[1] - meany)**2 for c in buff]) / buf_lengthif dev <= 50: # Deviation should be under margin of 50return self.normalize(meanx, meany)# get a new valuesample = self.raw_touch() # get a touchif sample is None:nsamples = 0 # Invalidate buffelse:buff[buffptr] = sample # put in buffbuffptr = (buffptr + 1) % buf_length # Incr, until rollovernsamples = min(nsamples + 1, buf_length) # Incr. until maxsleep(.05)timeout -= .05return Nonedef int_press(self, pin):"""Send X,Y values to passed interrupt handler."""if not pin.value() and not self.int_locked:self.int_locked = True # Lock Interruptbuff = self.raw_touch()if buff is not None:x, y = self.normalize(*buff)self.int_handler(x, y)sleep(.1) # Debounce falling edgeelif pin.value() and self.int_locked:sleep(.1) # Debounce rising edgeself.int_locked = False # Unlock interruptdef normalize(self, x, y):"""Normalize mean X,Y values to match LCD screen."""x = int(self.x_multiplier * x + self.x_add)y = int(self.y_multiplier * y + self.y_add)return x, ydef raw_touch(self):"""Read raw X,Y touch values.Returns:tuple(int, int): X, Y"""x = self.send_command(self.GET_X)y = self.send_command(self.GET_Y)if self.x_min <= x <= self.x_max and self.y_min <= y <= self.y_max:return (x, y)else:return Nonedef send_command(self, command):"""Write command to XT2046 (MicroPython).Args:command (byte): XT2046 command code.Returns:int: 12 bit response"""self.tx_buf[0] = commandself.cs(0)self.spi.write_readinto(self.tx_buf, self.rx_buf)self.cs(1)return (self.rx_buf[1] << 4) | (self.rx_buf[2] >> 4)
core/screen/TLedCfg.py
class TLedConfig:def __init__(self) -> None:self.CS = 15 # 片选, 低电平使能self.RESET = 2 # 低电平复位self.DC = 21 # 液晶屏寄存器/数据选择信号,0:寄存器,1:数据self.SDI = 23 # MOSI 写self.SCK = 18 # 时钟self.LED = 4 # 背光控制,高电平点亮self.SDO = 19 # MISO 读self.T_CLK = 14 # 触摸时钟self.T_CS = 27 # 片选,低电平使能self.T_DIN = 13 # 总线输入,接MOSIself.T_DO = 12 # 总线输出,接MISOself.T_IRQ = 33 # 中断,检测到触摸时为低电平
core/screen/test.py
from lib.ili9341 import Display, color565
from lib.xpt2046 import Touch
from machine import Pin, SPI, idle
from core.screen.TLedCfg import TLedConfig
from time import sleepdef ScreenTest():ledCfg = TLedConfig()power = Pin(ledCfg.LED, Pin.OUT)power.value(1)spi = SPI(2, baudrate=40000000, sck=Pin(ledCfg.SCK), mosi=Pin(ledCfg.SDI))display = Display(spi, dc=Pin(ledCfg.DC), cs=Pin(ledCfg.CS), rst=Pin(ledCfg.RESET))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()class Demo(object):"""Touchscreen simple demo."""CYAN = color565(0, 255, 255)PURPLE = color565(255, 0, 255)WHITE = color565(255, 255, 255)def __init__(self, display, spi2):"""Initialize box.Args:display (ILI9341): display objectspi2 (SPI): SPI bus"""ledCfg = TLedConfig()self.display = displayself.touch = Touch(spi2, cs=Pin(ledCfg.T_CS), int_pin=Pin(ledCfg.T_IRQ),int_handler=self.touchscreen_press)# Display initial messageself.display.draw_text8x8(self.display.width // 2 - 32,self.display.height - 9,"TOUCH ME",self.WHITE,background=self.PURPLE)# A small 5x5 sprite for the dotself.dot = bytearray(b'\x00\x00\x07\xE0\xF8\x00\x07\xE0\x00\x00\x07\xE0\xF8\x00\xF8\x00\xF8\x00\x07\xE0\xF8\x00\xF8\x00\xF8\x00\xF8\x00\xF8\x00\x07\xE0\xF8\x00\xF8\x00\xF8\x00\x07\xE0\x00\x00\x07\xE0\xF8\x00\x07\xE0\x00\x00')def touchscreen_press(self, x, y):"""Process touchscreen press events."""print('touch once ... ...')# Y needs to be flipped# y = (self.display.height - 1) - y# Display coordinatesself.display.draw_text8x8(self.display.width // 2 - 32,self.display.height - 9,"{0:03d}, {1:03d}".format(x, y),self.CYAN)# Draw dotself.display.draw_sprite(self.dot, x ,y, 5, 5)def TouchTest():"""Test code."""ledCfg = TLedConfig()power = Pin(ledCfg.LED, Pin.OUT)power.value(1)spi1 = SPI(2, baudrate=32000000, sck=Pin(ledCfg.SCK), mosi=Pin(ledCfg.SDI))spi2 = SPI(1, baudrate=1000000, sck=Pin(ledCfg.T_CLK), mosi=Pin(ledCfg.T_DIN))display = Display(spi1, dc=Pin(ledCfg.DC), cs=Pin(ledCfg.CS), rst=Pin(ledCfg.RESET))# display = Display(spi1, dc=Pin(4), cs=Pin(16), rst=Pin(17))# spi2 = SPI(2, baudrate=1000000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))Demo(display, spi2)try:while True:idle()except KeyboardInterrupt:print("\nCtrl-C pressed. Cleaning up and exiting...")finally:display.cleanup()
main.py
from core.WifiManager import WifiManager
from core.screen.TLedCfg import TLedConfig
from core.screen.test import ScreenTest,TouchTestdef main():print("Welcome to MicroPython!")# wifi = WifiManager()# wifi.start_ap("esp32","12345678")if __name__ == '__main__':main()ScreenTest()# TouchTest()
main函数里,测试哪个就把另一个注释就好。
附录:
驱动和参考主要来自如下:https://github.com/rdagger/micropython-ili9341
LVGL:https://github.com/lvgl/lvgl/tree/b66512183ab0ba3b36e8175504fa6fe9cd6e5312
ESP32引脚参考大全ESP32 – GPIO 引脚参考大全 – 凌顺实验室 (lingshunlab.com)
【micropython】SPI触摸屏开发相关推荐
- 触摸屏开发_Microchip推出新型电容触摸式控制器,加速汽车触摸屏EMI认证
为解决汽车触摸屏开发人员面临的电磁干扰(EMI)和电磁兼容性(EMC)问题,Microchip Technology Inc.(美国微芯科技公司)宣布推出三款全新的maXTouch® 触摸屏控制器和附 ...
- MicroPython支持的开发板:高性能、低成本创客首选
Python的开放.简洁.黏合正符合了现发展阶段对大数据分析.可视化.各种平台程序协作产生了快速的促进作用.自Python3的发布到现在已有五六年的时间,从刚发布的反对声音到慢慢被接受与喜欢经过了太漫 ...
- 探索MicroPython(二)--搭建开发环境
搭建开发环境 1. 简述 2. 安装Python3 3. 安装Mu 4. 安装驱动 5. 更新固件 6. 运行程序 1. 简述 pyBoard上使用了STM32自身flash的一定空间作为U盘与电脑进 ...
- 咸鱼Micropython—SPI
咸鱼Micropython-SPI SPI是Serial peripheral interface(串行外设接口)的简称,它也是一个通用的串行通信接口.无论从硬件还是软件上看,SPI和I2C都很类 似 ...
- 成都工控开发:太简单了!串口触摸屏开发HMI的全流程介绍
成都工控开发:太简单了!串口触摸屏开发HMI的全流程介绍 有很多事,把它说清楚了,就简单了,比如用触摸屏开发HMI. 触摸屏是很常见的,比如说你用的智能手机,用手指在上面划划划就能显示出我们想要的页面 ...
- 太简单了!串口触摸屏开发HMI的全流程介绍
有很多事,把它说清楚了,就简单了,比如用触摸屏开发HMI. 触摸屏是很常见的,比如说你用的智能手机,用手指在上面划划划就能显示出我们想要的页面,比如说早些年很流行的画画板,用触摸笔在上面画一画就能画出 ...
- 太简单了!串口触摸屏开发HMI的全流程介绍成都工控开发
有很多事,把它说清楚了,就简单了,比如用触摸屏开发HMI. 触摸屏是很常见的,比如说你用的智能手机,用手指在上面划划划就能显示出我们想要的页面,比如说早些年很流行的画画板,用触摸笔在上面画一画就能画出 ...
- MTK SPI驱动开发
MTK SPI驱动开发 1.由于SPI驱动 MTK封装的比较好,所以比较好处理. 唯一需要注意的是spi使用的全局Buffer必须要放在内存地址为"NONCACHEDZI"的区域直 ...
- MicroPython做嵌入式开发的优缺点
关注+星标公众号,不错过精彩内容 编排 | strongerHuang 微信公众号 | 嵌入式专栏 Python的火热程度相信大家都看到了,自然,MCU和嵌入式也紧跟"潮流",随之 ...
最新文章
- Struts2 的stream result用法
- 安装和使用花生壳(linux)
- 制作FAT12软盘以查看软盘的根目录条目+文件属性+文件内容
- git学习(8):windows系统下VI编辑器的基本使用
- LeetCode 2212. 射箭比赛中的最大得分(状态枚举)
- 多个无线 AP 怎么实现无缝漫游?
- 微信小程序——点赞和取消点赞功能的实现
- Excel公式向导,详细演艺多条件求平均值的操作
- 【视频分享】尚硅谷Java视频教程_Spring Boot视频教程(下)整合篇
- python刷题大杂烩
- 小红帽系统搭建dns服务器,红帽7(centos 7 )配置DNS服务器
- 如何快速生成2000万行数据
- 1198 今天星期几
- 【星辰傀儡线·命运环·卷一 血鸦】 6 四大长老
- MATLAB做回归分析
- 中信银行柜员岗面试总结
- TCP/IP五层模型简介
- RAW格式照片编辑工具分享,拥有无可替代的重要性
- 一开机鼠标锁定计算机图标,电脑鼠标可以移动无法点击桌面图标怎么回事
- onUnload事件
热门文章
- 在 Solaris 系统上安装 PHP
- 将文件大小数值转换成B、KB、MB、GB
- 数据库概念 第六版 中英文 带目录+习题答案
- 人脸识别Face Recognition综述
- 学了 C 语言到底能做什么, 能从事什么工作?
- android适配手机与平板,关于平板适配问题
- MyIE2变Maxthon就来了(转)
- java计算机毕业设计学生日常事务管理系统源码+mysql数据库+lw文档+系统+调试部署
- matlab norm函数使用_matlab中norm函数的用法
- pcm系统设计及matlab仿真实现,PCM系统设计及MATLAB仿真实现