Skip to content

issue in connecting multiple displays #139

@BOTO145

Description

@BOTO145

I've been planning to make my own desk assistant which can help me in my coding, arranging files and folders, has full access to my mails. [not describing the whole project sorry]. I want to connect two displays to my raspberry pi 4 8gb. An ILI9341 display with touch and an st7735 display.
now for refrence, all the displays are working and no connection issues are there.

the pinout i have been using is this:

Image

now, when I test the ILI93411 screen with the touch on it, it works perfectly. same with the ST7735 without touch. During this individual testing, the pinout is same, sharing some of there pins.

the main problem comes when I test both the screens simultaniously or functioning both the screens in a same python code. the code I have been using is this.

import time
import sys

# ── Graceful import checks ────────────────────────────────────────────────────
def require(pkg, install):
    import importlib
    try:
        return importlib.import_module(pkg)
    except ImportError:
        print(f"[ERROR] Missing '{pkg}'.  Run:  sudo pip3 install {install}")
        sys.exit(1)

require("board",                       "adafruit-blinka")
require("busio",                       "adafruit-blinka")
require("digitalio",                   "adafruit-blinka")
require("adafruit_rgb_display.ili9341","adafruit-circuitpython-rgb-display")
require("PIL",                         "pillow")

import board, busio, digitalio
import adafruit_rgb_display.ili9341 as ili9341
from PIL import Image, ImageDraw, ImageFont
import spidev
import RPi.GPIO as GPIO


# ── PIN CONFIGURATION  (BCM numbering) ───────────────────────────────────────
#  ILI9341 main display
DISP_CS_PIN  = board.CE0    # GPIO  8  – Display Chip Select
DISP_DC_PIN  = board.D25    # GPIO 24  – Data / Command
DISP_RST_PIN = board.D24    # GPIO 25  – Reset
DISP_BL_PIN  = 18           # GPIO 18  – Backlight (BCM int for RPi.GPIO PWM)

#  ST7735 secondary display (128x160) – raw spidev, manual GPIO CS/DC
ST_CS_BCM    = 5     # GPIO  5  – ST7735 Chip Select   (Pin 29)
ST_DC_BCM    = 23    # GPIO 23  – ST7735 Data/Command  (Pin 16)
ST_RST_BCM   = 4     # GPIO  4  – ST7735 Reset         (Pin 7)

#  Touch (XPT2046)
TOUCH_CS_BCM  = 7           # GPIO  7  – CE1  (T_CD / T_CS)
TOUCH_IRQ_BCM = 17          # GPIO 17  – T_IRQ
#  Shared SPI bus: SCK=GPIO11  MOSI=GPIO10  MISO=GPIO9

#  ILI9341 screen dimensions
SCREEN_W = 240
SCREEN_H = 320
ROTATION = 0                # 0 / 90 / 180 / 270

#  ST7735 screen dimensions
ST_W     = 128
ST_H     = 160
ST_ROT   = 0                # 0 / 90 / 180 / 270



# ── RAW ST7735 DRIVER (spidev + manual GPIO) ──────────────────────────────────
# The ILI9341 uses CE0 (GPIO 8) managed by adafruit/busio.
# To prevent ANY glitch on CE0 during ST7735 SPI transactions, we:
#   1. Forcibly hold CE0 HIGH via GPIO before every ST7735 transfer
#   2. Use writebytes2() not xfer2() – xfer2 toggles CS internally on some kernels
#   3. Release CE0 GPIO control after the transfer so busio can use it normally

ILI_CS_BCM = 8   # CE0 – ILI9341 chip select, must stay HIGH during ST7735 writes

