wxPython-ohjelmointia: USB-ISS sarjaporttimokkulan graafinen käyttöliittymä

Elektroniikan kanssa näperrellessä tarvitsee usein tiedonsiirtoa tietokoneelle päin ja toiseenkin suuntaan. Käyttötapauksia voi olla esimerkiksi anturitietojen lähetys sulautetulta laitteelta tietokoneelle analysoitavaksi tai ohjelman suorituksen debuggaaminen, tai muuta vastaavaa.

Monissa kehitysalustoissa (kyllä, Arduinossakin) on vakiona USB-sarjaporttimuunnin, minkä avulla saadaan mikroprosessorin lähettämä sarjamuotoinen tieto virtuaalisarjaportin avulla käsiteltäväksi. Tätä ei kuitenkaan aina ole mahdollista käyttää ja monesti ratkaisu ei välttämättä täysin käytännöllinenkään ole. Ainahan voisi viritellä erillisen sarjaporttimuuntimen prossun kylkeen, mutta näissä on monesti se ongelma, että muunnos tapahtuu 3/5 voltin (CMOS/TTL) logiikkatasosta RS232 tasolle, missä jännitetasot voivat olla prossun kannalta kuolettavia. Tästä aiheesta voit lukea tarkemmin tältä sivulta.

USB-ISS Sarjamoduulin kuva
USB-ISS Sarjamoduuli (SPI/I2C/Serial)

USB-ISS on Robot-Electronicsin tuote, minkä avulla saadaan helposti ja kompaktisti toteutettua muunnos suoraan USB:sta TTL/CMOS-tasoon. Eli voidaan käyttää mikroprosessorin sarjaporttia suoraan, ilman RS232-muunnospiirejä välissä. USB-ISS "mokkula" toimii tietokoneella virtuaalisarjaporttina, mutta sitä ohjataan sarjakomennoilla, joten käyttäminen vaatii erillisen sovelluksen. Tätä "mokkulaa" voidaan käyttää myös SPI ja I2C -sarjaväylien kanssa kommunikointiin ja on siksi varsin näppärä lisä työkalupakissa.

Itse USB-ISS laitteesta voit lukea lisää tältä sivulta: https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm.

Pythonille olisi ollut tarjolla useita erilaisia sovelluksen tekoon tarkoitettuja kehitysympäristöjä (IDE), mutta tyydyin tällä kertaa perus Python IDLE:en, sillä se tulee vakiona mukana Pythonin asennuksessa. Lisää erilaisia ohjelmointiympäristöjä löytyy tästä linkistä: https://www.programiz.com/python-programming/ide.

No mitä varten tähän tarvitaan Python-koodia?

Vaikka laitteelle on tehty esimerkkiohjelma valmistajan toimesta, ei se varsinkaan sarjaportin kohdalla ollut mielestäni sellainen, joka sopi parhaiten omaan käyttöön. I2C ja SPI-väylien käyttöön valmistajan esimerkkiohjelmat sopivat mielestäni oikein hyvin ja muutenkin valmistaja tuntuu painottavan niiden käyttöä enemmän.

Ajatus pythonilla tekemisestä lähti oikeastaan liikkeelle siitä, että etsiskelin netistä apuohjelmaa tämän laitteen käyttämiseksi, mutta koska kyseessä on ehkä vähän erikoisempi laite, ei tälle aivan kauheasti julkista softaa tuntunut olevan. Lopullisen niitin arkkuun iski se, että pythonille kuitenkin löytyi kirjasto tämän laitteen käyttämiseen suoraan PyPi.org sivuilta (Python Package Index): https://pypi.org/project/usb-iss/. Ja toisekseen, pythonilla oli mukava pitkästä aikaa päästä tekemään jotain käytännön tasolla hyödyllistä.

