19. Tiedon siirto Launchpadilta PC:lle

Alunperin julkaistu: 12.2.2017

Viimeksi muokattu: torstai 11.6.2020

Tämän osan aiheena onkin hieman erilaisempi epistola, missä käsittelemme tiedon siirtoa laitteelta toiselle - tässä tapauksessa siis Launchpadilta tietokoneelle. Ennen kuin mennään itse koodin kimppuun, on hyvä käydä asioita yleisellä tasolla lyhyesti läpi.

Jos sarjaliikenne ja sarjaportti on vähemmän tuttu, niin kannattaa vilkaista sarjaliikenne sivua, missä kerrotaan enemmän sarjaportista. Emme käy kaikkia asioita välttämättä pohjamutia myöten läpi, sillä se ei ole mielekästä - tietoliikenne ja varsinkin sarjamuotoinen sellainen on niin laaja käsite, että pienempi pala kakkua kerralla riittää varmasti hyvin.

Yleistä tietoliikenteestä

Tietoliikenne kahden laitteen välillä on tietotekniikassa varsin usein esiintyvä tapahtuma. Esimerkiksi PC eli tavallinen tietokone siirtää tietoa kiintolevyltä välimuistiin tai toiseen tiedon tallennuspaikkaan erilaisten sarjaväylien kautta. Mm. SATA-väylä toimii lähettämällä ja vastaanottamalla bittejä yksi toisensa jälkeen. Niin ikään USB on nimensä mukaisesti sarjaväylä (Universal Serial Bus) ja USB-tikut ovat varmasti monelle tuttuja tiedon tallennusvälineitä.

Kuten yllä olevasta käy ilmi, niin tietoliikennettä tarvitaan siis tiedon siirtoon. Esimerkiksi lämpötilaa, ilman kosteutta ja muita ilmakehän ominaisuuksia mittaava sääasema voi ilmoittaa antureilta havaitut tiedot tietokoneelle, joka tallentaa ne kiintolevyn muistiin. Yhtä hyvin tieto voidaan kyseisessä sääasemassa tallentaa vaikkapa muistikortille tietojen myöhempää tarkastelua varten. Olipa asia kummin päin tahansa, niin tietoliikenne on edellytys laitteen toiminnalle ja tietojen keruulle.

Sarjaportin käyttöön ottaminen PC:ssä

MSP430 mikrokontrollereissa, kuten monissa muissakin mikrokontrollereissa on siis mahdollista siirtää tietoa sarjamuotoisessa formaatissa. Sarjaporttia varten emme tarvitse mitään uutta kytkentää, sillä Launchpadiin on sisällytetty virtuaalisarjaportti, joka toimii Launchpadin USB-kaapelin kautta.

Kun kytket Launchpadin USB-johdolla tietokoneeseen, ilmestyy virtuaalinen sarjaportti tietokoneen laitehallintaan (windows). MSP430 Application UART -nimellä löytyvän sarjaportin numero kannattaa painaa mieleen, sillä sitä tarvitaan terminaaliohjelmassa yhteyden avaamiseen. Alla olevassa kuvassa ko. sarjaportti on COM10 ja sinun tietokoneessa se varmaankin on eri numerolla.

Jotta sarjaporttia voidaan käyttää ja sinne tulostettua tietoa voidaan lukea, niin tarvitaan terminaaliohjelma, joka käsittelee tietokoneen sarjaporttia, luo uuden yhteyden ja hoitaa muut hommat "konepellin alla".

Terminaaliohjelmia on olemassa erilaisia ja tässä esimerkissä olen käyttänyt Tera Term Pro (versio 2.3) nimistä ohjelmaa. Ohjelman lataussivulle on linkki tässä. Mikäli ohjelman käyttö on sinulle tuttua, niin hyvä juttu. Mutta jos ei ole, niin lyhyet ohjeet ohjelman käyttöön tulevat tässä:

