Fórum témák

» Több friss téma
Cikkek » Launchpad: ismerkedés az MSP430 mikrovezérlőkkel IV.
Launchpad: ismerkedés az MSP430 mikrovezérlőkkel IV.
Szerző: icserny, idő: Jan 14, 2013, Olvasva: 16046, Oldal olvasási idő: kb. 10 perc
Lapozás: OK   6 / 8

Közvetlen adatátvitel vezérlés (DTC)

Az előző oldal végén már láttunk egy példát a DTC használatára. Most ismerkedjunk meg a közvetlen adatátvitel beállításának és használatának részleteivel! A DTC lehetővé teszi, hogy a konverzió eredményét közvetlenül a memória címtartomány egy kijelölt területére másoltassuk.  Ez többnyire egy RAM terület, de lehet periféria regiszter, vagy akár  flash memória is. Ebben a DTC működése nagyon hasonlít a más mikrovezérlőknél vagy processzoroknál használt DMA-ra. A fő különbség az, hogy a DMA általános célú, külön meg kell adni, hogy melyik perifériából másolja át az adatokat. Az MSP430 DTC funkciója ilyen szempontból "bedrótozott DMA"-nak tekinthető, hiszen kizárólag az ADC-ből (az ADC10MEM regiszterből) viszi át a mérési adatokat, s a DTC-t kizárólag az ADC konverzió végét jelző impulzus triggerelheti.

A DTC működését három regiszter vezérli:

ADC10DTC0 - kiválasztja az egy- vagy kétblokkos átvitelt  és a folyamatos vagy blokkonkénti módot. A kétblokkos (ping-pong) mód lényege az, hogy amíg az adatok feldolgozása folyik az egyik blokkban, vele egyidejűleg az adatgyűjtés folytatódhat a másik blokkban.
 
ADC10DTC1 - adja meg az átviendő adatok számát egy blokkra vonatkozóan. A regiszterbe írt  0 érték letiltja  a DTC működését.
 
ADC10SA - a célhely kezdőcíme. Az ADC10SA regiszter beírása engedélyezi az adatátvitelt (ahogyan az ENC bit beírása engedélyezi a konverziót).

A kétblokkos mód azt jelenti, hogy a memóriában az ADC10DTC1 regiszterben megadott adatszám kétszeresének foglelunk helyet a memóriában, s előbb az első felét töltjük fel, majd a másodikat, s ezután (folyamatos átviteli mód esetén) újra az első blokkba írunk, majd a másodikba (más mikrovezérlő gyártók ping-pong módnak is nevezeik ezt az átviteli módot). A kétblokkos átvitel jelentősége az, hogy amíg az egyik blokk átvitele folyik, addig a másik blokkban zavartalanul végezhetjük az adatfeldolgozást.

 

Az ADC10DCT0 regiszter

adc10dtc0.png

 Reserved - fentartott (nem használt) bitek

ADC10TB - blokkmód választás (0: egyblokkos, 1: kétblokkos átviteli mód)

ADC10CT - folyamatos átvitel (0: az adatátvitel leáll, ha egy (vagy kétblokkos mód esetén két) blokk átvitele megtörtént, 1: az adatátvitel folyamatos, csak szakad meg, ha az ADC10CT bitet töröljük, vagy az ADC10SA regisztert újraírjuk)

ADC10B1 - ez az állapotjelző bit mutatja, hogy kétblokkos módban éppen melyik blokk feltöltése van folyamatban (0: a második blokk töltődik, 1: az első blokk töltődik)

ADC10FETCH - a MSP430x2xx Family User's Guide nem részletezi ennek a bitnek  a szerepét, csupán annyit mondanak róla, hogy általában '0'-át kell bele írni.

Ismételt ADC konverzió és DTC

A következő program azt mutatja be, hogy hogyan használhatjuk az ADC ismételt egycsatornás konverzió üzemmódját a közvetlen adatátvitellel kombinálva. Különösen hasznos ez az üzemmód akkor, ha több mérést akarunk végezni, hogy abból átlagot számoljunk.

