Adafruit PyPortal Titano - reduced size screen and inverted text

Hi,
I just recently bought the PyPortal Titano to make this project for my uni assignment, based on this link:

But I’ve been having issue with the screen.
Somehow, I only have a 3/4 screen size with the text is upside down and mirrored when I setup of the PyPortal Titano, I have tried to reset and reloading everything several times with no luck. Now I can’t even see anything on the screen or my laptop anymore. I don’t know what else to do at this point and my uni assignment is due next week and I don’t think I can afford to buy the replacement because it’s quiet expensive. Please help. Thank you so much for your help in advance.

Hey Kura,

Sorry to hear you’ve been having trouble with your display! At a glance, I can’t find anyone else with your issue!

Could you post the code you’re using as well as a clear photo of your wiring and setup, and we’ll get right to helping you out!

-James

Hi James,
Thank you for your help.
This is the code that I’m using as per the Adafruit example:

import time
import board
import busio
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi, adafruit_esp32spi_wifimanager
import adafruit_imageload
import displayio
import neopixel
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text.label import Label
from adafruit_io.adafruit_io import IO_MQTT
import adafruit_minimqtt.adafruit_minimqtt as MQTT
from adafruit_pyportal import PyPortal
from adafruit_seesaw.seesaw import Seesaw
from simpleio import map_range

#—| User Config |---------------

How often to poll the soil sensor, in seconds

DELAY_SENSOR = 30

How often to send data to adafruit.io, in minutes

DELAY_PUBLISH = 5

Maximum soil moisture measurement

SOIL_LEVEL_MAX = 500.0

Minimum soil moisture measurement

SOIL_LEVEL_MIN= 350.0

#—| End User Config |---------------

Background image

BACKGROUND = “/images/roots.bmp”

Icons for water level and temperature

ICON_LEVEL = “/images/icon-wetness.bmp”
ICON_TEMP = “/images/icon-temp.bmp”
WATER_COLOR = 0x16549E

Audio files

wav_water_high = “/sounds/water-high.wav”
wav_water_low = “/sounds/water-low.wav”

the current working directory (where this file is)

cwd = ("/"+file).rsplit(’/’, 1)[0]

Get wifi details and more from a secrets.py file

try:
from secrets import secrets
except ImportError:
print(“WiFi secrets are kept in secrets.py, please add them there!”)
raise

Set up i2c bus

i2c_bus = busio.I2C(board.SCL, board.SDA)

Initialize soil sensor (s.s)

ss = Seesaw(i2c_bus, addr=0x36)

PyPortal ESP32 AirLift Pins

esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
wifi = adafruit_esp32spi_wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)

Initialize PyPortal Display

display = board.DISPLAY

WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height

Initialize new PyPortal object

pyportal = PyPortal(esp=esp,
external_spi=spi)

Set backlight level

pyportal.set_backlight(0.5)

Create a new DisplayIO group

splash = displayio.Group(max_size=15)

show splash group

display.show(splash)

Palette for water bitmap

palette = displayio.Palette(2)
palette[0] = 0x000000
palette[1] = WATER_COLOR
palette.make_transparent(0)

Create water bitmap

water_bmp = displayio.Bitmap(display.width, display.height, len(palette))
water = displayio.TileGrid(water_bmp, pixel_shader=palette)
splash.append(water)

print(“drawing background…”)

Load background image

try:
bg_bitmap, bg_palette = adafruit_imageload.load(BACKGROUND,
bitmap=displayio.Bitmap,
palette=displayio.Palette)

Or just use solid color

except (OSError, TypeError):
BACKGROUND = BACKGROUND if isinstance(BACKGROUND, int) else 0x000000
bg_bitmap = displayio.Bitmap(display.width, display.height, 1)
bg_palette = displayio.Palette(1)
bg_palette[0] = BACKGROUND
bg_palette.make_transparent(0)
background = displayio.TileGrid(bg_bitmap, pixel_shader=bg_palette)

Add background to display

splash.append(background)

print(‘loading fonts…’)

Fonts within /fonts/ folder

font = cwd+"/fonts/GothamBlack-50.bdf"
font_small = cwd+"/fonts/GothamBlack-25.bdf"

pylint: disable=syntax-error

data_glyphs = b’0123456789FC-* ’
font = bitmap_font.load_font(font)
font.load_glyphs(data_glyphs)

font_small = bitmap_font.load_font(font_small)
full_glyphs = b’0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: ’
font_small.load_glyphs(full_glyphs)

Label to display Adafruit IO status

label_status = Label(font_small, max_glyphs=20)
label_status.x = 305
label_status.y = 10
splash.append(label_status)

Create a label to display the temperature

label_temp = Label(font, max_glyphs=4)
label_temp.x = 35
label_temp.y = 300
splash.append(label_temp)

Create a label to display the water level

label_level = Label(font, max_glyphs=4)
label_level.x = display.width - 130
label_level.y = 300
splash.append(label_level)

print(‘loading icons…’)

Load temperature icon

