Ero sivun ”Pacman ja ledimatriisi” versioiden välillä

Helsinki Hacklabin wikistä
Siirry navigaatioon Siirry hakuun
Ei muokkausyhteenvetoa
Ei muokkausyhteenvetoa
Rivi 60: Rivi 60:
Timerista saatavassa aaltomuodossa ei ole paljoa säätömahdollisuuksia: se on aina 0V ja 5V välillä heiluvaa, 50% pulssisuhteella olevaa kanttiaaltoa. Ainoastaan taajuus on säädettävissä kirjoittamalla timeriin sopiva reload-arvo. Toisaalta, tämä on varsin CPU-ystävällinen tapa generoida ääntä: timer generoi asetettua taajuutta suoraan hardwarella, ainoastaan taajuuden muutokset ja alkamiset/loppumiset vaativat ohjelmalta toimenpiteitä.
Timerista saatavassa aaltomuodossa ei ole paljoa säätömahdollisuuksia: se on aina 0V ja 5V välillä heiluvaa, 50% pulssisuhteella olevaa kanttiaaltoa. Ainoastaan taajuus on säädettävissä kirjoittamalla timeriin sopiva reload-arvo. Toisaalta, tämä on varsin CPU-ystävällinen tapa generoida ääntä: timer generoi asetettua taajuutta suoraan hardwarella, ainoastaan taajuuden muutokset ja alkamiset/loppumiset vaativat ohjelmalta toimenpiteitä.


Kaksi kanttiaaltoa tulevat ulos suoraan kontrollerin nastoista. Tein näiden perään ykisnkertaisen passiivisen mikserin, joka pudottaa myös tasot järkeviksi. Siinä on samalla RC-suodatusta, joka hiukan pyöristää kantteja, jotta ne eivät kuulosta niin teräviltä. Lopputulos viedään volume-säädön kautta LM386:lla tehdylle vahvistimelle, joka syöttää kaiutinta.
Kaksi kanttiaaltoa tulevat ulos suoraan kontrollerin nastoista. Tein näiden perään yksinkertaisen passiivisen mikserin, joka pudottaa myös tasot järkeviksi. Siinä on samalla RC-suodatusta, joka hiukan pyöristää kantteja, jotta ne eivät kuulosta niin teräviltä. Lopputulos viedään volume-säädön kautta LM386:lla tehdylle vahvistimelle, joka syöttää kaiutinta.


== Peliohjain ==
== Peliohjain ==

Versio 8. huhtikuuta 2025 kello 19.36


Pacman yleiskuva.jpg

Kuva projektista

Pacman ja ledimatriisi

Tyyppi: Retropeli
Tekijät: Jari
Aloitettu: Marraskuu 2024
Tila: valmis


Bonkkimatriisien tarina

Pacman matriisit.jpg

Eräänä päivänä Hacklabille ilmaantui iso kasa ledimatriisinäytön moduuleja, joiden yli oli vedetty teippi “BEYOND REPAIR”. Ilmettyä bonkkia matkalla SERiin, tullut siis ihan oikeaan paikkaan. Näytöt aiheuttivat välittömästi suurta kiinnostusta labilaisten keskuudessa, “noitahan pitää opetella ohjaamaan”.

Moduulien alkuperäinen käyttötarkoitus on isojen näyttöseinien kokoaminen erilaisissa tapahtumatuotannoisssa ja vastaavissa. Tässä käytössä niitä kuljetetaan, kootaan ja puretaan jatkuvasti ja siinä sivussa ne saavat myös eri asteista kolhimista osakseen. Kolhuja pyritään korjaamaan, mutta aina osa päätyy korjauskelvottomien kasaan ja menee hävitettäväksi. Yksi tällainen kasa kulkeutui nyt labille.

Yksi näyttömoduuli on kooltaan 25*25 cm ja siinä on 88*88 RGB-lediä, sekä tarvittava ohjauselektroniikka näiden ohjaamiseen, kaikki tiiviissä paketissa. Näytössä on 34-napainen piikkirimaliitin, jonka kautta kulkee sekä käyttösähkö että ohjausignaalit. Sitten puuttuukin enää ymmärrys siitä, minkälaisia ohjaussignaaleja näyttö odottaa saavansa, jotta se suostuisi jotain näyttämäänkin. Kuulostaa reverse engineering -projektilta.