1) Lataa ja asenna ohjelmisto
2) Asennuksen jälkeen avaa ttermpro.exe -tiedosto ohjelman asennuskansiosta
3) Ohjelma kysyy auettuaan mikä yhteys avataan (vasen kuva alla) ja valitaan alasvetovalikosta oikea COM-portti. Painetaan OK.
4) Avataan valikosta sarjaportin asetukset Setup -> Serial port... ja valitaan kuvan mukaiset asetukset valitulle COM-portille (oikea kuva alla). Painetaan OK. Nyt virtuaalisarjaportti on valmiina käyttöön.

Mikäli COM-porttia ei näy listassa, niin avaa Tera Term Pro -ohjelman asennuskansio ja avaa tiedosto TERATERM.INI notepad editorilla tai muulla tekstieditorilla. Etsi tiedostosta rivi, missä asetetaan MaxComPort -muuttujan arvo. Muuta siihen arvo, joka vastaa virtuaalisarjaportin COM-numeroa. Esimerkiksi kuvan tapauksessa arvoksi voisi laittaa:

MaxComPort=10

Itse käytän tässä arvoa 255 (maksimi), jolloin portin alasvetovalikossa näkyy 255 COM-porttia. Tallenna tiedosto ja käynnistä terminaaliohjelma uudestaan.

Asetusten tekemisen jälkeen voimmekin keskittyä mikrokontrollerin ohjelmoimiseen. Alla on esitetty tämän luvun ensimmäinen esimerkki sarjaportin käytöstä Timerin avulla.

Esimerkki 1: "Hello World!" sarjaporttiohjelma

Koska MSP430G2231 mikrokontrollerissa ei ole sisäänrakennettua UART-moduulia, täytyy sarjaportti tehdä ohjelmallisesti. Ohjelmallinen sarjaportti toteutetaan Timer A:n avulla ja kellosignaalina käytetään aiemmin asennettua 32,768 kHz:n kellokidettä.

Sarjaportti alustetaan toimimaan 9600 bit/s nopeudella ja sarjaporttiin tulostetaan teksti "Hello World!". Kun sarjaportista lähetetään dataa, niin väläytetään punaista lediä. Sarjaportissa ei käytetä pariteettia ja stop-bittien määrä on yksi. Asetukset notaatiolla merkittynä ovat näin ollen 8N1.

#include <msp430g2231.h>
// Ohjelman koko tavuina (byte) Flash/RAM = 434/12
// apumakroja käytetään helpottamaan koodin kirjoitusta 
#define INIT_LAUNCHPAD_LEDS (P1DIR |= BIT0+BIT6) 
#define LED_PUN (BIT0) 
#define LED_PUN_ON (P1OUT |= LED_PUN) 
#define LED_PUN_OFF (P1OUT &= ~LED_PUN)

// sarjaportin määrityksiä 
#define UART_TX BIT1 // Timer0_A OUT0 
#define UART_MASTER_CLK 1000000 // 1 MHz kello SMCLK 
#define UART_BAUDRATE 9600 // baudinopeus 
#define UART_BITLENGTH (UART_MASTER_CLK / UART_BAUDRATE) // bitin pituus

// globaali data mitä tarvitaan keskeytyksessä 
unsigned int tx; // tähän ladataan lähetettävä data sekä start että stop-bitit 

// lähetysfunktio 
void UART_tx(unsigned char tavu);

void main(void)
{
     WDTCTL = WDTPW + WDTHOLD; // watchdog pysäytys 
     INIT_LAUNCHPAD_LEDS; // Ledien alustus 

     // alustetaan CPU:n kello 1 MHz 
     BCSCTL1 = CALBC1_1MHZ; 
     DCOCTL = CALDCO_1MHZ; 
     // kiteen alustus 
     BCSCTL3 = XCAP_3; 
     // SW-UART pinnien alustus 
     P1OUT = 0; 
     P1SEL |= UART_TX; // Timer ohjaa TXD pinniä (P1.1) 
     P1DIR |= UART_TX + LED_PUN; // TX pinni ja punainen LED lähdöksi 

     // Timerin alustus 
     TA0CCTL0 = OUT; // idle-tila "1" 
     TACTL = TASSEL_2 + MC_2; // Kellona SMCLK ja timer laskee jatkuvasti 

     __enable_interrupt(); // sallitaan globaalit keskeytykset

     while(1)
     { 
     UART_tx('H');  // Kirjoitetaan 'Hello World' kirjain kerrallaan sarjaporttiin. 
     UART_tx('e');  // Kun halutaan lähettää esim. e-kirjainta vastaava numero 
     UART_tx('l');   // niin se täytyy laittaa heittomerkkien ' sisään. Tällöin kääntäjä 
     UART_tx('l');   // tulkitsee kirjaimen numerona ja välittää sen funktiolle oikein. 
     UART_tx('o'); 
     UART_tx(' '); 
     UART_tx('W'); 
     UART_tx('o'); 
     UART_tx('r'); 
     UART_tx('l'); 
     UART_tx('d'); 
     UART_tx('!'); 
     UART_tx(' ');  // rivinvaihto 
     UART_tx(' ');   // kelataan rivin alkuun 
     }
}