class ST7735:
    """
    Minimal ST7735 128x160 driver using raw spidev.
    Manually holds ILI9341 CE0 (GPIO8) HIGH during every transaction
    to prevent bus crosstalk corrupting the main display.
    """

    _SWRESET = 0x01
    _SLPOUT  = 0x11
    _NORON   = 0x13
    _INVOFF  = 0x20
    _DISPON  = 0x29
    _CASET   = 0x2A
    _RASET   = 0x2B
    _RAMWR   = 0x2C
    _MADCTL  = 0x36
    _COLMOD  = 0x3A
    _FRMCTR1 = 0xB1
    _DISSET5 = 0xB6
    _INVCTR  = 0xB4
    _PWCTR1  = 0xC0
    _PWCTR2  = 0xC1
    _PWCTR3  = 0xC2
    _VMCTR1  = 0xC5
    _GMCTRP1 = 0xE0
    _GMCTRN1 = 0xE1

    def __init__(self, width=128, height=160, rotation=0):
        self.width    = width
        self.height   = height
        self.rotation = rotation

        GPIO.setwarnings(False)
        GPIO.setmode(GPIO.BCM)
        # ST7735 pins
        GPIO.setup(ST_CS_BCM,  GPIO.OUT, initial=GPIO.HIGH)
        GPIO.setup(ST_DC_BCM,  GPIO.OUT, initial=GPIO.HIGH)
        GPIO.setup(ST_RST_BCM, GPIO.OUT, initial=GPIO.HIGH)
        # ILI9341 CE0 – we'll momentarily own it to keep it HIGH
        GPIO.setup(ILI_CS_BCM, GPIO.OUT, initial=GPIO.HIGH)

        # Always use SPI bus 0, no auto-CS – we drive ALL CS pins manually
        self._spi = spidev.SpiDev()
        self._spi.open(0, 0)
        self._spi.no_cs        = True    # disable kernel CS toggling entirely
        self._spi.max_speed_hz = 15_000_000
        self._spi.mode         = 0b00
        print("[ST7735]  SPI bus 0 opened in no_cs mode.")

        self._reset()
        self._init_display()
        print("[ST7735]  OK – raw spidev driver ready.")

    # ── CS / DC helpers ───────────────────────────────────────────────────────
    def _st_cs(self, active):
        """Assert (True) or deassert (False) the ST7735 CS pin."""
        GPIO.output(ST_CS_BCM, GPIO.LOW if active else GPIO.HIGH)

    def _ili_cs_hold_high(self):
        """Force ILI9341 CE0 HIGH so it ignores our SPI clock."""
        GPIO.output(ILI_CS_BCM, GPIO.HIGH)

    def _ili_cs_release(self):
        """Return CE0 to input so busio/adafruit can manage it again."""
        GPIO.setup(ILI_CS_BCM, GPIO.IN)   # float – busio will re-drive it

    def _dc(self, data_mode):
        GPIO.output(ST_DC_BCM, GPIO.HIGH if data_mode else GPIO.LOW)

    def _reset(self):
        GPIO.output(ST_RST_BCM, GPIO.LOW);  time.sleep(0.1)
        GPIO.output(ST_RST_BCM, GPIO.HIGH); time.sleep(0.1)

    # ── low-level write ───────────────────────────────────────────────────────
    def _write(self, cmd, data=None):
        """
        Send one command byte then optional data bytes.
        ILI9341 CE0 is held HIGH for the entire transaction.
        """
        self._ili_cs_hold_high()          # lock ILI9341 off the bus

        # command byte
        self._dc(False)
        self._st_cs(True)
        self._spi.writebytes2([cmd])
        self._st_cs(False)

        # data bytes
        if data:
            self._dc(True)
            self._st_cs(True)
            for i in range(0, len(data), 4096):
                self._spi.writebytes2(data[i:i+4096])
            self._st_cs(False)

        self._ili_cs_release()            # hand CE0 back to busio

    # ── init sequence ─────────────────────────────────────────────────────────
    def _init_display(self):
        w = self._write
        w(self._SWRESET);           time.sleep(0.15)
        w(self._SLPOUT);            time.sleep(0.5)
        w(self._FRMCTR1, [0x01, 0x2C, 0x2D])
        w(self._INVCTR,  [0x07])
        w(self._PWCTR1,  [0xA2, 0x02, 0x84])
        w(self._PWCTR2,  [0xC5])
        w(self._PWCTR3,  [0x0A, 0x00])
        w(self._VMCTR1,  [0x8A, 0x2A])
        w(self._COLMOD,  [0x05])          # RGB565
        madctl = {0:0x00, 90:0x60, 180:0xC0, 270:0xA0}.get(self.rotation, 0x00)
        w(self._MADCTL,  [madctl])
        w(self._GMCTRP1, [0x02,0x1C,0x07,0x12,0x37,0x32,0x29,0x2D,
                          0x29,0x25,0x2B,0x39,0x00,0x01,0x03,0x10])
        w(self._GMCTRN1, [0x03,0x1D,0x07,0x06,0x2E,0x2C,0x29,0x2D,
                          0x2E,0x2E,0x37,0x3F,0x00,0x00,0x02,0x10])
        w(self._NORON);             time.sleep(0.01)
        w(self._DISPON);            time.sleep(0.1)

    # ── public API ────────────────────────────────────────────────────────────
    def image(self, img):
        """Push a PIL Image (RGB, width×height) to the display."""
        if img.size != (self.width, self.height):
            img = img.resize((self.width, self.height))
        if img.mode != "RGB":
            img = img.convert("RGB")
        raw = img.tobytes()

        # RGB888 → RGB565
        buf = bytearray(self.width * self.height * 2)
        for i in range(self.width * self.height):
            r, g, b = raw[i*3], raw[i*3+1], raw[i*3+2]
            c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
            buf[i*2]   = (c >> 8) & 0xFF
            buf[i*2+1] =  c       & 0xFF

        # set window then blast pixels
        self._write(self._CASET, [0x00, 0, 0x00, self.width  - 1])
        self._write(self._RASET, [0x00, 0, 0x00, self.height - 1])

        # pixel data is one big write – hold ILI CE0 for the whole thing
        self._ili_cs_hold_high()
        self._dc(True)
        self._st_cs(True)
        for i in range(0, len(buf), 4096):
            self._spi.writebytes2(buf[i:i+4096])
        self._st_cs(False)
        self._ili_cs_release()