Az alábbi programban a korábbi, 4_adc_simple_ref1 mintapéldához hasonlóan az A5 (P1.5 láb) bemenetre vezett 0 - 1,5 V közötti jelet, valamint a belső hőmérő jelét (Chan 10) mérjük meg, a beépített 1,5 V-os belső referenciához viszonyítva. A fő különbség az, hogy most csatornánkánt több mérést végzünk gyors egymásutánban (MSP430G2231 esetén 32 db. mérést). Apró különbség az is, hogy ennél a mintapédánál egy Microchip gyártmányú TC1047A analóg hőmérő jelét kötöttük az A5 bemenetre, ezért a nyers mérési adat és az abból meghatározott feszültség mellett a Celsius fokokban mért hőmérsékletet is kiíratjuk.

Hardver követelmények:

  • Launchpad kártya ADC-vel rendelkező MSP430G2xxx mikrovezérlővel (például G2231, G2452, G2553)
  • Az újabb kiadású (v1.5) kártyán az RXD,TXD átkötéseket SW állásba kell helyezni (a többi átkötéssel párhuzamosan álljanak)
  • Kössünk a P1.5 bemenetre egy TC1047A hőmérő kimenő jelét (vagy más, 0 - 1,5 V közötti feszültséget)

tc1047a.pngA TC1047A analóg hőmérő egy parányi, háromlábú, SOT23 tokozású IC, amely az alábbi paraméterekkel rendelkezik:

  • Tápfeszültség tartomány: 2,5 V - 5,5 V
  • Széles hőfoktartományban képes mérni: -40°C -  +125°C
  • Nagy pontosságú hőmérséklet-feszültség  konverzió:
    max. ± 2 °C, 25 °C-on
  • Lineáris hőmérsékletfüggés: 0 °C-on 500 mV,
    25 °C-on 750 mV, meredekség = 10mV/°C
  • Kis áramfelvétel: tipikusan 35µA 

 

A TC1047A analóg hőmérő bekötése roppant egyszerű: az 1-es lábát a tápfeszültségre (VDD), a 3-as lábát a földre (VSS), a 2-es lábát pedig a mikrovezérlő A5 analóg bemenetére (P1.1 láb) kell kötni.

A csatornánkénti mérések számát az NDATA makró definiíciójában adhatjuk meg. Az MSP430G2231 mikrovezérlő esetén a RAM memória korlátozza az adatokat tartalmazó tömb méretét, az MSP430D2553 esetében azonban megnövelhetjük az adatok számát. A mérés eredményeit egyirányú szoftveres UART kezeléssel (csak adatküldés) kiíratjuk. A programlistából kihagytuk a korábbi programokból már ismerős függvényeket, itt csak az új, vagy megváltoztatott függvények szerepelnek.