icon_tmp_bitmap, icon_palette = adafruit_imageload.load(ICON_TEMP,
bitmap=displayio.Bitmap,
palette=displayio.Palette)
icon_palette.make_transparent(0)
icon_tmp_bitmap = displayio.TileGrid(icon_tmp_bitmap,
pixel_shader=icon_palette,
x=0, y=280)
splash.append(icon_tmp_bitmap)

Load level icon

icon_lvl_bitmap, icon_palette = adafruit_imageload.load(ICON_LEVEL,
bitmap=displayio.Bitmap,
palette=displayio.Palette)
icon_palette.make_transparent(0)
icon_lvl_bitmap = displayio.TileGrid(icon_lvl_bitmap,
pixel_shader=icon_palette,
x=315, y=280)
splash.append(icon_lvl_bitmap)

Connect to WiFi

label_status.text = “Connecting…”
while not esp.is_connected:
try:
wifi.connect()
except RuntimeError as e:
print("could not connect to AP, retrying: ",e)
wifi.reset()
continue
print(“Connected to WiFi!”)

Initialize MQTT interface with the esp interface

MQTT.set_socket(socket, esp)

Initialize a new MQTT Client object

mqtt_client = MQTT.MQTT(broker=“io.adafruit.com”,
username=secrets[“aio_user”],
password=secrets[“aio_key”])

Adafruit IO Callback Methods

pylint: disable=unused-argument

def connected(client):
# Connected function will be called when the client is connected to Adafruit IO.
print(‘Connected to Adafruit IO!’)

def subscribe(client, userdata, topic, granted_qos):
# This method is called when the client subscribes to a new feed.
print(‘Subscribed to {0} with QOS level {1}’.format(topic, granted_qos))

pylint: disable=unused-argument

def disconnected(client):
# Disconnected function will be called if the client disconnects
# from the Adafruit IO MQTT broker.
print(“Disconnected from Adafruit IO!”)

Initialize an Adafruit IO MQTT Client

io = IO_MQTT(mqtt_client)

Connect the callback methods defined above to the Adafruit IO MQTT Client

io.on_connect = connected
io.on_subscribe = subscribe
io.on_disconnect = disconnected

Connect to Adafruit IO

print(“Connecting to Adafruit IO…”)
io.connect()
label_status.text = " "
print(“Connected!”)

fill_val = 0.0
def fill_water(fill_percent):
“”"Fills the background water.
:param float fill_percent: Percentage of the display to fill.

"""
assert fill_percent <= 1.0, "Water fill value may not be > 100%"
# pylint: disable=global-statement
global fill_val

if fill_val > fill_percent:
    for _y in range(int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_val)),
                    int((board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent))):
        for _x in range(1, board.DISPLAY.width-1):
            water_bmp[_x, _y] = 0
else:
    for _y in range(board.DISPLAY.height-1,
                    (board.DISPLAY.height-1) - ((board.DISPLAY.height-1)*fill_percent), -1):
        for _x in range(1, board.DISPLAY.width-1):
            water_bmp[_x, _y] = 1
fill_val = fill_percent

def display_temperature(temp_val, is_celsius=False):
“”"Displays the temperature from the STEMMA soil sensor
on the PyPortal Titano.
:param float temp: Temperature value.
:param bool is_celsius:

"""
if not is_celsius:
    temp_val = (temp_val * 9 / 5) + 32 - 15
    print('Temperature: %0.0fF'%temp_val)
    label_temp.text = '%0.0fF'%temp_val
    return int(temp_val)
else:
    print('Temperature: %0.0fC'%temp_val)
    label_temp.text = '%0.0fC'%temp_val
    return int(temp_val)

initial reference time

initial = time.monotonic()
while True:
# Explicitly pump the message loop
# to keep the connection active
try:
io.loop()
except (ValueError, RuntimeError) as e:
print(“Failed to get data, retrying…\n”, e)
wifi.reset()
continue
now = time.monotonic()

print("reading soil sensor...")
# Read capactive
moisture = ss.moisture_read()
label_level.text = str(moisture)

# Convert into percentage for filling the screen
moisture_percentage = map_range(float(moisture), SOIL_LEVEL_MIN, SOIL_LEVEL_MAX, 0.0, 1.0)

# Read temperature
temp = ss.get_temp()
temp = display_temperature(temp)

# fill display
print("filling disp..")
fill_water(moisture_percentage)
print("disp filled..")

print("temp: " + str(temp) + "  moisture: " + str(moisture))

# Play water level alarms
if moisture <= SOIL_LEVEL_MIN:
    print("Playing low water level warning...")
    pyportal.play_file(wav_water_low)
elif moisture >= SOIL_LEVEL_MAX:
    print("Playing high water level warning...")
    pyportal.play_file(wav_water_high)


if now - initial > (DELAY_PUBLISH * 60):
    try:
        print("Publishing data to Adafruit IO...")
        label_status.text = "Sending to IO..."
        io.publish("moisture", moisture)
        io.publish("temperature", temp)
        print("Published")
        label_status.text = "Data Sent!"

        # reset timer
        initial = now
    except (ValueError, RuntimeError) as e:
        label_status.text = "ERROR!"
        print("Failed to get data, retrying...\n", e)
        wifi.reset()