def st_canvas():
    """Blank canvas sized for the ST7735."""
    return Image.new("RGB", (ST_W, ST_H), 0)




# ── TOUCH INIT ───────────────────────────────────────────────────────────────
def init_touch():
    try:
        import spidev
        import RPi.GPIO as GPIO
        GPIO.setwarnings(False)
        GPIO.setmode(GPIO.BCM)

        # CE1 (GPIO 7) manually controlled – keeps it HIGH (inactive) at start
        GPIO.setup(TOUCH_CS_BCM,  GPIO.OUT, initial=GPIO.HIGH)
        GPIO.setup(TOUCH_IRQ_BCM, GPIO.IN,  pull_up_down=GPIO.PUD_UP)

        # Open SPI bus 0 with no automatic CS (we drive CE1 manually above)
        t = spidev.SpiDev()
        t.open(0, 0)                  # bus 0 — CE0 pin owned by spidev but
        t.no_cs = True                # we disable auto-CS so GPIO 7 is manual
        t.max_speed_hz = 1_000_000
        t.mode = 0b00
        print("[TOUCH]   OK – XPT2046 SPI touch ready (CE1=GPIO7 manual).")
        return t, GPIO
    except Exception as e:
        print(f"[TOUCH]   SKIP – {e}")
        return None, None



# ── TOUCH CALIBRATION VALUES ─────────────────────────────────────────────────
# Raw ADC values (0-4095) at each screen edge. Run calibrate.py to find yours.
# Defaults below cover most common 2.4" ILI9341 panels but may be slightly off.
# ── XPT2046 CHANNEL COMMANDS ─────────────────────────────────────────────────
# Run touch_diag.py to find the correct commands for YOUR panel.
# Common values:
#   0x90 = X differential 12-bit   (most panels)
#   0xD0 = Y differential 12-bit   (most panels)
#   0xD0 = X on some panels        (swap if axes feel rotated)
#   0xB0 = Y on some panels
TOUCH_X_CMD = 0x90
TOUCH_Y_CMD = 0xD0