6_1. lista: Részletek a 4_adc_multi_ref1 programból

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. #define TXD       BIT1                 //TXD a P1.1 lábon
  5. #define NDATA     32                   //Adatok száma
  6. uint16_t adc_data[NDATA];              //Ide kerülnek a mérési adatok
  7.  
  8. /*-------------------------------------------------------------
  9.  * Sorozat mérés egy ADC csatornában, 1,5 V a belső referencia
  10.  * Feltételezzük, hogy külső jel mérése esetén az analóg funkció
  11.  * már engedélyezve van (az ADC10AE0 regiszterben).
  12.  *-------------------------------------------------------------
  13. */
  14. void ADC_multi_meas_REF1_5V(uint16_t chan, uint16_t *pbuf, uint8_t ndat) {
  15.   ADC10CTL0 &= ~ENC;                   //Az ADC letiltása újrakonfiguráláshoz
  16.     while (ADC10CTL1 & BUSY);          //Várakozás a foglaltság végére
  17.   ADC10CTL0 = ADC10SHT_3               //mintavétel: 64 óraütés
  18.              | ADC10ON                 //Az ADC bekapcsolása
  19.              | SREF_1                  //VR+ = VREF+ és VR- = AVSS
  20.              | REFON                   //Belső referencia bekapcsolása
  21.              | MSC;                    //Többszörös mintavétel és konverzió
  22.                                        //1,5 V-os referencia kiválasztása
  23.   ADC10CTL1 = ADC10SSEL_0              //csatorna = 'chan', ADC10OSC az órajel
  24.              | SHS_0                   //ADC10OSC indítja a mintavételezést
  25.              | CONSEQ_2                //Ismételt egycsatornás konverzió
  26.              | chan;                   //Az analóg csatorna kiválasztása  
  27.   ADC10DTC0 &= ~(ADC10TB+ADC10CT);     //Egy blokk mód
  28.   ADC10DTC1 = ndat;                    //ndat mérést végzünk
  29.   ADC10SA = (unsigned short)pbuf;      //adatok mentése a mutatóval jelzett helyre
  30.   ADC10CTL0 |= ENC + ADC10SC;          //Konverzió engedélyezése és indítása
  31.   while (!(ADC10CTL0 & ADC10IFG));     //Várakozás a konverzió végére
  32. }
  33.  
  34. /**------------------------------------------------------------
  35.  * Adatok átlagolása (számtani közép)
  36.  *-------------------------------------------------------------
  37.  * pbuf - az adtok tárhelyének címe
  38.  * n - az átlagolni kívánt adatok száma
  39.  */
  40. uint16_t avg(uint16_t *pbuf, uint8_t n) {
  41.   uint8_t i;
  42.   uint16_t sum;
  43.   sum = 0;
  44.   for(i=0; i < n; i++) sum += *pbuf++;
  45.  
  46.  
  47.  
  48.   return (sum/n);
  49. }
  50.  
  51. void main(void) {
  52. uint16_t data;
  53. int32_t temp;
  54.   WDTCTL = WDTPW + WDTHOLD;            //Letiltjuk a watchdog időzítőt
  55.   DCOCTL = CALDCO_1MHZ;                //DCO beállítása a gyárilag kalibrált
  56.   BCSCTL1 = CALBC1_1MHZ;               //1 MHz-es frekvenciára
  57.   P1DIR |= TXD;                        //TXD legyen digitális kimenet
  58.   P1OUT |= TXD;                        //TXD alaphelyzete: mark
  59. //--- P1.3 belső felhúzás engedélyezése -----------------------
  60.   P1DIR &= ~BIT3;                      //P1.3 legyen digitális bemenet
  61.   P1OUT |= BIT3;                       //Felfelé húzzuk, nem lefelé
  62.   P1REN |= BIT3;                       //Belső felhúzás engedélyezése  
  63. //--- Analóg csatornák engedélyezése --------------------------
  64.   ADC10AE0 |= BIT5;                    //P1.5 legyen analóg bemenet
  65.   while(1) {
  66.     delay_ms(1000);
  67.     ADC_multi_meas_REF1_5V(INCH_5,adc_data,NDATA);
  68.     data = avg(adc_data,NDATA);
  69.     sw_uart_puts("chan 5 = ");
  70.     sw_uart_out4hex(data);
  71.     temp = (int32_t)data*1500L/1023;   //A mV-okban mért feszültség
  72.     sw_uart_outdec(temp,3);            //kiírás 3 tizedesre
  73.     sw_uart_puts("V temp = ");
  74.     sw_uart_outdec(temp-500L,1);
  75.     sw_uart_puts("C chan 10 = ");    
  76.     ADC_multi_meas_REF1_5V(INCH_10,adc_data,NDATA);
  77.     data = avg(adc_data,NDATA);  
  78.     sw_uart_out4hex(data);  
  79.     temp = ((int32_t)data*270687L - 182023932L) >> 16;
  80.     sw_uart_puts(" temp = ");
  81.     sw_uart_outdec(temp,1);      
  82.     sw_uart_puts(" C\r\n");  
  83.     delay_ms(1000);  
  84.   }
  85. }

A számunkra fontos újdonságot elsősorban az ADC_multi_meas_REF1_5V() függvény jelenti. A függvény bemenő paraméterei:

  • chan - a csatorna kiválasztó bitek (ne feledjük, hogy a chan paraméter a csatorna sorszámát 12 helyiértékkel balra tolva tartalmazza!),
  • pbuf - ami egy mutató az adatok tárolására szolgáló tömbhöz,
  • ndat - ami az elvégzendő konverziók számát adja meg.  

