soodan sivut

arkisto

237 kirjotelmaa.

avainsanat

Ei sitä näemmä koskaan opi, vaikka viikoittain kiroaa itselleen, että koodin kopiointi tai pasteaminen pitäisi kieltää. "Hienot" bittitemput pitää tehdä vasta kun koodi toimii. Redundanssi on ihan OK jos se selventää koodin lukemista. Isomman pötkön copypastaaminen toiseksi ja refaktorointi sen pohjalta jättää aivan liian helposti jälkeensä sotkua tai bugeja. Tällä kertaa debuggaaminen oli vieläpä aivan erityisen hankalaa, sillä suurin osa koodista tuli kirjoitettua varsin sokeasti testaamatta varsinaisella alustalla. Tein metronäytölle matopeliä, joka toimi pc:llä simuloimalla ihan ok -- mikrokontrollerilla taas ohjattava näyttö sekosi täysin, jos matoa liikutti ja erityisesti jos omenan paikan asetti ohjelman alussa. Uutta koodia oli kokonainen matopeli, framebufferi ja uudenlainen näytönpäivitysrutiini, eli lähes kaikki.

AVR:lle devatessa saa varautua vaikka mihin jekkuihin, kun yhdellä kädellä näpyttää C:tä ja toisella selaa mikrokontrollerin datasheetiä kun niitä kaikkia taikarekistereitä ei muistakaan ulkoa. Sellaista se bare metal -koodaaminen on periaatteessa alustasta riippumatta. Luotettavaa koodia tehdessä huomaakin puljaavansa jotain käyttöjärjestelmäajurien kaltaisia abstraktioita, jotta joka yksityiskohtaa ei tarvitsisi joka vaiheessa pitää mielessä. Käyttiksestä tämä kaikki eroaa silti aivan täysin, sillä mitään järkeviä debuggaustyökaluja, jotka esim. linuxilla ovat itsestäänselvyyksiä (valgrind, gdb, strace, yms.), ei enää olekaan.

Kun rauta sekoaa, eikä käyttäydy kuten koodista luulisi, nopein debugkeino on monesti se perinteinen printf-debuggaus, eli tulostetaan sarjaporttiin eri koodipoluissa erinäisiä tekstejä, joista tulkitaan mitä tapahtuu. Mutta mitä sitten, kun debugprinttifunktion kutsuminen saa softan käyttäytymään eri lailla, ja saadaan heisenbug? Voidaan vaikka välkyttää lediä, jee?

Metronäyttö näyttää outoja väriliukuja, jos siihen ei ole kytketty mitään. Jos pari bittiä tai vaikka puolet unohtuu välistä, ei enää ota selvää mitä siihen aikoi kirjoittaa, vaan siinä näkyy satunnaisen oloisia kolmioita. Sain näitä molempia aikaan, ja toiminnan yksityiskohdat tuntuivat riippuvan kuun asennosta.

Nyt kyseessä olevassa tilanteessa olin niinkin onnellisessa asemassa, että bugitusta sai tutkittua oskilloskoopilla aivan hyvin. Lisäksi olin tehnyt softaan sopivat abstraktiotasot, niin sitä sai testattua varsin pitkälle "emuloimalla" linuxin puolella, ainoana erona ulos väylälle lähtevän bitin tason asettaminen. AVR:llä suoraan porttiin kirjoittamalla, linuxilla printtasin ykkösiä ja nollia stdoutiin. Metronäytölle tuleva matopeli-softaesimerkki päivittelee bittejä framebufferiin, jonka bufferin funktiot tulostelevat SPI-väylälle, mutta SPI-funktiot oikeastaan ovat totetettu toistaiseksi softa-bitbangilla, ja bitit menevät jonnekin lcd-paneelien siirtorekisterien uumeniin, josta ne tökätään lopuksi kerralla näkyville. Kun itse näyttö reagoi kummasti, niin tökätään skooppi väylälle ja katsotaan, mitä käy.

Näytöllä piti rullata matopelin mato nurkasta toiseen ja reagoida näppikseen, mutta vain pari madon pikseliä olivat selkeitä, ja muualla välkkyi kolmioita. Otin madonpiirtorutiinit pois ja täytin ruutua pikseli pikseliltä. Aluksi kaikki oli ok, mutta sitten näyttö alkoi harmaasävyliukuilemaan ihan omiaan. Kun otin alusta pois funktiokutsun, joka alustaa pelin omenan paikan, kaikki näytti taas toimivan. Mistä ihmeestä voisi olla kyse? Vuotaako avr:n pino yli io-muistialueelle, ja vaikka vaihtaa portin suunnan ulostulosta sisääntuloksi? Löytääkö optimoiva kääntäjä koodista jonkun polun, jonka voi jonkun bugini takia poistaa, jolloin sekoilu ilmenee? Jääkö jokin koodi kääntämättä kääntäjän bugin vuoksi? Kirjoitanko jonkun globaalin muuttujan päälle josta vaikutus näkyy vasta myöhemmin? Resetoituuko prossu jostain kumman syystä, niin että osa koodista ei ehdi ajaa?

Ei, ei ja pari eitä lisää. Pääloopissa printtaamalla debugviestejä sarjaporttiin viestit tulevat ihan hyvin perille. Madon sijainti päivittyy oikein. Viivesysteemi ajastaa ajot oikein. Portin tilakaan ei vaihtunut inputiksi ainakaan suuntarekisterin perusteella. Sitten skooppi käteen ja tutkimaan niitä data- ja kellonastoja väylältä.