# IRQ polarity confirmed correct by touch_diag.py
TOUCH_IRQ_ACTIVE_HIGH = False

# ── TOUCH CALIBRATION VALUES ─────────────────────────────────────────────────
# Derived from actual corner taps on this panel:
#   TOP-LEFT     (20,  20)  raw=(3729,  409)
#   TOP-RIGHT    (220, 20)  raw=(3715, 3487)
#   BOTTOM-LEFT  (20,  300) raw=( 568,  420)
#   BOTTOM-RIGHT (220, 300) raw=( 515, 3448)
#
# 0x90 changes 3729→568 vertically   → it is the Y axis (DECREASING, so flip)
# 0xD0 changes  409→3487 horizontally → it is the X axis (INCREASING, no flip)
# Axes are physically swapped vs display → SWAP_XY = True
#
CAL_X_MIN   = 445    # 0xD0 raw at LEFT   edge
CAL_X_MAX   = 3492   # 0xD0 raw at RIGHT  edge
CAL_Y_MIN   = 606    # 0x90 raw at BOTTOM edge (after flip)
CAL_Y_MAX   = 3615   # 0x90 raw at TOP    edge (after flip)
CAL_FLIP_X  = False
CAL_FLIP_Y  = True
CAL_SWAP_XY = True


def _raw_touch(tspi, GPIO):
    """Read noise-averaged raw ADC from XPT2046 with manual CE1 control."""
    import RPi.GPIO as _GPIO
    def ch(cmd):
        vals = []
        for _ in range(8):
            _GPIO.output(TOUCH_CS_BCM, _GPIO.LOW)   # assert   CE1
            r = tspi.xfer2([cmd, 0x00, 0x00])
            _GPIO.output(TOUCH_CS_BCM, _GPIO.HIGH)  # deassert CE1
            vals.append(((r[1] << 8) | r[2]) >> 3)
        vals.sort()
        return sum(vals[2:6]) // 4                  # average of middle 4
    return ch(TOUCH_X_CMD), ch(TOUCH_Y_CMD)


def read_touch(tspi, GPIO):
    """
    Returns calibrated (px_x, px_y) mapped to screen pixels, or None.
    Applies: noise averaging, axis swap, linear cal mapping, flip, clamp.
    """
    irq = GPIO.input(TOUCH_IRQ_BCM)
    touching = irq if TOUCH_IRQ_ACTIVE_HIGH else not irq
    if not touching:
        return None

    rx, ry = _raw_touch(tspi, GPIO)

    if CAL_SWAP_XY:
        rx, ry = ry, rx

    x = int((rx - CAL_X_MIN) / max(1, CAL_X_MAX - CAL_X_MIN) * SCREEN_W)
    y = int((ry - CAL_Y_MIN) / max(1, CAL_Y_MAX - CAL_Y_MIN) * SCREEN_H)

    if CAL_FLIP_X:
        x = SCREEN_W - 1 - x
    if CAL_FLIP_Y:
        y = SCREEN_H - 1 - y

    return max(0, min(SCREEN_W - 1, x)), max(0, min(SCREEN_H - 1, y))


# ── HELPERS ──────────────────────────────────────────────────────────────────
def canvas():
    return Image.new("RGB", (SCREEN_W, SCREEN_H), 0)

def push(disp, img):
    disp.image(img)

def fnt(size=14, bold=False):
    name = "DejaVuSans-Bold.ttf" if bold else "DejaVuSans.ttf"
    try:
        return ImageFont.truetype(
            f"/usr/share/fonts/truetype/dejavu/{name}", size)
    except Exception:
        return ImageFont.load_default()


# ── TEST 1 – Solid colour fills ───────────────────────────────────────────────
def test_color_fill(disp):
    print("\n[TEST 1] Solid colour fills")
    for name, rgb in [
        ("RED",   (255,   0,   0)),
        ("GREEN", (  0, 255,   0)),
        ("BLUE",  (  0,   0, 255)),
        ("WHITE", (255, 255, 255)),
        ("BLACK", (  0,   0,   0)),
    ]:
        print(f"         {name}")
        img = canvas()
        img.paste(rgb, [0, 0, SCREEN_W, SCREEN_H])
        push(disp, img)
        time.sleep(0.7)
    print("[TEST 1] PASSED")


