(Nincs még) Itt az idő

Asztali óra készítése Raspberry Pi Pico-val

Egy ideje gondolkodom azon, hogy érdekes projekt lenne összerakni egy asztali órát. Lehetne benne mondjuk stopper, visszaszámlálók, amik tudnak párhuzamosan futni, esetleg hőmérséklet és páratartalom kijelzés, ilyesmik. Persze a sors máshogy akarta, úgyhogy nem fogunk ilyen messzire jutni.

Először is, rendeltem hozzá pár alkatrészt:

Aztán el is kezdődhet a móka... miután úgy egy hónapra megfeledkezünk az egészről.

C/C++ SDK

A problémák már a Debug Probe környékén elkezdtek jelentkezni. Bár kisebb, mintha egy Pico-t alakítottunk volna át, viszont nem tudja árammal ellátni az éppen debug-olt Pico-t, úgyhogy összességében a több kábel miatt nekem egy kicsit csalódás volt.

Függetlenül attól, hogy Picoprobe vagy Debug Probe, mindkét esetben szükség volt egy extra hardverre (amit szerencsére már régebben beszereztem), hogy egyszerre tudjam rákötni a Pico-ra a kijelzőt és a probe-ot is.

Korábban már volt róla szó, hogy a CLion-os megoldás Linux-on egész szépen működött, de Windows-on beletört a bicskám. Ez csak azért probléma, mert Linux-ot a laptopomra telepítettem, az asztali gépen pedig Windows van és kényelmetlen nagyobb fejlesztéseket laptopon csinálni.

Így hát nekifutottam másodszor is a Windows-os változatnak, most WSL2-t használva. A dolgok szépen feltelepültek WSL2-ben a Linux-os leírás alapján, a usbipd-win segítségével meg is lehetett osztani a Pico-t vele, a CLion-ban is van WSL2 támogatás, úgyhogy sikeresen le tudta build-elni a projektet, de az OpenOCD-t nem tudja WSL2-ben futtatni, úgyhogy a debug-olás sajnos nem működött. Van is róla egy ticket, úgyhogy egyszer talán majd ez is megjavul.

Ilyenkor kerülnek a projektek parkolópályára, ahonnan majd évek múlva talán előkerülnek, de ez esetben volt még egy tervem. Van egy alternatív megoldás: MicroPython. Ha el tudjuk engedni a villámgyors C kódot, akkor megírhatjuk az egészet Python-ban is. Egy prototípushoz akár még elég is lehet.

MicroPython

Természetesen az ajánlott (működő) megoldással itt sem voltam elégedett, ami a Thonny nevezetű Python IDE lett volna. Nincs vele különösebb probléma, de ha már van egy PyCharm-om, akkor azt szeretném használni. Szerencsére van hozzá MicroPython plugin, amivel minden probléma nélkül fel tudtam küldeni a kódot a Pico-ra.

A Pico-ra először egy MicroPython-os UF2 fájl kerül. Esetünkben egy olyan, amit az LCD kijelző gyártója szolgáltatott, hogy az LCD-t meghajtó modulokat is el tudjuk érni. A hardver-es rész is lényegesen egyszerűbb lett így. Nincs szükség a Debug Probe-ra, csatlakoztathatjuk a Pico-t közvetlenül a kijelzőre is.

Kódkiegészítés

Nyilván túl egyszerű lett volna az élet, ha minden csak úgy működött volna. Az első probléma a kódkiegészítés volt. A MicroPython plugin-ben van ugyan valamennyi támogatás, de nem volt az igazi. Szerencsére léteznek stub-ok, mint a micropython-rp2-pico_w-stubs, ami részben megoldja a problémát, de a módosított MicroPython miatt bizonyos modulokról természetesen ez a stub sem tud. Szerencsére a gyártó csinált a saját moduljaihoz stub-ot is, de nem találtam belőle hivatalos pip csomagot, úgyhogy csak letöltöttem a ZIP-et és a fájlrendszerből telepítettem.

Szeretném azt mondani, hogy ezek után már minden gördülékenyen ment, de nem. Volt például a GfxPack osztály, aminek nem ismerte fel a PyCharm a display property-jét. Nem vagyok otthon a stub-okban, de ránézésre az jónak tűnt:

class GfxPack:
    # ...

    def __init__(self):
        self.display: PicoGraphics
        self.i2c: PimoroniI2C

Úgyhogy lehet a PyCharm-ban nem stimmel valami. Ha megmódosítottam egy kicsit a stub-ot, akkor már minden rendben volt:

class GfxPack:
    # ...

    display: PicoGraphics
    i2c: PimoroniI2C

    def __init__(self):
        ...

Na de most. Most már biztos minden jó, nem? Nem. Mondjuk úgy, hogy használható. Az importok környékén vannak még furcsaságok, nem ajánl fel importálásra bizonyos modulokat, de ha kézzel beimportálom, akkor felismeri őket.