// UART-lähetysfunktio 
void UART_tx(unsigned char tavu)
{ 
     while(TACCTL0 & CCIE); // odotellaan että entinen tavu on lähetetty 

     TACCR0 = TAR; // nykyinen laskurin tila capture/compare rekisterin arvoksi 
     TACCR0 += UART_BITLENGTH; // päivitetään capture/compare rekisterin arvo 
     TACCTL0 = OUTMOD0 + CCIE; 
     // timer asettaa pinnin OUT-bitin mukaisesti, sallitaan myös keskeytys 

     LED_PUN_ON; // LED päälle (sammutus keskeytyksessä) 
     tx = tavu; // ladataan tavu globaaliin muuttujaan 
     tx |= 0x100; // lisätään stop-bitti "1" lähetettävään dataan 
     tx <<= 1; // lisätään start-bitti "0" lähetettävään dataan 
     // esimerkiksi jos tavu on 0x55 = 0101 0101, niin tästä tulee ensin: 
     // 1 0101 0101 jonka jälkeen siirto ja saadaan: 
     // 10 1010 1010 
}

//------------------------------------------------------------------------------
// Timer_A UART - Lähetyskeskeytys.
// Tämä keskeytys lähettää kaikki bitit muuttujasta tx. Kaikkinensa siis 10 bittiä
// lähetetään ja kukin niistä yksi kerrallaan.
//------------------------------------------------------------------------------ 
#pragma vector = TIMERA0_VECTOR
__interrupt void Timer_A0_ISR(void)
{ 
     static unsigned char txCnt = 10; 
     // static estää muuttujan häviämisen kun funktiosta poistutaan 
     // alustus suoritetaan kuitenkin vain kerran 

     TACCR0 += UART_BITLENGTH; // Lisätään aikaa seuraavaan bittiin asti 

     if(txCnt == 0) // tarkastetaan onko kaikki bitit lähetetty 
     { 
          TACCTL0 &= ~CCIE; // kaikki bitit lähetetty, kielletään keskeytys 
          txCnt = 10; // palautetaan muuttujaan alkuperäinen määrä bittejä 
          LED_PUN_OFF; // sammutetaan LED (sytytys UART_tx -funktiossa) 
     } 
     else 
     { 
          if(tx & 1) // tarkastetaan onko lähetettävä bitti 1 vai 0 
          { 
               TACCTL0 &= ~OUTMOD2; // lähetetään '1' bitti 
          } 
          else 
          { 
               TACCTL0 |= OUTMOD2; // lähetetään '0' bitti 
          } 
          tx >>= 1; // siirretään lähetettävän bitin jonoa oikealle jolloin 
          // lähetettyä bittiä ei enää ole jonossa 
          txCnt--; // vähennetään lähetettävien bittien määrää yhdellä 
     }
}

Ohjelman kulku

Ohjelma alustaa ensin porttipinnit, kiteen ja timerin. Kun alustukset on suoritettu, niin pääohjelman silmukassa tulostetaan kirjain kerrallaan "Hello World!" sarjaporttiin, minkä terminaaliohjelma ottaa vastaan. Alla olevassa kuvassa nähdään terminaaliohjelman vastaanottamaa tietoa, joka on juuri kuten pitääkin olla. Erikoismerkit ja ovat yhdessä rivinvaihtomerkki, millä terminaaliohjelma siirtää kursorinsa aina uuden rivin alkuun. Merkit vastaavat siis esimerkiksi notepadiin kirjoittaessa enterin painallusta.