Az ADC konfigurálásánál a korábbiakhoz képest az alábbi változásokat figyelhetjük meg:

ADC10CTL0 beállításánál az MSC bitet is '1'-be állítjuk. Ennek hatására az első triggerjel (esetünkben ADC10SC '1'-be állítása) után az előírt darabszámú konverzió egymás után, automatikusan lefut. Akésőbbiekben majd még lesz róla szó, hogy ez a nagy sietség nem mindig vezet jóra...

ADC10CTL0 beállításánál a CONSEQx = 2 beállításra van szükség (ismételt konverzió egy csatornában).

ADC10DTC0 beállításánál gyakorlatilag csak két bittel kell foglalkoznunk (a többi bit fenntartott, vagy státuszjelző). Itt most minkdkettőt nullára kell állítanunk. ADC10TB = 0 azt jelenti, hogy egyblokkos módot használunk, ADC10CT = 0 pedig azt jelenti, hogy az adatblokk átvitele után leáll a konverzió, tehát nem folyamatos módot írunk elő.

ADC10DTC1 - ebbe aregiszterbe az átviendő adatok számát kell beleírnunk.

ADC10SA - ebbe a regiszterbe a tárolásra kijelölt terület kezdőcímét kell beírni. A programban

  1. uint16_t adc_data[NDATA];

deklarálta az adattömböt. Jegyezzük meg, hogy a tömb neve is egy mutató, tehát közvetlenül használhatjuk a memórai kezdőcím megadásához. Az ADC_multi_meas_REF1_5V() függvényben azoban (legalábbis az IAR EW esetében) típuskényszerítést kell használnunk, hogy a mutató értékét beírhassuk az ADC10SA regiszterbe.

Definiáltunk egy további függvényt a mérések átlagolására avg() néven. Ez egyszerűen a mért adatok számtani közepét számolja ki, nem igényel külön magyarázatot.

A főprogram teljes mértékben az 4_adc_simple_ref1 mintapélda menetét követi. A fő különbség az, hogy a korábban használt ADC_single_meas_REF1_5V() függvény helyett most az ADC_multi_meas_REF1_5V() függvényt hívjuk meg, s minden méréssorozat után el kell végeznünk az átlagolást is. Például:  

  1. ADC_multi_meas_REF1_5V(INCH_5,adc_data,NDATA);
  2. data = avg(adc_data,NDATA);

A mintavételezés optimalizálása

Nem mindig előnyös az, ha az ADC-vel gyors egymásutánban végezzük a méréseket. Ha az ismételt konverziónak az a célja, hogy a zavarjeleket (ami többnyire az 50 Hz-es hálózatból származik) kiátlagoljuk, akkor célszerű a mintavételezési frekvenciát a zavarjel frekvenciájához igazítani.

Az alábbi 4_adc_multi2_ref1 programban a Timer0 alegység CCR0 csatornájának OUT0 (máshol ezt TA0.0 néven említi az adatlap) kimenőjelével 2 ms-onként indítunk egy konverziót. Összesen 32 mérést végzünk, ez tehát az 50 Hz-es zavarjel nagyjából három periódusát fogja át (tulajdonképpen 31 mintavételi pont lenne éppen három periódus, de ennyire már nem érdemes precízkedni, hiszen a DCO frekvenciája sem tökéletesen pontos). 

A program nagymértékben megegyezik az előzővel, a Timerrel történő triggerelést pedig az előző oldalon bemutatott 4_adc_pwm2 programból puskázhatjuk ki. A programlistában szokás szerint csak a kritikus részeket mutatjuk be, az unásig ismételt rutinokat itt nem listáztuk ki.

