How to clear 0.96 LCD breakout?

I’ve been having some fun with the 0.96" SPI Color LCD (160x80) Breakout. Mostly running modified Enviro and Enviro+ code on them.

One thing I don’t know how to do is clear it so its blank. There are times I’d like to have the Pi shutdown, but not unplug the power supply. I can stop my running python program no problem but what ever image is on the display just freezes and it stays lit up.

I’d like it to revert to how it is when the Pi first boots up dark and blank.
Turning the backlight off via software isn’t going to work. As soon as the Pi shuts down the backlight GPIO pin will float and the backlight will turn back on.

I have to plead guilty to knowing what the blocks of code for the display do, I just have no idea how they do what they do. ;)

It has been a while since I poked at these screens, but I think what I did was just display a black rectangle on them. So, you’d have code like this:

blank_image = Image.new("RGB", (160,80), (0,0,0))
display.show(blank_image)

I’ve heard tell of a way to set the state of GPIO pins on shutdown but the documentation is scarce and seems a bit confused with using pins to shut down the Pi. The relevant documentation is apparently:

Name:   gpio-poweroff
Info:   Drives a GPIO high or low on poweroff (including halt). Enabling this
        overlay will prevent the ability to boot by driving GPIO3 low.
Load:   dtoverlay=gpio-poweroff,<param>=<val>
Params: gpiopin                 GPIO for signalling (default 26)

        active_low              Set if the power control device requires a
                                high->low transition to trigger a power-down.
                                Note that this will require the support of a
                                custom dt-blob.bin to prevent a power-down
                                during the boot process, and that a reboot
                                will also cause the pin to go low.
        input                   Set if the gpio pin should be configured as
                                an input.
        export                  Set to export the configured pin to sysfs
        timeout_ms              Specify (in ms) how long the kernel waits for
                                power-down before issuing a WARN (default 3000).

So you’d chuck that into your config.txt file. I’ve not experimented with it though.

Thanks @Shoe. One drawback to the gpio poweroff pin state set option is grounding GPIO 3 won’t boot your Pi back up. You have to ground reset or global enable, or power cycle it. I tried that in the past with the fan shim to turn the fan off on shutdown. It worked but the fan shim button wouldn’t boot the Pi back up.

I should have a chance to test that code today or maybe tomorrow. I may do it tomorrow. We have a storm forecast for today and I’m likely just going to leave it running all day and not mess with it.

Ah, that’s a shame, it’s probably the most power-efficient way to turn it off. If you want a hand with image library stuff I’m happy to help out, I’ve spent a reasonable amount of time poking at it.

My current file is a wall of code. I’m sure it has bloat and redundant code, “but” it works. =)
I tried to remove the mode stuff but always got an error. And for the most part I know what a particular block of code does, I just have no idea how it does it. ;)

#!/usr/bin/env python3

import os
import sys
import time
import colorsys
import logging
import ST7735
import RPi.GPIO as GPIO

from sys import exit
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from subprocess import PIPE, Popen
from fonts.ttf import RobotoMedium as UserFont
from bme280 import BME280

def Shutdown(channel):  
    os.system("sudo shutdown now -P")
    time.sleep(30)

GPIO.setmode(GPIO.BCM)  
GPIO.setwarnings(False)
GPIO.setup(24, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.add_event_detect(24, GPIO.FALLING, callback = Shutdown, bouncetime = 2000)

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)-8s %(message)s',
    level=logging.INFO,
    datefmt='%Y-%m-%d %H:%M:%S')

logging.info("""Displays readings from BME280 sensor
Press Ctrl+C to exit!
""")

bme280 = BME280()

disp0 = ST7735.ST7735(
    port=1,
    cs=0, 
    dc=19,
#    backlight=5, 
    rotation=90,
    spi_speed_hz=10000000
)

disp1 = ST7735.ST7735(
    port=1,
    cs=1, 
    dc=19,
 #   backlight=6, 
    rotation=90,
    spi_speed_hz=10000000
)

disp2 = ST7735.ST7735(
    port=1,
    cs=2, 
    dc=19,
  #  backlight=13, 
    rotation=90,
    spi_speed_hz=10000000
)

WIDTH = 160
HEIGHT = 80

disp0.begin()
WIDTH0 = disp0.width
HEIGHT0 = disp0.height

disp1.begin()
WIDTH1 = disp1.width
HEIGHT1 = disp1.height

disp2.begin()
WIDTH2 = disp2.width
HEIGHT2 = disp2.height