Koska koneelleni oli jo aikaisemmin asennettu pip (Python Package Installer), ei minun sitä tarvinnut asentaa ( https://pypi.org/project/pip/ ), vaan pystyin asentamaan USB-iss paketin Windowsin komentoriviltä suoraan:

pip install usb-iss
USB-ISS python asennus
USB-ISS python asennus. Huom uusin versio on 1.0.0!

Kun paketti oli asennettu, aloin tutustumaan manuaaliin ja referenssikoodeihin sivuilla: https://usb-iss.readthedocs.io/en/latest/usb_iss.html.

Ensimmäisenä oli saatava yhteys "mokkulaan". Tarkistin mille sarjaportille laite oli Windowsissa ilmestynyt ja testasin Python shellissä laiteyhteyttä hyvin simppelisti kysymällä laitteen fw-versiota.

USB-ISS muuntimelle voidaan lähettää erilaisia komentosarjoja. Yksi näistä komennoista on esimerkiksi heksakoodina [0x5A, 0x01]. Kyseinen komento laittaa moduulin vastaamaan ID-numerolla, firmisversiolla ja toimintamoodilla. Pythonissa näille on kuitenkin funktiot valmiina, joten ei tästä tässä vaiheessa enempää.

Lisää komennoista voit lukea USB-ISS laitteen valmistajan kotisivuilta: https://www.robot-electronics.co.uk/htm/usb_iss_tech.htm.

Kun yhteys pelasi ja kirjasto vaikutti toimivan, siirryin oman sovelluksen suunnitteluun ja tekemiseen.

GUI:n rakennus wxPythonilla

Sovelluksen piti olla graafinen, joten ensimmäisenä etsiskelin netistä tietoa kuinka pythonilla voidaan koodata graafisia käyttöliittymiä. Vastaan tuli muutama erilainen vaihtoehto joista lopuksi käyttöön valkkautui wxPython. Jos et tiedä mikä wxPython on, niin lue alla oleva lainaus ensin.

https://wxpython.org/

This website is all about wxPython, the cross-platform GUI toolkit for the Python language. With wxPython software developers can create truly native user interfaces for their Python applications, that run with little or no modifications on Windows, Macs and Linux or other unix-like systems.

Lyhyesti suomennettuna wxPythonilla voidaan luoda graafisia käyttöliittymiä tietokonealustasta (Windows/Linux/Apple) riippumatta. Tämähän sopii hyvin!

Asennus hoitui helposti Windowsin komentorivillä:

pip install wxpython

Lue lisää asennusohjeista täältä: https://wxpython.org/pages/downloads/index.html

Lueskelin wxPythonin sivuilta ensin perustietoa sen käytöstä ja googlettelin esimerkkikoodeja. Näiden tarkoituksena oli päästä hieman hajulle siitä, mistä kannattaa aloittaa ja kuinka sovellus rakentuu pythonilla tehtäessä.

wxPython sovelluksen runko

Muutamia päiviä käytin wxPythonin kontrollien ja eventtien tutkimiseen, eli kuinka esimerkiksi saadaan tehtyä sovellusikkuna ja siihen tekstiä, nappuloita, alasvetovalikoita jne. ja kuinka niistä saadaan tapahtumat ohjelman käsiteltäväksi. Näiden asioiden tultua hieman selvemmäksi aloitin tekemään omaa GUI:ta (Graphical User Interface) wxPython ja usb-iss -kirjastojen/moduleiden avulla.

Perus sovelluksen runko, millä pelkkä ikkuna saadaan tehtyä on esitetty alla olevassa koodilohkossa. Koodilohkon alle on lyhyesti selostettu rivien toiminta:

1    import wx
2
3    class testWindow(wx.Frame):
4        def __init__(self):
5        super().__init__(parent=None, title="Testi-ikkuna",size=(400,300))
6        self.panel = wx.Panel(self)
7        self.Show()
8                         
9 if __name__ == "__main__":
10    app = wx.App()
11    frame = testWindow()
12    app.MainLoop()

Rivillä 1 tuodaan wxPython kirjasto käyttöön. Riviltä 3 alkaen tehdään oma luokka käytetylle ikkunalle ja alustetaan se näyttämään kuten alla olevassa kuvassa.

Rivi 6 asettaa ikkunaan paneelin, eli ikäänkuin "containerin" tai "placeholderin" minkä sisälle käyttöliittymäkomponentteja voidaan luoda niin, että ne pysyvät järjestyksessä. Rivillä 7 ko. tyhjä ikkuna näytetään ja siihen loppuukin luokan testWindow koodi.

Rivillä 9 tarkistetaan python skriptiä ajettaessa, että suoritetaanko koodia komentotulkista vai "itsenäisenä ohjelmana" ja toimitaan sen mukaisesti (lue lisää aiheesta täältä: https://www.journaldev.com/17752/python-main-function)

Kyseinen testikoodi tuottaa alla olevan ikkunan:

UsbIss lopullinen sovellus

Tarvitsin lopullisessa sovelluksessa muutamia eri asioita käyttöliittymästä ja toiminnallisuudesta, joten speksasin itselleni listan toteutettavista asioista.

Ohjelmassa tulisi olla seuraavat asiat:

  • Kommunikointiportin valinta, eli se portti missä Usb-Iss moduuli näkyy
  • Nopeuden valinta TTL/COMS sarjaliikenteelle (ei kommunikoinnille moduulin kanssa, koska se on vakio)
  • Yhdistä nappi
  • Info-kenttä josta nähdään moduulin tietoja
  • Datan vastaanottokenttä
  • Datan lähetyskenttä
  • Heksadesimaalilukujen lähetyskenttä
  • Rivinvaihtomerkkien lähetyksen valinta

Aloitin ohjelman tekoa luomalla ensin yhteyden moduulille ja tekemällä tarvittavat koodit sille. Erittelen alle lyhyesti wxPythonin avulla tehtyjä käyttöliittymäelementtejä, mutta itse toiminnallisen koodin selityksen jätän pois, sillä sen voit tarkistaa lähdekoodista itsekin.

Porttinopeuden alasvetolaatikon sisältö

self._comspeeds = ["300", "1200", "2400", "9600", "19200", "38400", "57600", "115200", "250000", "1M"]

self._comboboxSpeed = wx.ComboBox(self.panel,id=wx.ID_ANY,value="speed",choices=list(self._comspeeds))

self._comboboxSpeed.SetSelection(7)

Ensimmäisellä rivillä luodaan lista joka pitää sisällään valittavat tiedonsiirtonopeudet TTL/CMOS sarjaportille. Seuraavaksi luodaan ComboBox elementti, mihin lisätään luotu lista. Lopuksi valitaan oletusnopeudeksi 115200. ID-tunnistetta en käyttänyt näissä mihinkään, mutta ajattelen että sitä voi käyttää halutessaan jonkun tietyn elementin etsimiseen käyttöliittymästä. Tulee mieleen GetElementById-javascript metodi... Myös portin valinta tehdään samalla tavalla, mutta porttilistaukseen käytetään funktiota getComportsList().

self.comboboxCOMport = wx.ComboBox(self.panel,id=wx.ID_ANY,value="Select port...",choices=self.getComportsList(),size=(130,35))

getComportsList() sisältää toiminnallisuuden joka palauttaa listan kaikista tietokoneella saatavilla olevista sarjaporteista:

def getComportsList(self):
        coms = list()
        i = 0
        for port in serial.tools.list_ports.comports():
            coms.append(str(port)[0:5])
            #print (coms[i])
            i += 1
        return coms

Tekstikentät ja infokenttä

self.infotext = wx.StaticText(self.panel,id=wx.ID_ANY,label="No device detected.\nSelect COM port\nand connect.")

self.outputtextlabel = wx.StaticText(self.panel, id=wx.ID_ANY, label="User data for sending:")

self.outputtext = wx.TextCtrl(self.panel,id=wx.ID_ANY, size=(400,200),style = wx.TE_MULTILINE)

self.inputtext = wx.TextCtrl(self.panel,id=wx.ID_ANY, size=(400,200),style = wx.TE_MULTILINE)

self.inputtextlabel = wx.StaticText(self.panel, id=wx.ID_ANY, label="Received data:")

StaticText on staattinen tekstikenttä-elementti, minkä sisältö luodaan alustuksessa. Myöhemmin sen sisältämää tekstiä voidaan muuttaa SetLabel-funktiolla. TextCtrl on käyttäjän editoitavissa oleva tekstikenttä.

Nappien luonti

Nappeja voidaan luoda Button:in avulla:

self.btn_cnct = wx.Button(self.panel, label="Connect")
self.btn_cnct.Bind(wx.EVT_BUTTON, self.connectToUSBISS)

Nappeihin kiinnitetään tapahtuma (event) joka ohjaa sovelluksen suoritusta haluttuun käyttäjän kirjoittamaan funktioon, tässä tapauksessa connectToUSBISS-funktioon. Sen sisältö on hieman pidempi (kannattaa tarkistaa lähdekoodista, löytyy kirjoituksen lopusta), mutta pseudokoodina alla:

  • Tarkistetaan onko nappi Connect vai Disconnect -tilassa
  • Jos Connect niin yritetään avata portti
    • Lähetetään porttiin komento "lue id"
    • jos id on ok, enabloidaan tekstikentät ja vastaanotetaan dataa timerin avulla
    • jos id ei ole ok niin herjataan
  • Jos Disconnect niin suljetaan portti ja nollataan muuttujat, pysäytetään timerit jne

Ajastimen avulla luetaan dataa sarjaportista 0.1 sekunnin välein ja päivitetään vastaanottoikkunaa. Kun käyttäjä painaa Send-nappia, niin käyttäjän syöttämät datat lähetetään sarjamokkulan kautta kohteeseen. Jos "Send hex data only" -boksi on valittuna, niin pienemmästä teksti-ikkunasta lähetetään vain heksamuodossa tiedot väylälle.

Lopulta sain siis aikaiseksi alla olevan kuvan mukaisen sovelluksen:

USB ISS GUI Serial
USB-ISS GUI

Testaus

Testasin tehtyä ohjelmistoa koko koodin kehityksen ajan STM32F100 mikrokontrollerin avulla. Tein lyhyen koodipätkän (mistä on kerrottu tarkemmin tällä sivulla: https://www.hutasu.net/stm32f100-mikrokontrollerin-ohjelmointi/), joka lähettää dataa sarjaporttiin.

STM32:ssa käytetty koodi datan lähetykseen.
STM32:ssa käytetty koodi datan lähetykseen.

Kytkin STM32:n TX-pinnin (PB10) USB-ISS moduulin RX-pinniin, ja STM32:n RX-pinnin (PB11) USB-ISS moduulin TX-pinniin.

USB-ISS pinnilista
USB-ISS pinout
USB-ISS ja STM32 yhteydessä toisiinsa
USB-ISS ja STM32 yhteydessä toisiinsa

Kuten kuvasta huomataan, niin tietoa saadaan nyt suoraan mikroprosessorilta talteen käyttöliittymän kautta. Python mahdollistaa myös monenlaisien graafien ja muunlaisen data-analyysin aivan eri tasolla, kuin mitä olen aikaisemmin harrastanut.

Kohdattuja koodiongelmia

Aivan aluksi en meinannut saada sarjaliikennettä usb-iss moduulilta luettua oikein, vaan kirjasto herjasi koko ajan "Expected 3 bytes, but 0 received". Tämän ongelman metsästämiseen USB-ISS python koodeista vei muutaman tunnin aikaa ja tein monia erilaisia korjausyrityksiä. Lopulta kuitenkin paikansin ongelman "ajuritiedostoista" ja tein alla olevan korjauksen, millä sain moduulin lukemaan sarjaväylältä havaitut datat.

Datan tiedustelu moduulilta
Datan tiedustelu moduulilta

Ongelmana minun ymmärrykseni mukaan oli se, että USB-ISS moduulilta piti kysyä kaksi kertaa datoja, eli protokollaa tulkittiin väärällä tavalla tässä tapauksessa (korjatkaa jos joku tietää paremmin). Kun moduulille lähetetään vain yksi tavu 0x62 (Serial-komento), niin moduuli vastaa takaisin kolmella tavulla jotka sisältävät kuittaustavun, lähetettyjen tavujen määrän ja vastaanotettujen tavujen määrän. Näin ollen viestiprotokollan kehys on:

VastausLähetetyt tavutVastaanotetut tavut
0xFF / 0x000x00 - 0x1E (0-30)0x00 - 0x3E (0 - 62)

Ensimmäinen tavu on kuittaus, joka on 0x00 silloin kun kaikki on ok (ACK), muutoin 0xFF (NACK). Toinen tavu on lähetettyjen datojen määrä joka on maksimissaan 30 tavua. Kolmas tavu on vastaanotettujen määrä maksimissaan 62 tavua.

Näitä tietoja ei siis alkuperäinen koodi jotensakin käsitellyt oikein, joten tätä ongelmaa piti kiertää ylimääräisellä "datan tiedustelu" toiminnolla, joka on esitetty kuvassa yllä.

HUOM! Uusin versio USB ISS python kirjastosta toimii ilman yllä olevaa korjausta!

Yhteenveto ja opitut asiat

Tätä oli oikein mukava koodailla ja testailla toimintaa STM32-mikroprosessorin avulla. Kaikkinensa tämä vei aikaa noin viikon verran, sillä opeteltavia asioita oli aika paljon.

wxPython jäi mielenkiintoisella tavalla mieleen ja perus käyttöliittymäkomponenttien luonti tuli tutuksi. Tälle löytyy kyllä graafisiakin työkaluja, mutta minä tein kaiken ihan vain koodaamalla, koska se mielestäni tehostaa oppimista aina parhaiten.

Pythonin käyttö oli ennestäänkin jo hieman tuttua data-analytiikan ja big-datan kursseilta, mutta tässä tuli vähän verestystä muistoihin ja toivottavasti myös vähän osaamista ja luontevuutta lisää Pythonin käyttöön.

USB-Iss -python kirjasto vaikutti hyvältä, vaikka sarjaportista luku ei meinannut toimia ollenkaan. Alkuperäisen koodaajan sivuilla kyllä mainitaankin, että sarjaliikenne (ei SPI/I2C) oli testaamaton ominaisuus, joten siinäkin saattaa olla yksi syy miksi homma ei aivan mennyt niin kuin pitänyt. Mutta lopulta sain kuitenkin kaikki toimimaan ja nyt on työkalulaatikossa yksi uusi softa/rauta -palikka lisää!

Lähdekoodit ja lisätietoa

Koodit tähän työkaluun löytyvät gitistä (USB-ISS-GUI) ja wiki-sivuilla on myös käyttö- ja asennusohje: (USB-ISS-GUI/wiki).

Jätä kommentti

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *

This site uses Akismet to reduce spam. Learn how your comment data is processed.