time.sleep(DELAY_SENSOR)



Attached is the photos of my wiring (only connected to the soil sensor and speaker).

Thank you again for your help in advance.

Hey Kura,

Seems like your display isn’t working at the right width and height, probably set up by the code here:

But I can’t see why this wouldn’t work unless you’re supposed to fill these in with your own values.
I’ll keep digging but pay close attention to the tutorial in this bit and see if there’s anything you’re supposed to change.

One thing is that the Titano has more pixels than the regular PyPortal, so that’d be your first clue as to why it isn’t running at the right resolution.

-James

EDIT: Just found out that it uses a different controller board too, which might explain why it’s inverted etc.

Thanks James.
So you think I should change the width and height for the board display? I tried that by multiplying each with integer number (e.g. 50, 100, etc). but it didn’t make any different.
With the resolution and controller board, how do I update/change that? do you know by any chance?
Thanks again.

Hi Kura,

Since the Adafruit CircuitPython library doesn’t make any distinctions between the smaller and larger display, it should just work. I’ll have a closer look at the code and library and get back to you if I find anything.

-James

Thanks so much and I really appreciate it.

Hey Kura,

Can I just get you to confirm the version of CircuitPython you are using? Apparently the Titano will work on 6.2 (the latest release) so you should check that you’re running that.

-James

Yes that’s the version that I use CircuitPython 6.2.0 from this link:

and I tried to use both UK and US version too to see if it make any difference.

Hey Kura,

Hmm if that’s all fine I’m not sure what to check now, my colleagues and I will keep looking into this and see if we can dig anything up.

Again, It should just work!
-James

Hey Kura,

Welcome to the forum!! :smiley:

I had a dig and I think I may have found a lead!

The following lines store the default width and height of the display and most likely some other values that we might have to change to get it working correctly.

# Initialize PyPortal Display
display = board.DISPLAY
 
WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height

If we can get all of the contents then we might be able to find which attributes might be mirroring the display.
Depending on which environment you are using you’ll need access to a REPL, MU - Adafruits CircuitPython IDE allows access by clicking on the serial button.
Running the command: dir(board) in the REPL will return all of the attributes.
If there is one called mirror or both WIDTH and HEIGHT return negative values we should see if there is an easy way to change those.

Could you please send through the output when running the “dir” command and we can help out from there!

Liam.

Hi Liam, thanks again for your time this morning over the phone. I use Mu and just try your suggestion to run the dir(board) in the REPL and this is the output:

Sorry for silly question here, where am I supposed to type in the dir(board) and should I comment out all the other codes?
image

Also I tried to rotate the display to 180 and it looks upright now (still mirrored and reduced size).


but at least something is changed now :slight_smile:

Also for the CircuitPython 6.2.0, should I use UK or US version? Do I need to have the UF2 Bootloader (Latest version: v3.13.0) also saved in the CIRCUITPY(D:)?
I tried both version and having the UF2 Bootloader and not, still no luck.

Hey Kura,

In some roving over on the Adafruit forum I also came across another Maker with a similar issue: Adafruit customer service forums • View topic - PyPortal Titano lost Board File

I dont think they have come to a resolution just yet but it will be interesting seeing where Carter takes them!

Cheers,
Liam.

Thank you Liam.
I also found another discussion with the similar issue:
https://forums.adafruit.com/viewtopic.php?f=60&t=177637&p=865329&hilit=pyportal+titano#p865329

On the weekend I ended up erasing my PyPortal Titano by using this command:
import storage
storage.erase_filesystem()
and set up from the start again and it worked, the screen size and text display are back to normal and not inverted, yay!!

But now I have another error :frowning:
MemoryError: memory allocation failed, …

even though my RAM is not even 50% the capacity, I’m not sure why??
Would you be able to help again by any chance?

Thank you so much.

3 Likes

Hi Kura,

Awesome that you got your board (sort-of) working correctly!

Just to confirm, can you send through the complete error message, and whether you are uploading a py or mpy file?

-James

2 Likes

Hi James,
I used all mpy file.
Sorry I didn’t screenshot the error message and I can’t see it anymore as it’s been fixed now.
I came across to this Github link when I searched on memory error:

So I follow some of the tips (e.g. reducing the size of the bitmaps, text and fonts that I use).
It’s working now and thanks again for your help.

2 Likes

Hi Kura,

Awesome! I was going to suggest some tips like that but I was thinking that it was a mpy error rather than just too much code/images

Great to see it working, keep us updated once you get the whole thing up and running!
-James

3 Likes

Hi Kura,

Glad to hear you got your board running again!!

The memory allocation would most likely have to do with how some of the lower-level stuff is programmed, I would say 50% is user-accessible and the remaining is kept free for libraries loading and unloading content to keep your board running fast (other programming languages require manual cleaning of memory, C and C++ are great examples of this).

Here’s the documentation I took a look at for this post:

Liam.

3 Likes

Thank you James and Liam for all your help. I really appreciate it :slight_smile:

2 Likes