img = Image.new('RGB', (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
path = os.path.dirname(os.path.realpath(__file__))
font_size = 20
font = ImageFont.truetype(UserFont, font_size)

message = ""

top_pos = 25

def display_text0(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH0, HEIGHT0), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT0), (r, g, b))
        line_y = HEIGHT0 - (top_pos + (colours[i] * (HEIGHT0 - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp0.display(img)

def display_text1(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH1, HEIGHT1), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        r, g, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT1), (r, g, b))
        line_y = HEIGHT1 - (top_pos + (colours[i] * (HEIGHT1 - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp1.display(img)

def display_text2(variable, data, unit):
    values[variable] = values[variable][1:] + [data]
    vmin = min(values[variable])
    vmax = max(values[variable])
    colours = [(v - vmin + 1) / (vmax - vmin + 1) for v in values[variable]]
    message = "{}:{:.0f}{}".format(variable[:11],data,unit)
    logging.info(message)
    draw.rectangle((0, 0, WIDTH2, HEIGHT2), (255, 255, 255))
    for i in range(len(colours)):
        colour = (1.0 - colours[i]) * 0.6
        g, r, b = [int(x * 255.0) for x in colorsys.hsv_to_rgb(colour, 1.0, 1.0)]
        draw.rectangle((i, top_pos, i + 1, HEIGHT2), (r, g, b))
        line_y = HEIGHT2 - (top_pos + (colours[i] * (HEIGHT2 - top_pos))) + top_pos
        draw.rectangle((i, line_y, i + 1, line_y + 1), (0, 0, 0))

    draw.text((0, 0), message, font=font, fill=(0, 0, 0))
    disp2.display(img)    

last_page = 0

variables = ["Temperature",
             "Humidity",
             "Pressure"]

values = {}

for v in variables:
    values[v] = [1] * WIDTH

mode = 0
F = 0
D = 0

while True:

    
    if F < 161:
        F = F + 1 
    elif F >= 161:
        time.sleep(9)
        D = D + 1 
            
    if mode == 0:
        mode %= len(variables)
        last_page = time.time()
        unit = "C"
        data = bme280.get_temperature()
        display_text0(variables[mode], data, unit)
        mode = 1

    if mode == 1:
        mode %= len(variables)
        last_page = time.time()
        unit = "%"
        data = bme280.get_humidity()
        display_text1(variables[mode], data, unit)
        
    if D == 0:        
        mode = 2
    elif D > 0 and D < 5:
        mode = 0
    elif D == 5:
        mode = 2
        D = 0
    
    if mode == 2:
        mode %= len(variables)
        last_page = time.time()
        unit = "mb"
        data = bme280.get_pressure()
        display_text2(variables[mode], data, unit)
        mode = 0

:-D As long as it works, that’s the important thing. I try to make a habit of meticulously adding comments to sections, even if I’m not 100% sure what they do, because when I come back to it tomorrow I’ll have lost all notion of where I was and which bits of code might be important for what.

So the mode stuff just looks like it is about changing which sensor is displayed, but I can’t make heads not tails of mode %= len(variables). As far as I can see that mostly just returns the same number?

Yeah, on the Enviro+ waving your hand in front of the light / proximity sensor switches the mode, and changes which graph gets displayed on the one LCD.
I tried to split it up into three separate bits but if always errored out. The proximity stuff is gone from my version. But hey, it works. Basically I update display 0, then display 1 then display 2.
The original sampling rate is so fast that you don’t see any slow changes / trends. I plot the temp and humidity once every 9 seconds. That gets me ~ 13 minutes of graphed data, just enough to see if they are going up or down. Any slower and the graphs get jerky and unsmooth. The pressure gets plotted once every 46 seconds which gets me 2 hours of plots. A 2mb change in one hour is considered a fast change.
The first 160 plots are done with no delay just to get things rolling, that’s what all the time sleep stuff at the end is about.
If I remember correctly, this is what I started with.
enviroplus-python/all-in-one-enviro-mini.py at master · pimoroni/enviroplus-python · GitHub

The first 160 plots are done with no delay just to get things rolling, that’s what all the time sleep stuff at the end is about.

Oooh, that makes so much more sense now! Do you have your Enviro+ indoors? I’d love an outdoor station, but for one thing I’d need to find the fancy housing they have to make weather stations waterproof, and for another, I’d need a discreet way to hang it out of my window seeing as I don’t have a garden…

I don’t actually have an Enviro or Enviro+. It’s just the equivalent in breakouts.

You could just remote mount a BME280, for outside temps etc. Keep the main bit inside on the other side of the wall.

I take stuff like this outside on my deck in the summer, under my gazebo.

I’ll be trying out that blackout code a little latter on today. Storms over now, we got dumped on big time. Heavy wet snow, easily over a foot deep. I went out and shoveled off the front steps so the dog can get out the front door.
I’ll start up the snow blower latter on after my wife gets out of bed. Nice and quite here now. Just watching my neighbors slowly didg out on my Pi cameras.

I got some undefined etc errors and edited it to this.

image = Image.new("RGB", (160,80), (0,0,0))
disp.display(image)

Then I changed my def shutdown to this.

def Shutdown(channel):  
    image = Image.new("RGB", (160,80), (0,0,0))
    disp.display(image)
#    os.system("sudo shutdown now -P")
    time.sleep(30)

No errors but it didn’t work, I saw flash / refresh at the end of the 30 seconds.
And no more shutdown after removing the # on the shutdown line.

If I do this shutdown works

def Shutdown(channel):  
#    image = Image.new("RGB", (160,80), (0,0,0))
#    disp.display(image)
    os.system("sudo shutdown now -P")
    time.sleep(30)

This my breakout garden single display running the weather and light enviro example.