# ── TEST 2 – RGB gradient ─────────────────────────────────────────────────────
def test_gradient(disp):
    print("\n[TEST 2] RGB gradient")
    img = canvas()
    px  = img.load()
    for y in range(SCREEN_H):
        for x in range(SCREEN_W):
            px[x, y] = (
                int(x / SCREEN_W  * 255),
                int(y / SCREEN_H  * 255),
                128,
            )
    push(disp, img)
    time.sleep(2)
    print("[TEST 2] PASSED")


# ── TEST 3 – Shapes & text ────────────────────────────────────────────────────
def test_shapes_text(disp):
    print("\n[TEST 3] Shapes and text")
    img  = canvas()
    draw = ImageDraw.Draw(img)
    draw.rectangle([0, 0, SCREEN_W, SCREEN_H], fill=(10, 10, 40))

    draw.rectangle([ 10, 10, 100, 60], fill=(200, 50, 50),  outline=(255,255,255))
    draw.rectangle([110, 10, 200, 60], fill=( 50,200, 50),  outline=(255,255,255))
    draw.rectangle([210, 10, 310, 60], fill=( 50, 50,200),  outline=(255,255,255))

    draw.ellipse([10, 80, 110, 180], outline=(255, 200, 0), width=3)

    for i in range(0, SCREEN_W, 20):
        draw.line([(i, 80), (i + 40, 230)], fill=(100, 200, 255), width=1)

    draw.text((10, 190), "ILI9341 Display Test",
              font=fnt(20, bold=True), fill=(255, 255, 100))
    draw.text((10, 215), "Raspberry Pi 4  –  SPI",
              font=fnt(14),           fill=(180, 180, 180))
    push(disp, img)
    time.sleep(3)
    print("[TEST 3] PASSED")


