1
Fork 0
appunti-steffo/5 - TPS/tpsit_msp430.md

9.1 KiB

MSP430

Licenza CC-BY-3.0-IT

Copyright (c) 2019 Stefano Pigozzi

In pratica, potete riutilizzare il file dove, come e quando vi pare, ma dovete inserire il mio nome nei documenti in cui questo viene usato.

Capito, prof. ______? Non si usano appunti di altri studenti senza dargli credito!

Prerequisito: Le maschere

Prima di programmare l'MSP430 bisogna sapere cosa sono e come usare le maschere (bit mask) in C.

Una maschera è un'operazione che permette di modificare solo alcuni bit di una variabile a più di un bit, senza cambiare gli altri.

Esempi

unsigned int variabile = 0;

//Imposta il secondo bit a 1, senza modificare gli altri 7.
variabile |= 0b0000 0010;
//Imposta il quinto bit a 1, senza modificare gli altri 7.
variabile |= 0b0001 0000;

//In questo punto del codice, variabile vale 0001 0010.

//Imposta il secondo bit a 0, senza modificare gli altri 7.
variabile &=~ 0b0000 0010;

//In questo punto del codice, variabile vale 0001 0000.

In particolare, se si sta modificando codice dell'MSP, è possibile usare le costanti BITn:

unsigned int variabile = 0;

//Imposta il secondo bit a 1, senza modificare gli altri 7.
variabile |= BIT1;
//Imposta il quinto bit a 1, senza modificare gli altri 7.
variabile |= BIT4;

//In questo punto del codice, variabile vale 0001 0010.

//Imposta il secondo bit a 0, senza modificare gli altri 7.
variabile &=~ BIT1;

//In questo punto del codice, variabile vale 0001 0000.

Infine, puoi usare una maschera per leggere un solo bit alla volta!

//Leggi solo il quinto bit.
variabile & BIT4 // --> 0001 0000

//Esegui il codice se il quinto bit è 1
if(variabile & BIT4) {
    //Codice goes here
}

//Esegui il codice se il quinto bit è 0
if(!(variabile & BIT4)) {
    //It'sa-me, code!
}

Prerequisito: Differenze nel C dell'MSP430

Il C dell'MSP430 è simile al C normale, ma ha alcune piccole differenze.

  • L'MSP430 NON HA i bool, vanno rappresentati come int che sono false se sono 0 e true se sono qualsiasi altro numero.
  • Alcune variabili, come P1IN, vengono cambiate dall'esterno del codice (vedi sotto).

Usare le porte

Le porte

Le porte sono quei pin/buchi che vedete sull'MSP sulla sinistra e sulla destra.

Hanno un nome che va da P1.0 a P1.7 e da P1.0 a P5.0.

Ogni porta ha cinque bit associati:

  • DIR(ezione): specifica se una particolare porta è un output (1) o un input (0).
  • OUT(put): se la porta è un output, decide cosa mandare fuori da quel bit, se un 0 o un 1.
  • IN(put): ha sempre il valore dell'input dell'ingresso desiderato; non può essere modificata.
  • SEL(ezione): visto che tutti i pin possono fare due cose diverse, seleziona quale cosa delle due devono fare: funzionano da Input/Output (0) oppure usano la funzione secondaria (1).
  • REN: abilita / disabilita una resistenza di pullup/down, assicurati che sia sempre attiva quando usi qualcosa come input o potrebbero succedere cose inaspettate!

Per (s)comodità, nel codice C questi bit sono raggruppati in gruppi di 8:
tutte le porte da P1.0 a P1.7 sono raggruppate nelle variabili P1DIR, P1OUT, P1IN, P1SEL e P1REN; quelle da P2.0 a P2.7 in P2DIR, P2OUT, etc.

//P1.5 è un input
P1DIR &=~ BIT5;
//Assicurati che P1REN sia a 1, P1OUT sia a 0 e P1SEL sia a 0 quando usi un input!
P1REN |= BIT5;
P1SEL &=~ BIT5;
P1OUT &=~ BIT5;
//P1.5 è un output
P1DIR |= BIT5;

//Fai uscire un 1 da P1.5
P1OUT |= BIT5;
//Fai uscire uno 0 da P1.5
P1OUT &=~ BIT5;

Le funzioni

Nonostante il C dell'MSP430 sia una cosa paciugata e stranissima, per fortuna le funzioni rimangono uguali a quelle del C standard:

int nomeFunzione(int parametri) {
    //Codice qui...
}

Esempio

//Scrivi una funzione di inizializzazione
void initAll() {
    //Ho attaccato un led a P1.0; è quindi un I/O; per la precisione, un output.
    P1SEL &=~ BIT0;
    P1DIR |= BIT0;
    //Ho attaccato un led a P4.7; è quindi un I/O; per la precisione, un output.
    P4SEL &=~ BIT7;
    P4DIR |= BIT7;
    //Ho attaccato un interruttore a P1.1; è quindi un I/O; per la precisione, un input.
    P1DIR &=~ BIT1;
    P1REN |= BIT5;
    P1SEL &=~ BIT5;
    P1OUT &=~ BIT5;
}

//Scrivi una funzione che cambi lo stato del led di P1.0
void led1(int state) {
    if(state != 0) {
        //Setta a 1
        P1OUT |= BIT0;
    }
    else {
        //Setta a 0
        P1OUT &=~ BIT0;
    }
}