Ohjelman lähetysfunktio UART_tx ottaa parametrina vastaan yhden kirjaimen (tai numeron) ja asettaa sen globaaliin muuttujaan tx. Ennen muuttujan asetusta funktio myös alustaa timerin asetuksia niin, että lähetys onnistuu ilman virheitä ja myös timerin keskeytys sallitaan. Lähetettävä tieto on pohjimmiltaan vain numeroita, joita tietokoneen päässä tulkitaan kirjaimiksi. Itse asiassa kyseessä on ASCII-merkistö, ja kun katsotaan mitä numeroa kirjain (iso hoo) H vastaa ASCII-taulukossa (linkki1, linkki2), niin huomataan että se on desimaalina 72 ja heksalukuna 0x48. Mikäli H-kirjaimen paikalle vaihdetaan luku 72, niin sarjaportista vastaanotetaan yhä sama rimpsu kuin aiemminkin.

Ohjelma lähettää 8-bitin merkit bitti kerrallaan keskeytyksessä ja kun kaikki bitit on lähetetty, nollataan bittien lähetyslaskuri txCnt sekä kielletään keskeytys. Koska timer 'juoksee' koko ajan, niin keskeytys pitää kieltää viimeisen merkin jälkeen jotta ohjelma toimii oikein. Tämän ohjelman voi toki tehdä toisellakin tavalla, mutta tämä koodi on sovellettu TI:n luomasta esimerkkikoodista ja asiat on pyritty pitämään yksinkertaisena.

Kun suoritat ohjelmaa, niin huomaat että punainen LED palaa koko ajan ja terminaaliohjelmaan ilmestyy paljon samaa tekstiä. Tämä johtuu siitä, että ohjelma tulostaa merkkejä sarjaporttiin hyvinkin tiuhaan tahtiin, koska ohjelmaan ei tehty mitään viivettä. Jos asetat debuggerin avulla breakpointin jollekin pääohjelman riville, voit huomata että tulostus pysähtyy kun ohjelma saavuttaa sen.

Voitkin alkaa esimerkkikoodin avulla tutkimaan millaisia merkkejä saat sarjaporttiin tulostettua ja saisiko sinne vaikka hymiön ilmaantumaan? 😉

Voit kokeilla muokata ohjelmaa myös niin, että tulostus tapahtuu vain napin painalluksesta. Lisäksi edellisiä osia hyödyntämällä saat tehtyä itsellesi vielä tästäkin  monipuolisempiakin sovelluksia.

Ongelmanratkaisua:

Jos mitään ei terminaaliohjelmassa näy, niin kannattaa koettaa seuraavia asioita:

1) Irroita Launchpad USB-johdosta
2) Sulje CCS
3) Sulje yhteys terminaaliohjelmasta ja sulje vasta sitten terminaaliohjelma
4) Kytke Launchpad uudestaan tietokoneeseen
5) Avaa terminaaliohjelma. Valitse valikosta Control -> Reset Terminal ja Reset port.Ota nyt uusi yhteys oikeaan sarjaporttiin (tarkasta tietokoneen hallinnasta/laitehallinnasta COM-portin numero)
6) Käynnistä CCS ja yritä ajaa ohjelma uudestaan

Mikäli yllä oleva ei millään tehoa, niin vaihda COM-portin numeromääritystä (Ohjeita: linkkivinkki1linkkivinkki2linkkivinkki3) jonka jälkeen toista kohdat 1-6.

Esimerkki 2: sarjaporttiin 'printtaus'

Esimerkki 1:ssä oli siis yksinkertainen merkki kerrallaan tulostus sarjaporttiin ja kaikki (toivon mukaan) sujui kuten pitikin. Merkki kerrallaan tulostuksessa ei mitään väärää ole, mutta varsin työlästähän se on jos pitäisi tulostaa vaikkapa 100 merkkiä ruudulle. Tämän vuoksi muokataan edellistä esimerkkiä niin, että sarjaporttiin voidaan tulostaa merkkijonoja pelkkien yksittäisten merkkien sijasta.