Reversaukseen osallistui useampikin labilainen, itse en ollut siinä mukana, joten en osaa juurikaan kertoa, mitä kaikkia vaiheita siihen sisältyi. Ainakin se, että näyttö avattiin ja piirilevyltä vakoiltiin, mitä piirejä ledien ohjaukseen käytetään. Niiden datalehteä ei tietenkään löytynyt netistä, mutta likimain samanlaisen piirin datalehti löytyi. Tuo sopivasti lisää haastetta, kun tietää, että piiri ja datalehti poikkeaa toisistaan ennalta arvaamattomissa kohdissa.

Haasteista huolimatta lopputuloksena syntyi “reference design” Teensylle ja piirilevy, jonka avulla näyttöön saa kytkettyä useammankin eri ohjaimen, ainakin Teensyn ja Picon. Referenssikoodi löytyy täältä: https://helsinkihacklab.slack.com/archives/C85882Z8X/p1730843039817539

Matriisin ohjausperiaatteet

Näyttö haluaa saada useammankinlaisia pulssituksia kohtalaisen rivakassa tahdissa. Ohjaussignaaleja on kaikkiaan 13:

  • kaksi kelloa: multipleksauskello GCLK ja datan syöttökello DCLK
  • scanlinen valinta: 4 linjaa
  • RGB-data: 6 linjaa
  • latch enable LE

Kuvaa multipleksataan GCLK:n tahdissa, joten sille tulee alarajaksi noin 1 MHz, jotta kuva ei ala värisemään liikaa. DCLK:lle alaraja tulee halutun frame raten kautta: esim. 12,5 fps vaatii n. 770 kHz DLCK:n.

Signaalien välillä on vielä monenlaisia riippuvuuksia, joten niiden generointi ei ole ihan triviaalia. Alkuperäisessä käytössä signaalit generoidaan FPGA:lla, joka on ihan viisas valinta tähän. Mutta riittävän nopealla prosessorillakin sen voi tehdä.

Matriisin ohjaaminen 8051:llä

8051-breakout
Pöytäteline
Sisuskalut

Koska tykkään tehdä asioita 8051:llä, niin heti tuli tietysti mieleen kysymys: “voisiko tuota ohjata 8051:llä?”. Ilmeinen vastaus on “ei onnistu ilman ulkoista hardwarea”. Ja näin se varmasti onkin, jos ajatellaan alkuperäistä 8051:tä ja aika montaa modernimpaakin versiota. Mutta kuin sattuman kautta olin juuri hiljattain tilannut eBaysta pari yksinkertaista kehityslautaa, jossa istuu kiinalaisen STC:n tekemä 8051-pohjainen STC8A8K64D4 (voisiko yhtään hankalampaa tyyppimerkintää keksiä?). En ollut tehnyt niillä vielä mitään, mutta datalehden mukaan vaikutti varsin kyvykkäältä piiriltä.

Hetken pähkäilyn jälkeen alkoi näyttää siltä, että näytön ohjaus voisi olla jopa mahdollista tuolla piirillä. Siinä on CPU:ssa 1-clocker-arkkitehtuuri ja max 45 MHz kellotaajuus, joten vanha 8051-käskykanta saa aikamoista kyytiä. Erityisesti piirissä oleva DMA näytti lupaavalta tekemään asiat tarpeeksi nopeasti. En ole aiemmin nähnyt yhtään 8051:tä, jossa olisi DMA, mutta tässä on. Myös muut I/O-laitteet ovat varsin monipuoliset, esim. 5 timeria ja 4 UARTia. On-chip XRAMmia on 8 kB, flashia 64 kB.

Yksi hankalimmista asioista näytön ohjauksessa on multipleksauskellon (GCLK) generointi: pitää tuottaa tasan 256 pulssia vähintään 1 MHz taajuudella ja toistaa näitä purskeita. Ohjelmallinen generointi tällä taajuudella vie liikaa CPU:ta. Timerilla tuottaminen on mahdollista, mutta timerin pysäyttäminen tasan 256 pulssin jälkeen on vaikeaa.