6_2. lista: Részletek a 4_adc_multi2_ref1 programból

  1. #include "io430.h"
  2. #include "stdint.h"
  3.  
  4. #define TXD       BIT1                 //TXD a P1.1 lábon
  5. #define NDATA     32                   //Adatok száma
  6. uint16_t adc_data[NDATA];              //Ide kerülnek a mérési adatok
  7.  
  8. /*-------------------------------------------------------------
  9.  * Sorozat mérés egy ADC csatornában, 1,5 V a belső referencia
  10.  * Feltételezzük, hogy külső jel mérése esetén az analóg funkció
  11.  * már engedélyezve van (az ADC10AE0 regiszterben).
  12.  *-------------------------------------------------------------
  13.  */
  14. void ADC_multi2_meas_REF1_5V(uint16_t chan, uint16_t *pbuf, uint8_t ndat) {
  15.   ADC10CTL0 &= ~ENC;                   //Az ADC letiltása újrakonfiguráláshoz
  16.     while (ADC10CTL1 & BUSY);          //Várakozás a foglaltság végére
  17.   ADC10CTL0 = ADC10SHT_3               //mintavétel: 64 óraütés
  18.              | ADC10ON                 //Az ADC bekapcsolása
  19.              | SREF_1                  //VR+ = VREF+ és VR- = AVSS
  20.              | REFON                   //Belső referencia bekapcsolása
  21.              | ADC10IE;                //Programmegszakítás engedélyezése
  22.                                        //1,5 V-os referencia kiválasztása
  23.   ADC10CTL1 = ADC10SSEL_0              //csatorna = 'chan', ADC10OSC az órajel
  24.              | SHS_2                   //Hardveres triggerelés (TA0.0)
  25.              | CONSEQ_2                //Ismételt egycsatornás konverzió
  26.              | chan;                   //Az analóg csatorna kiválasztása  
  27.   ADC10DTC0 &= ~(ADC10TB+ADC10CT);     //Egy blokk mód
  28.   ADC10DTC1 = ndat;                    //ndat mérést végzünk
  29.   ADC10SA = (unsigned short)pbuf;      //adatok mentése a mutatóval jelzett helyre
  30.   ADC10CTL0 |= ENC;                    //Konverzió engedélyezése
  31.    __low_power_mode_0();               //CPU altatás a méréssorozat végéig
  32.   ADC10CTL0 &= ~ENC;                   //Az ADC letiltása újrakonfiguráláshoz  
  33. }
  34.  
  35.  
  36. void main(void) {
  37. uint16_t data;
  38. int32_t temp;
  39.   WDTCTL = WDTPW + WDTHOLD;            //Letiltjuk a watchdog időzítőt
  40.   DCOCTL = CALDCO_1MHZ;                //DCO beállítása a gyárilag kalibrált
  41.   BCSCTL1 = CALBC1_1MHZ;               //1 MHz-es frekvenciára
  42.   P1DIR |= TXD;                        //TXD legyen digitális kimenet
  43.   P1OUT |= TXD;                        //TXD alaphelyzete: mark
  44. //--- P1.3 belső felhúzás engedélyezése -----------------------
  45.   P1DIR &= ~BIT3;                      //P1.3 legyen digitális bemenet
  46.   P1OUT |= BIT3;                       //Felfelé húzzuk, nem lefelé
  47.   P1REN |= BIT3;                       //Belső felhúzás engedélyezése  
  48. //--- Analóg csatornák engedélyezése --------------------------
  49.   ADC10AE0 |= BIT5;                    //P1.5 legyen analóg bemenet
  50. //-- Timer_A beállítása: OUT0=500 Hz, SMCLK, "Felfelé számlálás"
  51.     TACCR0 = 999;                      //A periódusidő 1000 us
  52.     TACCTL0 = OUTMOD_4;                //Toggle mód (OUT0 500 Hz lesz)
  53.     TACTL = TASSEL_2 |                 //SMCLK az órajel forrása
  54.                 ID_0 |                 //1:1 osztás beállítása
  55.                 MC_1 |                 //Felfelé számláló mód
  56.                 TACLR;                 //TAR törlése
  57.  
  58.   while(1) {
  59.     ADC_multi2_meas_REF1_5V(INCH_5,adc_data,NDATA); //A5 csatorna mérése
  60.     data = avg(adc_data,NDATA);        //Az eredmények átlagolása
  61.     sw_uart_puts("chan 5 = ");
  62.     sw_uart_out4hex(data);
  63.     temp = (int32_t)data*1500L/1023;   //A mV-okban mért feszültség
  64.     sw_uart_outdec(temp,3);            //kiírás 3 tizedesre
  65.     sw_uart_puts("V temp = ");
  66.     sw_uart_outdec(temp-500L,1);       //A TC1047A hőmérsékletének kiíratása
  67.     sw_uart_puts("C chan 10 = ");    
  68.     ADC_multi2_meas_REF1_5V(INCH_10,adc_data,NDATA); //Mérés a belső hőmérővel
  69.     data = avg(adc_data,NDATA);        //Az eredmények átlagolása
  70.     sw_uart_out4hex(data);  
  71.     temp = ((int32_t)data*270687L - 182023932L) >> 16;
  72.     sw_uart_puts(" temp = ");
  73.     sw_uart_outdec(temp,1);            //A hőmérséklet kiíratása 1 tizedesre
  74.     sw_uart_puts(" C\r\n");  
  75.     delay_ms(2000);                    //2 s késleltetés
  76.   }
  77. }
  78.  
  79. //----------------------------------------------------------------------
  80. // ADC10 megszakítás kiszolgálása: CPU felébresztése a méréssorozat végén
  81. //----------------------------------------------------------------------
  82. #pragma vector = ADC10_VECTOR
  83. __interrupt void ADC10_ISR (void)      //A jelzőbit automatikusan törlődik
  84. {
  85.   __low_power_mode_off_on_exit();      //Felébresztjük az alvó CPU-t
  86. }

