20. Tiedon siirto PC:ltä Launchpadille

Tässä osassa jatketaan siitä mihin edellisessä osassa jäätiin ja lisätään olemassa olevaan ohjelmaan koodia, minkä avulla saadaan tieto liikkumaan myös tietokoneelta mikrokontrollerin suuntaan.

Koska sarjaportti ja sen käyttöön ottaminen käytiin aiemmin läpi, ei tässä osassa ole muuta tarpeellista esiteltävää kuin itse koodi ja kuinka signaali voidaan timerin avulla napata talteen.

Timerin käyttö signaalin "pyydystyksessä"

Tiedon vastaanottaminen tapahtuu tässä ohjelmassa Timer A:n avulla. Timer A alustetaan siten, että porttipinnin P1.2 laskeva reuna aiheuttaa keskeytyksen Capture/Compare 1 -rekisteriin (TACCR1). Timer alustetaan siis "pyydystämään" haluttu signaalin muutos data-linjalla, joka sarjaportin tapauksessa on siis start-bitti. Se miten liipaisu tai "pyydystäminen" itse asiassa tapahtuu, on ilmaistu MSP430x2xx User's Manual -dokumentissa kappaleessa 12.2.4, ja alle olen pyrkinyt vielä omin sanoin selittämään miten kyseinen asia käytännössä hoituu.

Yllä oleva kuva löytyy edellä mainitusta manuaalista ja ajattelin tähän lyhyesti kertoa, miten liipaisu aiheuttaa timerin keskeytyksen. Noniin. Kuvassa on varmaankin muutama asia joita täytyy selventää. Ensiksikin ylimpänä on timerin kellosignaali, joka tässä tapauksessa on siis 32,768 kHz:ä (tai tulee olemaan koodissa). Sen alla on timerin kellojakson kuva, joka ilmaisee TAR-rekisterissä juoksevaa lukua. CCI-linja on syöttölinja timerille, joka konfiguroidaan porttipinniin ja mistä timer keskeytyksen tekee. Mahdolliset pinnit, joita inputtina timerille voi käyttää on merkitty MSP430G2231 mikrokontrollerille porttipinnin P1.2 kohdalla TA0.1. Vastaavanlainen merkintätapa löytyy muistakin laitteista.  Capture ilmaisee tapahtuman pituutta ja kuvasta voidaan huomata että se on noin puolet kellojaksosta. Lopuksi on keskeytyslipun asetus ja kuten nähdään, keskeytys ei jää päälle vaan käy hetkellisesti "1"-tilassa.

Kuvassa keskeytyksen tapahtuminen on asetettu niin, että kellosignaalin laskevalla reunalla tutkitaan CCI-linjaa ja jos se on kuvan tapauksessa "1"-tilassa, niin keskeytyslippu asettuu hetkeksi. Lisäksi TACCTLx rekisteriin on asetettu SCS-bitti päälle, joka synkronoi keskeytyslipun asettumisen timer-kellon laskevaan reunaan.

Asia voi tuntua aluksi hankalalta käsittää, mutta siihen tulee varmasti selvyys ajan kanssa. Pitemmittä puheitta koodin kimppuun.

Esimerkki 1: Ledin ohjaus tietokoneen ja sarjaportin avulla

Alla oleva koodi on muilta osin identtinen edellisen osan koodin kanssa, mutta erovaisuuksien havaitsemiseksi olen korostanut lisätyt koodirivit keltaisella taustavärillä alkuun.

#include <msp430g2231.h> 

// Ohjelman koko tavuina (byte) Flash/RAM = 518/70 
// apumakroja käytetään helpottamaan koodin kirjoitusta 
#define INIT_LAUNCHPAD_LEDS (P1DIR |= BIT0+BIT6) 
#define LED_PUN (BIT0) 
#define LED_VIHREA (BIT6)
#define LED_PUN_ON (P1OUT |= LED_PUN) 
#define LED_PUN_OFF (P1OUT &= ~LED_PUN)
#define LED_VIHREA_ON (P1OUT |= LED_VIHREA) 
#define LED_VIHREA_OFF (P1OUT &= ~LED_VIHREA)

// sarjaportin määritys: 8N1, SMCLK 1 MHz kalibroituna
#define UART_RX       BIT2
#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
#define UART_BITLENGTH_DIV_2 (UART_MASTER_CLK / (UART_BAUDRATE * 2)) 
// bitin näytteistyskohta

// globaali data mitä tarvitaan keskeytyksessä 
unsigned int tx; // tähän ladataan lähetettävä data sekä start että stop-bitit 
unsigned char rx; // tähän ladataan vastaanotettu tavu 
unsigned char jatka = 0; // tämä asettuu "1"-tilaan kun kokonainen tavu on vastaanotettu