Päätin sitten väärinkäyttää SPI:tä ja DMA:ta tähän: annetaan DMA:lle tehtäväksi lähettää 32 tavua dataa SPI:n kautta. Tällöin SPI tuottaa 256 pulssia (32*8) SCK-pinnistä ja lopettaa siihen, ja DMA aiheuttaa keskeytyksen, jossa siirto voidaan alustaa uudelleen. SCK viedään suoraan GCLK:iin ja MOSI:a ei viedä mihinkään. DMA-keskeytyksessä tehdään sitten scanline-askellus, LE-pulssitus ym. asiat, jotka pitää tehdä 256 pulssin välein. Kun SPI-kellona käytetään 2,8 MHz, DMA-keskeytyksiä tulee n. 90 µs välein, joka on ihan käsiteltävissä oleva tahti.

Dataa lähetettäessä DCLK:lla ei näytön puolesta ole alarajaa. Kun puhutaan Pacmanin tyyppisestä reaaliaikaisesta pelistä, pitää kuvan tietysti päivittyä riittävän usein, että hahmojen liike ruudulla näyttää sujuvalta; tästä tulee käytännön alaraja. Käytin tässä frame ratea 12,5 fps (80 ms/frame) jolloin dataa pitää lähettää 4,6 Mbit/s. Näyttöä voi ajatella kuutena rinnakkaisena siirtorekisteriketjuna, joihin bit-bängätään dataa bitti kerrallaan. Tästä laskien DCLK:ksi tulee n. 770 kHz. Tässä ei voi käyttää apuna DMA:ta tai muutakaan, kaikki pitää tehdä ohjelmallisesti. Sitten vain optimoidaan koodi niin hyvin, että tuohon pystytään. 45 MHz 1-clocker helpottaa tätä.

Näytön ohjaamisesta saadaan siis hardwaren puolesta hyvin yksinkertainen: mikrokontrollerin pinnit vain suoraan näytön ohjauspinneihin, väliin ei tarvita mitään. Ei edes level-shiftereitä, koska STC8-kontrollerit toimivat 5V:lla. Näyttö haluaa ohjaussignaalit 5V tasoilla, joten esim. Teensyn kanssa on käytettävä shiftereitä.

Äänten toteutus

Pacmanin kaltaisessa pelissä pitää olla myös äänet. Nuo klassiset uikutukset ja muut vinkunat ovat oleellinen osa pelikokemusta. STC8A8K64D4:ssa on jokaisessa viidessä timerissa mahdollisuus ottaa timerin tuottama taajuus ulos I/O-pinnistä. Päätin käyttää tähän kaksi timeria, jolloin saadaan kaksi riippumatonta äänikanavaa.

Timerista saatavassa aaltomuodossa ei ole paljoa säätömahdollisuuksia: se on aina 0V ja 5V välillä heiluvaa, 50% pulssisuhteella olevaa kanttiaaltoa. Ainoastaan taajuus on säädettävissä kirjoittamalla timeriin sopiva reload-arvo. Toisaalta, tämä on varsin CPU-ystävällinen tapa generoida ääntä: timer generoi asetettua taajuutta suoraan hardwarella, ainoastaan taajuuden muutokset ja alkamiset/loppumiset vaativat ohjelmalta toimenpiteitä.

Kaksi kanttiaaltoa tulevat ulos suoraan kontrollerin nastoista. Tein näiden perään yksinkertaisen passiivisen mikserin, joka pudottaa myös tasot järkeviksi. Siinä on samalla RC-suodatusta, joka hiukan pyöristää kantteja, jotta ne eivät kuulosta niin teräviltä. Lopputulos viedään volume-säädön kautta LM386:lla tehdylle vahvistimelle, joka syöttää kaiutinta.

Peliohjain

Pacmanin ohjaimena kuuluisi tietysti olla ihan oikea joystick, mutta kun tämä on puoliksi bonkkiteemainen projekti, otin tähän käyttöön vanhan IR-kaukosäätimen, joita lojuu nurkissa liiankin kanssa. Useissa niissä on kursorinohjaukseen tarkoitetut ristipainikkeet, jotka ovat riittävän hyvä approksimaatio joystickistä. Ohjauksen kökköys on samalla osa pelin haastetta.