Nastojen tila näyttää ensi silmäyksellä ihan hyvältä. Eivät ole inputtina kellumassa, kohinaa ei juurikaan näy. Vaihtelen koodia vuorotellen tilaan, jossa näytön saa sujuvasti täyteen mustia pikseleitä, ja tilaan, jossa sen pitäisi olla täynnä mustaa mutta sekoamiset ilmenevät. Molemmat signaalit näyttävät silmin katsottuna identtiseltä. Kaapataanpa bitit skoopilta ja tarkastellaan pc:llä. Nyt alkaa näkyä eroja.

Kuvissa datasignaalin pitäisi olla nollassa täytebittien aikaan ja ykkösessä (5 volttia) kun pikseli on päällä (testissä kaikki ovat). Samassa kuvassa on päällekkäin kaappaus toimivan session sekä bugisen session datasta ja kellosta.

Näytöllehän pusketaan ensin rivin koko yläosan bitit, jonka perään seuraa alaosa. Kun alaosaa aletaan kirjoittaa eli sen kellot alkavat (kuvassa vihreä ja purppura), datan paddingbitit eivät täsmää virheellisessä tilassa odotettuun. Virrassa pitäisi tulla suuri määrä täytebittejä, mutta ne tuntuvat alkavan vasta jälkeenpäin. Yläosankin täytebitit ovat jotenkin epäsynkassa, mutten jaksanut selvittää, onko se yhtenäistä jokaiselle näyttöpäivitykselle, vai vaihteleeko virheoffsetti. Miten kummassa yläosan täytebittivälien näköiset kolot jatkuvat vielä alaosan kellon ajan? Miksi alaosan täytebitit alkavat jäljessä, eivätkä mene loppuun asti? Linuxin puolella testatessa vaikka mitkä grafiikat näkyivät oikein, enkä muistaakseni saanut valgrindilta muistivirheitäkään, enhän? Mutta eivät globaalien muuttujien ylivuodot voi tuhota arvoja, jotka ovat funktioiden lokaaleja ja alustetaan vasta tulevaisuudessa!

Framebufferikoodi ajaa näyttöä lcd-spi-abstraktion/"kirjaston" läpi. Abstraktio ottaa sarakkeen bitit kerralla, ja muodostaa niistä tavuja, joita voi syöttää raudassa olevalle shifterille, tai jopa DMA:lle isompaan puskuriin jos sellainen raudasta löytyy (tavoitteena olisi). Se myös laskee sarakkeen kohtaa, ja sijoittaa puskuriin varsinaisten sarakebittien välille täytebittejä tarpeen mukaan. Jos täytebittejä ei tule tai tulee vähän väärässä järjestyksessä, näyttö selkeästi siis sekoaa täysin.

Ajetaanpa valgrindilla taas muutaman avr-spesifisen muutoksen jälkeen... ja sieltähän se suoraan näkyy; ehdollinen hyppy riippuu arvosta, jota ei ole alustettu vielä missään. Iffirakenteessa vertaillaan yläosan sarakelaskuria maksimiarvoon. Mutta sarakehan alustetaan alustusfunktiossa, jota kutsutaan aina ennen näytön tulostusta. Miten tämä voisi vaikuttaa alaosan offsetattuun dataan, kun se alkaa vasta yläosan bittien jälkeen? Vilkaisu alustusfunktioon saa kaiken naksahtamaan päässä kohdalleen.

Lcd-spi-käkättimen alustusfunktio alustaa sarakelaskurin. Lisäsin toisen sarakelaskurin copypastaillessani abstraktion kontekstistruktia toisesta lcd-käsittelykoodista. Jätin varsinaisen laskurin alustamatta, ja vanhan niminen laskuri alustuu, mutta sitä ei käytetä mihinkään. Aivan liian triviaali virhe jotta tulisi mieleenkään.

Alustin laskurin. Onneksi C on niin porttautuva kieli, että itse paddingbittiveivaamisalgon voi tosiaan testata järkevässä ympäristössä, kun ulostulonastan veivaamisen abstraktoi muualle EIKÄ OPTIMOI SITÄ ENNENAIKAISESTI INLINENÄ MUKAAN.

Laskuri vaikutti vain ulos lähtevään dataan, ja varsinkin täytebittien sijainteihin. Jos laskuri ei ala nollasta, tietysti datassa on offsettiä. Kellosignaalin valinta ylä- tai alapuolelle tulee erikseen, ja se vaihtuu datasta riippumatta, kun framebufferkoodi luulee, että yläosan bitit on käsitelty, koska SPI:tä toistaiseksi vielä bitbangataan. Omenan alustaminen aiheutti tarpeeksi pitkän funktiokutsuketjun, että stackissa päästiin yhtä syvälle kuin missä lcd-piirtely käy. Siihen kohtaan, mihin laskuri päätyy, tuli jokin muu arvo kuin 0.

Tarinan opetus: COPYPASTE ON VÄÄRIN. Valgrind on ystävä. Ylimääräiset alustukset ovat ihan ok. Tarkista aina ilmiselvältä tuntuvat funktiot äläkä luota mihinkään. Huomaa, että samankaltaiset muuttujanimet eivät ole samoja. Mutta onneksi virheistä kuulemma oppii, ja ehkä tällainen pitkä avautuminen ja syvällinen tarkastelu opettaa lisää.

1 kommentti

Oma kommenttisi

Mielipide tämän sivun asiasta? Kirjoita toki. Älä raapusta kuitenkaan ihan asiattomia juttuja.

Jos on yksityisempää asiaa, tarkkaa kysyttävää tai aihetta pidemmälle keskustelulle, käytä yhteydenottolomaketta kommentoinnin sijaan.

Hölmöt kommentit saatetaan moderoida pois jälkikäteen.

Nimimerkki:

Spammibottiesto: Mikä on kahden ja nollan erotus? (vastaus numeroina)