// alustus- ja lähetysfunktiot 
void UART_init(void);                       // UART alustusfunktio
void UART_print(char *merkkijono);  // UART printtifunktio
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;
    
     // alustetaan sarjaportti
     UART_init();

     __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-alustusfunktio
void UART_init()
{
     // 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 
     // UART Timerin alustus 
     TA0CCTL0 = OUT; // idle-tila "1" 
      // RX-pinnin alustus timeria varten
     TA0CCTL1 = SCS + CM1 + CAP + CCIE + CCIS_0;
     // synkroninen kellosignaali (SCS) + 
     // reagoidaan laskevalla reunalla (MC1) + 
     // "kaappaus"-tila (engl. capture) (CAP) + 
     // sallitaan keskeytys (CCIE) ja porttipinni P1.2 (CCIS_0)
     TACTL = TASSEL_2 + MC_2; // Kellona SMCLK ja timer laskee jatkuvasti 
}

// 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ä 
     } 
}

//------------------------------------------------------------------------------ 
// Timer_A UART - Vastaanottokeskeytys. 
// Tämä keskeytys tarkkailee P1.2 linjaa timerin ohjaamana. Jos linjalla havaitaan 
// laskeva reuna, niin keskeytys asettaa näytteistys ajankohdan noin bitin puoleen väliin 
// ja kun keskeytykseen tullaan seuraavan kerran, niin tarkastetaan onko bitti 1 vai 0. 
// Tämä toistetaan niin kauan että 8 bittiä tietoa on saatu luettua. 
//------------------------------------------------------------------------------ 
#pragma vector = TIMERA1_VECTOR 
__interrupt void Timer_A1_ISR(void) 
{ 
     static unsigned char rxBitCnt = 8; 
     static unsigned char rxData = 0; 

     switch (__even_in_range(TAIV, TAIV_TAIFG)) 
     { 
     case TAIV_TACCR1: // TACCR1 CCIFG - UART RX 
          TACCR1 += UART_BITLENGTH; // Lisätään timerin capture kelloon bitin pituus 
          if (TACCTL1 & CAP) // Jos capture moodi on valittuna 
          { 
               TACCTL1 &= ~CAP; // valitaan compare moodi (vertaileva) 
               TACCR1 += UART_BITLENGTH_DIV_2; // lisätään timeriin aikaa niin että seuraava 
               // timer-keskeytys osuu bitin D0 keskelle (suurinpiirtein) 
          } 
          else 
          { 
               // compare moodi valittuna, otetaan bitit talteen. Else lohko 
               // suoritetaan aina start-bitin jälkeen 
               rxData >>= 1; 
               if (TACCTL1 & SCCI) // CCI1A pinnin tila voidaan lukea SCCI bitistä 
               { 
                    rxData |= 0x80; // asetetaan ylin bitti ykköseksi 
               } 
               rxBitCnt--; 
               if (rxBitCnt == 0) // tarkastetaan onko kaikki bitit otettu talteen 
               { 
                    rx = rxData; // tallennus 
                    rxBitCnt = 8; // ladataan bittilaskuriin alkuperäinen arvo 8 
                    TACCTL1 |= CAP; // vaihdetaan takaisin capture moodiin 
                    // jotta uusi tavu voidaan vastaanottaa 
                    jatka = 1; // asetetaan 1, jotta pääohjelma etenee 
               } 
          } 
     break; 
     } 
}

Ohjelman toiminta

Ohjelman alustuksessa on edelliseen ohjelmaan verrattuna uutta se, että timerin asetukset tehdään funktion sisällä ja että siinä alustetaan myös RX-pinnin eli P1.2:n toiminta.

Pääohjelmassa tulostetaan sarjaporttiin käyttöohjeet jonka jälkeen while -silmukan ehtolauseen ollessa totta odotellaan kunnes siitä päästään eteenpäin. Kun sarjaportista on saatu jokin merkki ja keskeytysrutiini on asettanut jatka-muuttujan arvoksi 1, niin ohjelma jatkaa eteenpäin ja tutkii oliko merkki L,l,O vai o ja sytyttää tai sammuttaa vihreän ledin Launchpadilla.

Alle olen kuvannut videon, mistä nähdään toiminta käytännössä.

Tähän päättyy ensimmäiset kaksikymmentä osaa Launchpadin käytöstä yksinkertaisissa sovelluksissa. Seuraavassa kymmenessä osassa keskitytään myös Launchpadin ja MSP430 mikrokontrollerin käyttöön, mutta jatkossa sovellukset vaativat hieman muutakin elektroniikkaa ympärille toimiakseen.