IR-kaukosäätimen käytön etu on, että vastaanotto-hardware menee hyvin yksinkertaiseksi: vastaanotin on valmis kolmijalkainen komponentti, jonka lähtö viedään suoraan kontrollerille. I/O-pinnejä kuluu tasan yksi.

Ohjelma

Laitteen ohjelma koostuu neljästä päälohkosta:

  • näytön ohjaus
  • äänten generointi
  • IR-koodien vastaanotto
  • pelilogiikka

Tämä on sinänsä yleiskäyttöinen rakenne, jossa kaikki Pacman-spesifinen on pelilogiikka-lohkossa. Tämän vaihtamalla tästä voisi tehdä muunkin pelin.

Näytön ohjaus

Tämä on tehty Teensy-esimerkkiä mallina käyttäen. Teensy-koodi ei siirry mitenkään suoraan 8051:lle ja SDCC:lle, aika monta asiaa on tehtävä toisin. Mutta periaatteet on otettu sieltä. Ilman tätä mallitoteutusta olisi homma varmaan jäänyt tekemättä, on tuo ohjaus sen verran kiharainen ja epälooginenkin.

Näytön multipleksaus hoituu suurimmaksi osaksi hardwarella, kiitos edellä kuvatun DMA/SPI-viritelmän. CPU-aikaa tarvitaan ainoastaan scanlinen vaihtokeskeytykseen.

Lähes kaiken muun CPU-ajan kuluttaakin sitten pikselidatan lähettäminen näytölle. Jokaisen pikselin jokaista osaväriä kohti pitää lähettää 16 bittiä dataa. Tämä tehdään jokaisessa framessa, eli 12,5 kertaa sekunnissa. Tämä on sen verran massiivista bit-bängäystä, että se vie CPU-ajasta n. 90%, vaikka mennään 45 MHz:lla.

Ylijäävä CPU-aika käytetään sitten kaikkeen muuhun: pelilogiikan ajaminen, äänten generointi ja IR-vastaanotto. Se riittää siihen ihan hyvin, vaikka toisin voisi luulla. Kun meillä on vajaa 10% CPU-ajasta jäljellä, se vastaa noin 4 MHz:llä pyörivää 1-clockeria, joka on sama kuin 48 MHz:lla pyörivä 12-clocker. On siis 3 kertaa nopeampi kuin kurssilaudan 80C51FA 16 MHz:lla!

Äänten generointi

Varsinainen taajuus kummallekin kanavalle tulee kahdesta hardware-timerista. Taajuuksien muuttamista varten tehdään reaaliaikakeksytyksessä 10 ms tick, jonka varassa taajuuksia muutellaan. Taajuutta voidaan siis muuttaa tiheimmillään 10 ms välein. Tämä on ihan riittävä resoluutio, jotta taajuusrampit saadaan kuulostamaan portaattomilta. Kaikki Pacmanin äänitehosteet, myös alkuperäisessä pelissä, perustuvat jonkinlaisiin taajuusramppeihin.

IR-koodien vastaanotto

8051-kurssilla tehtiin NEC-IR-protokollan vastaanottorutiini PCA:lla. STC8:n PCA on riittävän yhteensopiva, joten sama rutiini kelpaa sellaisenaan tähän.

Pelilogiikka

Lohkoista suurin on Pacman-pelilogiikan toteuttava lohko. Näitä löytyy netistä monen tasoisia, ja ajattelin käyttää jotain tällaista. Mutta nopeasti huomasi, että ne jakautuvat kahteen ryhmään:

On pitkälle abstrahoituja koodeja, jotka erottavat pelilogiikan ja näytön ym. hardwaren toisistaan hyvin. Nämä yleensä olettavat alleen tehokkaan prosessorin (PC-tasoisen). Jos tällaista koodia alkaa tarjota 8051:lle, nopeakin CPU tukehtuu abstraktiotasojen alle. Yleensä ne eivät edes ole C:tä vaan esim. JavaScriptiä, jolloin pitäisi tehdä paljon töitä koodin kääntämiseksi C:lle, jotta sen saa käännettyä SDCC:llä.