# ── TEST 4 – Scrolling banner ─────────────────────────────────────────────────
def test_scroll(disp):
    print("\n[TEST 4] Scrolling banner (5 s)")
    msg = "  *** ILI9341 OK on Raspberry Pi 4 ***  SPI + XPT2046 Touch Test  "
    f   = fnt(24, bold=True)

    tmp = Image.new("RGB", (1, 1))
    bb  = ImageDraw.Draw(tmp).textbbox((0, 0), msg, font=f)
    tw  = bb[2] - bb[0]

    strip = Image.new("RGB", (tw + SCREEN_W, 40), (0, 0, 60))
    ImageDraw.Draw(strip).text((SCREEN_W, 4), msg, font=f, fill=(0, 220, 255))

    end, offset = time.time() + 5, 0
    while time.time() < end:
        frame = Image.new("RGB", (SCREEN_W, SCREEN_H), (0, 0, 30))
        frame.paste(strip.crop((offset, 0, offset + SCREEN_W, 40)),
                    (0, SCREEN_H // 2 - 20))
        push(disp, frame)
        offset = (offset + 4) % (tw + SCREEN_W)
        time.sleep(0.03)
    print("[TEST 4] PASSED")


# ── TEST 5 – Touch draw ───────────────────────────────────────────────────────
def test_touch_draw(disp, tspi, GPIO):
    if tspi is None:
        print("\n[TEST 5] SKIPPED – touch not available")
        return

    print("\n[TEST 5] Touch draw – draw on screen for 15 s")
    img  = canvas()
    draw = ImageDraw.Draw(img)
    draw.rectangle([0, 0, SCREEN_W, 22], fill=(30, 30, 30))
    draw.text((4, 4), "Touch to draw – 15 s",
              font=fnt(14), fill=(255, 255, 100))
    push(disp, img)

    end, prev, touches = time.time() + 15, None, 0
    while time.time() < end:
        pt = read_touch(tspi, GPIO)
        if pt:
            x, y = pt
            col = (int(255 * x / SCREEN_W), int(255 * y / SCREEN_H), 200)
            if prev:
                draw.line([prev, (x, y)], fill=col, width=3)
            else:
                draw.ellipse([x-3, y-3, x+3, y+3], fill=col)
            push(disp, img)
            prev = (x, y)
            touches += 1
        else:
            prev = None
        time.sleep(0.02)
    print(f"[TEST 5] PASSED – {touches} touch sample(s) detected")


# ── TEST 6 – Backlight PWM ────────────────────────────────────────────────────
def test_backlight(pwm):
    print("\n[TEST 6] Backlight PWM blink")
    if pwm is None:
        print("[TEST 6] SKIPPED – backlight PWM not available")
        return
    try:
        for v in [10, 100, 10, 100, 100]:  # dim → bright → dim → bright → full
            pwm.ChangeDutyCycle(v)
            time.sleep(0.35)
        pwm.ChangeDutyCycle(100)           # restore full brightness
        print("[TEST 6] PASSED")
    except Exception as e:
        print(f"[TEST 6] FAILED – {e}")


# ── ST7735 TEST A – Colour fills ─────────────────────────────────────────────
def test_st_color_fill(st):
    print("\n[ST7735-A] Solid colour fills")
    for name, rgb in [
        ("RED",   (255,   0,   0)),
        ("GREEN", (  0, 255,   0)),
        ("BLUE",  (  0,   0, 255)),
        ("WHITE", (255, 255, 255)),
        ("BLACK", (  0,   0,   0)),
    ]:
        print(f"           {name}")
        img = st_canvas()
        img.paste(rgb, [0, 0, ST_W, ST_H])
        st.image(img)
        time.sleep(0.6)
    print("[ST7735-A] PASSED")


# ── ST7735 TEST B – Gradient ──────────────────────────────────────────────────
def test_st_gradient(st):
    print("\n[ST7735-B] Gradient")
    img = st_canvas()
    px  = img.load()
    for y in range(ST_H):
        for x in range(ST_W):
            px[x, y] = (int(x/ST_W*255), int(y/ST_H*255), 128)
    st.image(img)
    time.sleep(2)
    print("[ST7735-B] PASSED")


# ── ST7735 TEST C – Shapes & text ────────────────────────────────────────────
def test_st_shapes_text(st):
    print("\n[ST7735-C] Shapes and text")
    img  = st_canvas()
    draw = ImageDraw.Draw(img)
    draw.rectangle([0, 0, ST_W, ST_H], fill=(10, 10, 40))
    draw.rectangle([ 4,  4, 60, 30],   fill=(200, 50, 50),  outline=(255,255,255))
    draw.rectangle([64,  4, 124,30],   fill=( 50,200, 50),  outline=(255,255,255))
    draw.ellipse  ([ 4, 38, 60, 94],   outline=(255,200,  0), width=2)
    for i in range(0, ST_W, 16):
        draw.line([(i, 38), (i+20, 110)], fill=(100,200,255), width=1)
    draw.text(( 4, 115), "ST7735",       font=fnt(14, bold=True), fill=(255,255,100))
    draw.text(( 4, 133), "128x160 SPI",  font=fnt(11),            fill=(180,180,180))
    draw.text(( 4, 148), "RPi 4",        font=fnt(11),            fill=(180,180,180))
    st.image(img)
    time.sleep(3)
    print("[ST7735-C] PASSED")


# ── DUAL DISPLAY TEST – both screens simultaneously ───────────────────────────
def test_dual_sync(disp, st):
    """
    Drives both displays simultaneously – ILI9341 shows a countdown timer,
    ST7735 shows a live colour-cycling border. Confirms the shared SPI bus
    handles both devices without corruption.
    """
    print("\n[DUAL]    Simultaneous dual-display test (8 s)")
    f_big  = fnt(48, bold=True)
    f_sml  = fnt(14)
    hues   = [(255,0,0),(255,128,0),(255,255,0),(0,255,0),
              (0,255,255),(0,0,255),(128,0,255),(255,0,128)]

    for i in range(8, 0, -1):
        # ── ILI9341: countdown ──────────────────────────────────────────────
        img  = canvas()
        draw = ImageDraw.Draw(img)
        draw.rectangle([0,0,SCREEN_W,SCREEN_H], fill=(10,10,30))
        draw.text((10, 20),  "ILI9341  320×240",  font=f_sml, fill=(150,150,150))
        draw.text((10, 40),  "Dual SPI bus test", font=f_sml, fill=(150,150,150))

        # big countdown number centred
        num  = str(i)
        bb   = ImageDraw.Draw(Image.new("RGB",(1,1))).textbbox((0,0),num,font=f_big)
        nx   = (SCREEN_W - (bb[2]-bb[0])) // 2
        ny   = (SCREEN_H - (bb[3]-bb[1])) // 2
        draw.text((nx, ny), num, font=f_big, fill=(0, 220, 255))
        draw.text((10, SCREEN_H-24), "ST7735 running →", font=f_sml, fill=(100,255,100))
        disp.image(img)

        # ── ST7735: cycling colour border ───────────────────────────────────
        col  = hues[i % len(hues)]
        simg = st_canvas()
        sdraw= ImageDraw.Draw(simg)
        sdraw.rectangle([0,0,ST_W,ST_H],   fill=(15,15,15))
        sdraw.rectangle([0,0,ST_W-1,ST_H-1], outline=col, width=6)
        sdraw.text(( 8, 10), "ST7735",      font=fnt(16,bold=True), fill=col)
        sdraw.text(( 8, 32), "128×160",     font=fnt(13),           fill=(180,180,180))
        sdraw.text(( 8, 50), "Shared SPI",  font=fnt(13),           fill=(180,180,180))
        sdraw.text(( 8, 70), f"tick {9-i}",  font=fnt(13),          fill=(100,200,255))
        # small colour swatch
        sdraw.rectangle([8, 100, ST_W-8, ST_H-20], fill=col)
        st.image(simg)

        time.sleep(1)

    print("[DUAL]    PASSED – both displays ran without corruption")


# ── SUMMARY SCREEN ────────────────────────────────────────────────────────────
def show_summary(disp, st, results):
    print("\n[SUMMARY] Showing results on both screens...")

    # ── ILI9341 summary ──────────────────────────────────────────────────────
    img  = canvas()
    draw = ImageDraw.Draw(img)
    draw.rectangle([0, 0, SCREEN_W, SCREEN_H], fill=(10, 10, 30))
    draw.text((8, 6), "Test Results",
              font=fnt(18, bold=True), fill=(255, 220, 0))
    y = 34
    for name, status in results:
        col = (80, 255, 80)  if status == "PASS" else \
              (255, 80, 80)  if status == "FAIL" else \
              (180, 180, 80)
        draw.text(( 10, y), name,   font=fnt(13), fill=(200, 200, 200))
        draw.text((265, y), status, font=fnt(13), fill=col)
        y += 22
    draw.text((10, y + 8), "Done!", font=fnt(18, bold=True), fill=(100, 200, 255))
    disp.image(img)

    # ── ST7735 summary ────────────────────────────────────────────────────────
    if st:
        passed = sum(1 for _, s in results if s == "PASS")
        total  = len(results)
        simg   = st_canvas()
        sdraw  = ImageDraw.Draw(simg)
        sdraw.rectangle([0,0,ST_W,ST_H], fill=(10,10,30))
        sdraw.text((4,  4), "Results",   font=fnt(14,bold=True), fill=(255,220,0))
        sdraw.text((4, 24), f"{passed}/{total} PASS",
                   font=fnt(18, bold=True),
                   fill=(80,255,80) if passed==total else (255,180,80))
        sy = 50
        for name, status in results:
            col  = (80,255,80) if status=="PASS" else (255,80,80)
            short = name.split(".")[0].strip()   # just the number
            sdraw.text((4, sy), f"{short} {status}", font=fnt(11), fill=col)
            sy += 14
        sdraw.text((4, ST_H-18), "Done!", font=fnt(13,bold=True), fill=(100,200,255))
        st.image(simg)

    time.sleep(5)


# ── MAIN ──────────────────────────────────────────────────────────────────────
def main():
    print("=" * 50)
    print(" ILI9341 + ST7735 Dual Display Test – RPi 4")
    print("=" * 50)

    # ── ILI9341 via adafruit_rgb_display on its own busio.SPI ─────────────────
    print("[SPI]     Initializing ILI9341 SPI handle...")
    spi_ili = busio.SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO)

    try:
        cs  = digitalio.DigitalInOut(DISP_CS_PIN)
        dc  = digitalio.DigitalInOut(DISP_DC_PIN)
        rst = digitalio.DigitalInOut(DISP_RST_PIN)
        print("[DISPLAY] Creating ILI9341 driver...")
        disp = ili9341.ILI9341(spi_ili, cs=cs, dc=dc, rst=rst,
                               width=SCREEN_W, height=SCREEN_H,
                               rotation=ROTATION, baudrate=40_000_000)
        print("[DISPLAY] OK – ILI9341 ready.")
    except Exception as e:
        print(f"\n[FATAL] ILI9341 init failed: {e}")
        print("  Check wiring – CS=GPIO8  DC=GPIO24  RST=GPIO25")
        sys.exit(1)

    # ── ST7735 via raw spidev driver (no library, no bus contention) ──────────
    st = None
    try:
        st = ST7735(width=ST_W, height=ST_H, rotation=ST_ROT)
    except Exception as e:
        print(f"[ST7735]  WARNING – init failed: {e}")
        print("  Check wiring – CS=GPIO5  DC=GPIO23  RST=GPIO4  (continuing without ST7735)")

    # ── Backlight ON ─────────────────────────────────────────────────────────
    backlight_pwm = None
    try:
        GPIO.setwarnings(False)
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(DISP_BL_PIN, GPIO.OUT)
        backlight_pwm = GPIO.PWM(DISP_BL_PIN, 500)
        backlight_pwm.start(100)
        print("[DISPLAY] Backlight ON.")
    except Exception as e:
        print(f"[DISPLAY] Backlight warning: {e}")

    tspi, GPIO_mod = init_touch()
    results = []

    def run(label, fn, *args):
        try:
            fn(*args)
            results.append((label, "PASS"))
        except Exception as e:
            print(f"[ERROR] {label}: {e}")
            results.append((label, "FAIL"))

    # ── ILI9341 tests ─────────────────────────────────────────────────────────
    run("1. ILI Colour Fill",  test_color_fill,   disp)
    run("2. ILI Gradient",     test_gradient,     disp)
    run("3. ILI Shapes+Text",  test_shapes_text,  disp)
    run("4. ILI Scroll",       test_scroll,       disp)
    run("5. Touch Draw",       test_touch_draw,   disp, tspi, GPIO_mod)
    run("6. Backlight",        test_backlight,    backlight_pwm)

    # ── ST7735 tests ──────────────────────────────────────────────────────────
    if st:
        run("7. ST Colour Fill",  test_st_color_fill,  st)
        run("8. ST Gradient",     test_st_gradient,    st)
        run("9. ST Shapes+Text",  test_st_shapes_text, st)
        run("10. Dual Sync",      test_dual_sync,      disp, st)
    else:
        for label in ["7. ST Colour Fill","8. ST Gradient",
                      "9. ST Shapes+Text","10. Dual Sync"]:
            results.append((label, "SKIP"))

    show_summary(disp, st, results)

    if tspi:          tspi.close()
    if backlight_pwm: backlight_pwm.stop()
    GPIO.cleanup()

    passed  = sum(1 for _, s in results if s == "PASS")
    skipped = sum(1 for _, s in results if s == "SKIP")
    print(f"\n[DONE] {passed}/{len(results)} passed, {skipped} skipped.")


if __name__ == "__main__":
    main()

I have tried making individual SPI objects of both the screens but also didn't work. can someone tell me why is it happening and how can I fix it?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions