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.