Sitten on erilaisia hyvin pieniin ympäristöihin tehtyjä koodeja, esim. Arduino, jopa TinyAVR. Näissä taas pelilogiikka ja näytön ohjaus muodostavat hyvin tiiviin paketin, ja esim. näytön resoluution muuttaminen rikkoo koodin monesta kohdasta, ja se pitää kirjoittaa pitkälti uusiksi. Mikään niistä ei ole kirjoitettu valmiiksi 88*88-resoluutiolle, koska sen kokoisia näyttöjä ei ole myynnissä.

Niinpä yksinkertaisimmaksi osoittautui kirjoittaa pelilogiikka kokonaan itse. Alkuperäistä Pacman-peliä on analysoitu todella tarkasti ja netistä löytyy kokonaisia sivustoja, jotka kuvaavat pikkutarkasti pelin toiminnot. Näiden perusteella ei ole mitään ongelmaa kirjoittaa niin tarkkaa kopiota pelistä kuin haluaa. Tätä luin paljon: https://pacman.holenet.info/

Ei ole itsestään selvää, miten kuviot sijoitellaan näytölle. Alkuperäinen peli muodostuu 28*31 lohkosta (tile). Lohkon koon on hyvä olla pariton määrä pikseleitä, jotta lohkon keskipiste osuu pikselille. 3*3-pikselin lohko vaatisi 84*93 pikseliä, eli ei mahdu. 1*1 pikseliä ei ole oikein mielekäs (tällainenkin on tehty, mutta en nyt halunnut sitä: https://hackaday.com/2015/06/01/1-pixel-pacman/).

Päädyin muokkamaan labyrinttia siten, että se on 26*27 lohkoa, jolloin 3*3-pikselin lohkoilla sen koko on 78*81 pikseliä. Tässä jää sopivasti reunoille tilaa score-näytölle, vara-Pacmaneille ym. Ja tietysti ne reunimmaiset pikselirivit ovat muutenkin käyttökiellossa, koska nämähän ovat rikkinäisiä näyttöjä. Pimeät ledit ovat käytännössä aina reunoissa, koska sinne ne kolhut osuvat.

Labyrintin seinämien ja lohkojen suhteesta johtuen spritet (Pacman ja haamut) voivat olla yhden pikselin isompia joka suuntaan kuin lohkot, eli 5*5 pikseliä. Tämä on riittävä koko, että niihin saadaan jo jonkinlainen mielekäs animaatiokin eli Pacmanin haukkailu ja haamujen jalkojen vatvonta.

Pelin toiminnallisuuksia luin edellä linkitetystä Pacman Dossier -dokumentista. Osan toteutin kuvatulla tavalla, osan toisin, joitakin jätin pois. Esim. haamuja liikuttavat algoritmit ovat pääosin samat, mutta niihin on lisätty enemmän satunnaisuutta; Pacmanin nopeus ei riipu siitä, onko reitillä syötäviä pisteitä vai ei; jne. Tämän tasoisia yksityiskohtia on paljon.

Rakenne

Näyttö on irrallisena yksikkönä vähän hankala, siinä ei itsessään ole mitään tukirakenteita, joilla se pysyisi pystyssä. Tähän löysin omien varastojen nurkasta puisen telineen, joka on ilmeisesti tarkoitettu jonkinlaiseksi pieneksi pöydällä pidettäväksi maalaustelineeksi. Se sopi tähän hommaan hyvin. Kiinnitin näytön siihen parin apuriman avulla ja nyt se seisoo tukevasti pöydällä ja siinä on jopa kaltevuussäätö.

Elektroniikan tein pikaisesti Veroboardille, koska se on varsin yksinkertainen. Suurin osa komponenteista liittyy äänipuoleen. Veroboard, kaiutin ja käyttösähköliitin on kiinnitetty pieneen kovalevynpalaan, joka on ruuvattu puutelineeseen kiinni. Veroboard ja näyttö on yhdistetty toisiinsa lyhyellä pätkällä 34-napaista nauhakaapelia. IR-vastaanotin on liimattu telineen etukulmaan.

Kytkentäkaavio ja softa löytyy täältä: https://github.com/JKN0/RocManV1