Mindenesetre tudunk kódot írni, amit aztán le is tudunk futtatni a Pico-n, viszont a PyCharm-on belüli MicroPytho REPL valamilyen oknál fogva nem működik. A serial-ra kiküldött print-eket vagy éppen egy exception stack trace-ét nem látjuk sehol, ami azért nem könnyíti meg az életet. PuTTY segítségével rá tudok csatlakozni és működik is, viszont amíg a PuTTY rá van csatlakozva, addik a PyCharm nem tud kódot küldeni rá, úgyhogy elég kényelmetlen a helyzet. Linux-on biztos ez is működne.

Az óra

Azért a végére csak össze kellene dobni valami óra-szerűt, hogy legyen valami sikerélményünk is. A kinézetet először a számítógépen rajzolgattam meg, kiexportáltam WBMP formátumban (van még valaki, aki emlékszik a WAP-ra?), amit aztán egész egyszerűen be lehetett olvasni és beállítani a kijelzőn, hogy a tényleges hardveren is meg tudjam nézni.

from gfx_pack import GfxPack


gp = GfxPack()
gp.set_backlight(0, 180, 60, 140)

with open('pico-clock.wbm', 'rb') as f:
    buffer = bytearray([b ^ 0xFF for b in f.read()[-1024:]])

gp.display.set_framebuffer(buffer)
gp.display.update()

A WBMP elején egy változó méretű header van, de tudjuk az adat méretét, mivel a kijelző (és így a kép is) 128*64 pixeles, minden bit egy pixel állapotát tárolja, szóval az adat (128/8)*64, azaz 1024 byte. Szerencsére a framebuffer formátuma is pont ilyen, úgyhogy könnyű dolgunk van. A kép eredetileg negatívban jelent meg, úgyhogy a b ^ 0xFF segítségével még invertálni is kellett a biteket.

Itt is jelentkeztek azért kisebb problémák a PyCharm környékén. Nem másolta fel automatikusan a képfájlt a Pico-ra, kellett a fájlon egy jobb klikk és Run 'Flash pico-clock.wbm...' menüpontot nyomni (minden alkalommal, amikor a kép frissült).

Most, hogy megvan a kinézetünk, meg is feledkezhetünk róla és összerakhatunk végre egy prototípust. Az első lépés az lenne, hogy felmegyünk WiFi-re és NTP segítségével szerzünk egy pontos időt:

import network
import ntptime
import time


wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('SSID', 'secret')

while not wlan.isconnected():
    print('WLAN is not ready\n')
    time.sleep(1)

ntptime.host = 'hu.pool.ntp.org'
ntptime.settime()

wlan.disconnect()

Aztán ezt szépen meg is jelenítjük:

from gfx_pack import GfxPack


gp = GfxPack()
gp.set_backlight(0, 0, 0, 40)

while True:
    t = time.localtime()

    gp.display.set_pen(0)
    gp.display.clear()
    gp.display.set_pen(15)

    gp.display.set_font('bitmap6')
    gp.display.text(f'{t[0]}. {t[1]:02}. {t[2]:02}.', 0, 0)

    gp.display.set_font('bitmap14_outline')
    gp.display.text(f'{t[3]:02}:{t[4]:02}', 0, 20)
    if t[5] % 2:
        gp.display.set_pen(15)
    else:
        gp.display.set_pen(0)
    gp.display.text(':', 32, 20)

    gp.display.update()

    time.sleep_ms(30)

Esetleg még a háttérvilágításról gondoskodhatunk, hogy kicsit jobban hasonlítson egy kilencvenes évekbeli Casio karórára.

light_timeout = None
while True:
    if not light_timeout and gp.switch_pressed(SWITCH_E):
        light_timeout = time.time_ns() + 2000000000
        gp.set_backlight(0, 180, 60, 140)

    if light_timeout and light_timeout < time.time_ns():
        light_timeout = None
        gp.set_backlight(0, 0, 0, 40)

    # ...

A végeredmény pedig (némi extra státusz információ hozzáadása után):

Még egy prototípushoz képest is rengeteg dolog hiányzik belőle, de első körben sajnos csak eddig jutott a projekt. Ezen kívül a MicroPython nem tud időzónákat kezelni, úgyhogy el kell költözni egy UTC időzónás országba, hogy használni is tudjuk.

Hogy ezek után mi lesz a sorsa az órának? Vajon a fiók mélyére kerül vagy talán a későbbiekben még találkozhatunk vele? Csak az idő a megmondhatója.

This post is also available in english: The time has (not yet) come

Hozzáfűznél valamit?

Dobj egy emailt a blog kukac deadlime pont hu címre vagy irány a bejegyzéshez tartozó tweet.

Feliratkoznál?

Az RSS feed-et ajánljuk, ha a régi jó dolgokat kedveled, de követheted a blogot Twitteren is.