Ohjelmaan voidaan helposti yllä mainittu ominaisuus lisätä kirjoittamalla koodiin tulostusfunktio, joka hyödyntää aiemmin tehtyä merkin tulostusfunktiota. Kyseinen koodinpätkä on kirjoitettu alle:

// UART-printtifunktio 
void UART_print(char *merkkijono) 
{ 
      int i=0; 
      while(merkkijono[i] != '') // tulostetaan niin kauan kunnes tämä merkki (NULL) kohdataan 
      { 
            UART_tx(merkkijono[i]); 
            i++; 
      } 
}

Tulostusfunktio ottaa vastaan osoittimen merkkijonoon, minkä perusteella se voi tietää mitä kirjaimia täytyy tulostaa. Mikäli et tunne osoittimien käyttöä, niin esimerkiksi wikipediassa on lyhkäisesti kerrottu tästä. Osoitinta ilmaistaan tähdellä ja käytännössä osoitin on vain muistipaikkaan osoittava muuttuja, tässä tapauksessa osoitetaan merkkijonon "Hello World " ensimmäiseen kirjaimeen H. Tämänkin oppaan aihepiiriin kuuluu kertoa osoittimien käytöstä selvemmin, mutta niistä en ole vielä kerennyt kirjoittamaan. Noniin, mutta takaisin funktion toimintaan.

Funktio toimii siis siten, että saadusta merkkijonosta poimitaan ensimmäinen merkki, joka lähetetään UART_tx -funktiolle, tässä tapauksessa siis H-kirjain. Tulostusta jatketaan niin kauan kunnes kohdataan erikoismerkki '' eli NULL-merkki eli 0 (nolla), joka kertoo funktiolle että tulostusta ei enää jatketa. NULL tarkoittaakin aina tyhjää ja nollalla voidaan tämä merkkijonojen kohdalla kätevästi ilmaista.

Kun koodi lisätään, niin pääohjelman pääsilmukan sisältökin muuttuu yksinkertaisemmaksi käsittää. Olen korostanut lisätyt/muokatut koodirivit keltaisella pohjavärillä. Alla koodi kokonaisuudessaan.

#include <msp430g2231.h> 
// Ohjelman koko tavuina (byte) Flash/RAM = 434/12 
// apumakroja käytetään helpottamaan koodin kirjoitusta 
#define INIT_LAUNCHPAD_LEDS (P1DIR |= BIT0+BIT6) 
#define LED_PUN (BIT0) 
#define LED_PUN_ON (P1OUT |= LED_PUN) 
#define LED_PUN_OFF (P1OUT &= ~LED_PUN) 

// sarjaportin määrityksiä 
#define UART_TX BIT1 // Timer0_A OUT0 
#define UART_MASTER_CLK 1000000 // 1 MHz kello SMCLK 
#define UART_BAUDRATE 9600 // baudinopeus 
#define UART_BITLENGTH (UART_MASTER_CLK / UART_BAUDRATE) // bitin pituus 

// globaali data mitä tarvitaan keskeytyksessä 
unsigned int tx; // tähän ladataan lähetettävä data sekä start että stop-bitit 

// lähetysfunktiot 
void UART_print(char *merkkijono);  // uuden funktion määrittely
void UART_tx(unsigned char tavu); 

void main(void) 
{ 
     WDTCTL = WDTPW + WDTHOLD; // watchdog pysäytys 
     INIT_LAUNCHPAD_LEDS; // Ledien alustus 

     // alustetaan CPU:n kello 1 MHz 
     BCSCTL1 = CALBC1_1MHZ; 
     DCOCTL = CALDCO_1MHZ; 
     // kiteen alustus 
     BCSCTL3 = XCAP_3; 
     // SW-UART pinnien alustus 
     P1OUT = 0; 
     P1SEL |= UART_TX; // Timer ohjaa TXD pinniä (P1.1) 
     P1DIR |= UART_TX + LED_PUN; // TX pinni ja punainen LED lähdöksi 

     // Timerin alustus 
     TA0CCTL0 = OUT; // idle-tila "1" 
     TACTL = TASSEL_2 + MC_2; // Kellona SMCLK ja timer laskee jatkuvasti 

     __enable_interrupt(); // sallitaan globaalit keskeytykset

     while(1)
     { 
          // Nyt tarvitaan vain yksi rivi koodia printtifunktion ansiosta! 
          UART_print("Hello World! "); // Kirjoitetaan 'Hello World' sarjaporttiin. 
          // = rivinvaihto = null -merkki
     } 
} 