Az ADC_multi2_meas_REF1_5V() függvényben az ADC konfigurálásánál ügyeljünk arra, hogy most nem szabad '1'-be állítani az MSC bitet, mert minden konverziót külön-külön akarunk triggerelni, hiszen épp ez volt a fő célunk ennek a programnak a megírásánál. AZ ADC10CTL1 regiszternél  hardveres triggerelés TA0.0-val (SHSx=2) és ismételt konverzió egy csatornában (CONSEQx=2) beállításások kellenek.

Az ADC megszakítását engedélyezzük az ADC10IE bit '1'-be állításával, de a megszakítás a DTC használata miatt most csak az előírt számú mérés elvégzése után következik be, amikor már minden eredmény eltárolásra került.

A DTC konfigurálása megegyezik az előző példában bemutatottakkal. A Timer konfigurálását viszont nem az ADC_multi2_meas_REF1_5V() függvényben, hanem a főprogram elején végezzük el, mivel ezt elég egyszer végrehajtani.

Az ADC beállítások végén most nem indítunk konverziót ADC10SC '1'-be billentésével, hanem alvás módba tesszük a CPU-t, ahonnan csak az ADC megszakítás ébreszti fel a méréssorozat végén. Természetesen az altatás és a megszakítás helyett most is várakozhattunk volna szoftveres várakozó hurokban az ADC10IFG bebillenésére úgy, mint az előző programban.

A Timer CCR0 csatornájának beállítása hasonló az előző oldalon bemutatott 4_adc_pwm2 programban bemutatotthoz. A különbség annyi, hogy az időzítő bemenőjel most az 1 MHz-es SMCLK lesz, leosztás nélkül, a TACCR0 periódus regisztert pedig 999-re állítjuk (a periódus 1000 μs). Ne feledjük azonban el, hogy a toggle módú (OUTMOD_4) kimenőjel csak minden második periódusban kerül azonos fázisba, tehát a kimenőjel periódusideje 2000 μs, azaz 2 ms lesz!

Az ADC megszakítás kiszolgálásakor nincs más teendőnk, mint a CPU felébresztése. A feléledés után a ADC_multi2_meas_REF1_5V() függvényben a biztonság kedvéért letiltjuk a további  ADC konverziókat (ENC=0), bár lehet, hogy erre nincs szükség.

A program futási eredménye az alábbi ábrán látható.

4_adc_multi2_ref1.png

 


A cikk még nem ért véget, lapozz!
Következő: »»   6 / 8
Értékeléshez bejelentkezés szükséges!
Bejelentkezés

Belépés

Hirdetés
XDT.hu
Az oldalon sütiket használunk a helyes működéshez. Bővebb információt az adatvédelmi szabályzatban olvashatsz. Megértettem