//Scrivi una funzione che cambi lo stato del led di P4.7
void led2(int state) {
    //Posso omettere l' != 0, perchè è implicito in tutti gli if senza altre operazioni
    if(state) {
        //Setta a 1
        P4OUT |= BIT7;
    }
    else {
        //Setta a 0
        P4OUT &=~ BIT7;
    }
}

//Scrivi una funzione che legga il valore dello switch in P2.1
int readSwitch() {
    return (P2IN & BIT1);
}

//Puoi anche usare delle variabili nel tuo codice!
//Creo una variabile globale.
int statoPrecedente;

//Scrivi una funzione che controlli se lo switch P2.1 è appena stato spinto
int debounce() {
    int statoAttuale = readSwitch();
    if(statoAttuale != statoPrecedente && statoAttuale) {
        statoPrecedente = statoAttuale;
        return 1;
    }
    else {
        statoPrecedente = statoAttuale;
        return 0;
    }
}

//Scrivi una funzione che cambi stato al led1
int toggleLed1() {
    int statoAttuale = P1OUT & BIT0;
    if(statoAttuale) {
        led1(0);
    }
    else {
        led1(1);
    }
}

//...c'è altro, ma non ho il testo della verifica, quindi non lo so...

Usare il timer

TL;DR in fondo, guarda quello se hai fretta!

Il timer è una delle funzioni dell'MSP: serve per contare il tempo con una precisione altissima.

Ha millemila impostazioni, ma al Fermi se ne è sempre usata praticamente solo una.

L'MSP ha due timer che possono funzionare contemporaneamente: si chiamano TA0 (TAimer 0... Cosa? Non si scrive così timer?) e TA1 (Well, ci ho provato).

Qui sotto parlerò solo di TA1, ma TA0 si configura nello stesso identico modo; basta mettere lo 1 invece che l'1 nel nome della variabile!

Velocità del timer

Il timer dell'MSP può andare a due velocità:

  • TASSEL__SMCLK: 1 MHz = 1 000 000 Hz
  • TASSEL__ACLK: 32 KHz = 32 000 Hz

Sì, quelli sono due underscore. E' fatto così.

Inoltre, queste due velocità possono essere diminuite con l'impostazione ID, che divide per un certo numero la frequenza:

  • ID__1: Mantiene la frequenza uguale. What's even the point?
  • ID__2: Divide la frequenza per due.
  • ID__4: Divide la frequenza per quattro. Inizi a vedere un pattern?
  • ID__8: Divide la frequenza PER OTTO! WOW!

Ricordati di non confonderti con la versione con un underscore solo, che fa una cosa diversa per qualche motivo...

Combinate, le impostazioni danno questi risultati:

TASSEL__SMCLK TASSEL__ACLK
ID__1 1 000 000 tick/sec 32 000 tick/sec
ID__2 500 000 tick/sec 16 000 tick/sec
ID__4 250 000 tick/sec 8 000 tick/sec
ID__8 125 000 tick/sec 4 000 tick/sec

Configurare la durata

Per configurare il timer, bisogna mettere il numero di tick per cui si vuole che conti nella variabile TA1CCR0.

Come calcolare il numero di tick? Basta fare numero_secondi * frequenza_timer, prendendo la frequenza timer dalla tabella sopra!

Esempio

Voglio che il timer duri mezzo secondo.

Decido di usare TASSEL_ACLK e ID__8, perchè è il più facile da usare.

Calcolo il numero di tick: 0.5 secondi * 4000 tick/sec = 2000 tick.

Allora, scrivo questo:

TA1CCR0 = 2000;

Tante altre cose inutili

Il timer ha tante, tante, tante altre impostazioni che sono tanto, tanto, tanto inutili.

Queste vanno inserite nella variabile TA1CTL (TAimer 1 ConTroL).

/*
    clock_scelto: metti TASSEL__ACLK o TASSEL__SMCLK in base a cosa hai scelto di usare prima
    divisore_scelto: metti ID__1, ID__2, ID__4 o ID__8 in base a cosa hai scelto di usare prima
    MC__UP: fai in modo che il timer vada avanti (duh)
    TAIE: attiva/disattiva il timer (TAimer Interrupt Enable)
    TACLR & ~TAIFG: resetta il timer
*/
TA1CTL = clock_scelto | divisore_scelto | MC__UP | TAIE | TACLR & ~TAIFG;

Funzione da chiamare quando finisce il timer

Per marcare una funzione come "quella da chiamare quando finisce il timer", bisogna aggiungere __attribute__ (( interrupt(TIMER1_A1_VECTOR) )) dopo void, ma prima del nome della funzione.

Anche qui sì, sono due parentesi tonde.

Tutto il resto del codice verrà interrotto mentre questa funzione è in esecuzione, e questa potrebbe essere chiamata mentre un'altra è a metà.

Esempio

void __attribute__ (( interrupt(TIMER1_A1_VECTOR) )) timer1() {
    //Fai cose!
}

TL;DR: Too long, didn't read

Per eseguire una funzione dopo X secondi, scrivi questo.

void avviaTimer1(float secondi) {
    TA1CCR0 = (int) (secondi * 4000)
    TA1CTL = TASSEL__ACLK | ID__8 | TACLR | TAIE | MC__UP & ~TAIFG; 
}

void __attribute__ ((interrupt(TIMER1_A1_VECTOR))) timer1() {
    //Codice che vuoi che venga eseguito!
}