rm-hull / luma.oled

Python module to drive a SSD1306 / SSD1309 / SSD1322 / SSD1325 / SSD1327 / SSD1331 / SSD1351 / SH1106 OLED
https://luma-oled.readthedocs.io
MIT License
807 stars 161 forks source link

RPi3B multiple SPI SSD1306 displays #388

Closed nickehallgren closed 5 months ago

nickehallgren commented 5 months ago

I would like to start by thanking you for this amazing software (I'm using it for other projects too!)

Type of Raspberry Pi

RPi3B+

Linux Kernel version

Linux icbs5 6.6.20+rpt-rpi-v7 #1 SMP Raspbian 1:6.6.20-1+rpt1 (2024-03-07) armv7l GNU/Linux

Expected behaviour

I would like to use 8 SSD1306 (128x32) displays over SPI, I've tried different approaches based on what I've found online and it feels like I'm close but it doesn't really work as I want. I now have 5 displays connected (all share the same SPI0 pins, except the CS) the CS pins are connected to the displays from left to right, GPIO 16, 26, 19, 13, 12. And I want to show "DISPLAY 1" on the first, "DISPLAY 2" on the second etc etc

Actual behaviour

With the code below I get four of the five connected displays to work but in the wrong order (I get 2, 3, 4, 5 and nothing) but only after a reboot. When I exit the program and restart it I only get text on one random screen (and then I also might get the last display to show "Display 1").

Is there a better way to do this and why does I only get text on multiple displays after a reboot?

import atexit, time
import RPi.GPIO as GPIO
from luma.core.render import canvas
from luma.core.interface.serial import spi
from luma.oled.device import ssd1306

serial = spi(device=0, port=0, bus_speed_hz=8000000)
cs_pins = [16, 26, 19, 13, 12]
GPIO.setmode(GPIO.BCM)
for pin in cs_pins:
  GPIO.setup(pin, GPIO.OUT)
  time.sleep(0.001)

devices = [ssd1306(serial, height=32, rotate=0, cs_pin=cs) for cs in cs_pins]

def cleanup():
  GPIO.cleanup()

atexit.register(cleanup)

for device in devices:
    device.clear()

while True:
  for i, device in enumerate(devices):
    with canvas(device) as draw:
      draw.text((10, 10), f"Display {i + 1}", fill="white")
    time.sleep(0.1)
    for j, pin in enumerate(cs_pins):
      GPIO.output(pin, GPIO.LOW if j == i else GPIO.HIGH)
      time.sleep(0.001)
    time.sleep(0.1)
thijstriemstra commented 5 months ago

Why do you use GPIO.output(pin, GPIO.LOW if j == i else GPIO.HIGH)?

nickehallgren commented 5 months ago

@thijstriemstra Without that all the displays are looping "Display 1", "Display 2" etc all the time (they are all showing the same text)... But without it I get text on the first four displays after a restart of the program...

thijstriemstra commented 5 months ago

I wouldn't 'manually' try to change the gpio pins that luma.oled manages, unless there's a really good reason (e.g bugfix). This sounds to me like it's a coding issue (I want to show X) rather than an issue with luma.oled?

But without it I get text on the first four displays after a restart of the program...

Maybe do the cleear of the screens in the cleanup instead, e.g.

def cleanup():
  for device in devices:
    device.clear()
  GPIO.cleanup()

atexit.register(cleanup)

Or do a cleanup of the CS pins manually instead of using GPIO.cleanup()?

Also: try different/default SPI bus speeds..

nickehallgren commented 5 months ago

I agree, I'm pretty sure the problem is my code. The change to the cleanup fixed the restart issue. Removed the bus_speed but still have same text on all displays (or actually only on four out of five).

I have also experimented with code from here https://github.com/rm-hull/luma.oled/issues/311 but didn't get the gpio_cs_spi to work

All help is greatly appreciated

nickehallgren commented 5 months ago

So I got it to work as I wanted, this how I solved it

import atexit, time
from luma.core.render import canvas
from luma.core.interface.serial import spi, gpio_cs_spi
from luma.oled.device import ssd1306
import RPi.GPIO as GPIO

cs_pins = [16, 26, 19, 13, 12]
serials = [gpio_cs_spi(device=0, port=0, gpio_CS=cs) for cs in cs_pins]
devices = [ssd1306(serials[i], height=32, rotate=0) for i, _ in enumerate(cs_pins)]

def cleanup():
  for device in devices:
    device.clear()
  GPIO.cleanup()

atexit.register(cleanup)

for i, device in enumerate(devices, start=1):
  with canvas(device) as draw:
    draw.rectangle(device.bounding_box, outline="white", fill="black")
    draw.text((10, 10), f"DISPLAY {i}", fill="white")

while True:
  time.sleep(0.1)