// UART-printtifunktio 
void UART_print(char *merkkijono) 
{ 
      int i=0; 
      while(merkkijono[i] != '') // tulostetaan niin kauan kunnes tämä merkki (NULL) kohdataan 
      { 
            UART_tx(merkkijono[i]); 
            i++; 
      } 
}

// UART-lähetysfunktio 
void UART_tx(unsigned char tavu) 
{ 
     while(TACCTL0 & CCIE); // odotellaan että entinen tavu on lähetetty 

     TACCR0 = TAR; // nykyinen laskurin tila capture/compare rekisterin arvoksi 
     TACCR0 += UART_BITLENGTH; // päivitetään capture/compare rekisterin arvo 
     TACCTL0 = OUTMOD0 + CCIE; 
     // timer asettaa pinnin OUT-bitin mukaisesti, sallitaan myös keskeytys 

     LED_PUN_ON; // LED päälle (sammutus keskeytyksessä) 
     tx = tavu; // ladataan tavu globaaliin muuttujaan 
     tx |= 0x100; // lisätään stop-bitti "1" lähetettävään dataan 
     tx <<= 1; // lisätään start-bitti "0" lähetettävään dataan 
     // esimerkiksi jos tavu on 0x55 = 0101 0101, niin tästä tulee ensin: 
     // 1 0101 0101 jonka jälkeen siirto ja saadaan: 
     // 10 1010 1010 
} 

//------------------------------------------------------------------------------ 
// Timer_A UART - Lähetyskeskeytys. 
// Tämä keskeytys lähettää kaikki bitit muuttujasta tx. Kaikkinensa siis 10 bittiä 
// lähetetään ja kukin niistä yksi kerrallaan. 
//------------------------------------------------------------------------------ 
#pragma vector = TIMERA0_VECTOR 
__interrupt void Timer_A0_ISR(void) 
{ 
     static unsigned char txCnt = 10; 
     // static estää muuttujan häviämisen kun funktiosta poistutaan 
     // alustus suoritetaan kuitenkin vain kerran 

     TACCR0 += UART_BITLENGTH; // Lisätään aikaa seuraavaan bittiin asti 

     if(txCnt == 0) // tarkastetaan onko kaikki bitit lähetetty 
     { 
          TACCTL0 &= ~CCIE; // kaikki bitit lähetetty, kielletään keskeytys 
          txCnt = 10; // palautetaan muuttujaan alkuperäinen määrä bittejä 
          LED_PUN_OFF; // sammutetaan LED (sytytys UART_tx -funktiossa) 
     } 
     else 
     { 
          if(tx & 1) // tarkastetaan onko lähetettävä bitti 1 vai 0 
          { 
               TACCTL0 &= ~OUTMOD2; // lähetetään '1' bitti 
          } 
          else 
          { 
               TACCTL0 |= OUTMOD2; // lähetetään '0' bitti 
          } 
          tx >>= 1; // siirretään lähetettävän bitin jonoa oikealle jolloin 
          // lähetettyä bittiä ei enää ole jonossa 
          txCnt--; // vähennetään lähetettävien bittien määrää yhdellä 
     } 
}

Ohjelman toiminta

Ohjelma toimii täysin samoin kuten esimerkissä 1, tai ainakin siltä se näyttää, vaikka sisäisesti ohjelma tekee asioita nyt hieman tehokkaammin ja pienemmällä määrällä koodia. Ohjelman toimintaa ei varmaankaan tämän vuoksi tarvitse enempää selittää.

Toivottavasti sarjaportin käyttö on nyt jotenkuten helpompi aihe ja tästä on hyötyä lukijalle.