Come già detto i nostri prodotti sono schede di sviluppo per i microcontrollori della famiglia PIC32 di Microchip. Per chi non ha mai lavorato con questi microcontrollori abbiamo pensato di dedicare un po’ di spazio per indicare quali siano i primi passi consigliati a tutti coloro che vogliono iniziare ad esplorare questo mondo. Da un punto di vista della programmazione bisogna dotarsi dell’ambiente MPLAB di Microchip comprensivo di compilatore C per PIC32, prodotto questo denominato MPLAB C32 Suite e scaricabile in versione LITE gratuitamente dal sito di Microchip. Consigliamo a tutti di seguire questi passi per la corretta configurazione dell’ambiente di sviluppo:
1. All’indirizzo http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=1406&dDocName=en019469&part=SW007002#P175_5429
trovate i link per scaricare l’ultima versione dell’ambiente di lavoro MPLAB, gratuito e con il quale sarete in grado di sviluppare software per tutte le famiglie di microcontrollori PIC. Alla stessa pagine è disponibile un manuale utente in formato PDF che rappresenta un buon punto di partenza per conoscere l’ambiente di sviluppo.
2. In fase di installazione dell’ambiente integrato MPLAB potete scegliere di effettuare l’installazione completa oppure quella personalizzata (custom), purché abbiate cura di non togliere la spunta dalla voce MPLAB C32 Suite. Questo farà sì che verrà installato anche il compilatore dal linguaggio C specifico per PIC32.
3. All’indirizzo
http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2615&dDocName=en532454
trovate i link per scaricare gli ultimi aggiornamenti del compilatore PIC32 LITE. Il compilatore in questa versione gratuita è limitato nelle funzionalità solo per quanto concerne le possibilità di ottimizzazione del codice macchina generato. Scaricate l’eseguibile e installate gli aggiornamenti per avere l’ambiente di sviluppo più aggiornato.
La figura riporta una schermata di MPLAB IDE dalla quale possiamo brevemente ricavare poche fondamentali caratteristiche dell’ambiente:
- In MPLAB lavoriamo per ‘Workspaces’, spazi di lavoro: questi rappresentano la vetta della gerarchia di documenti che vengono logicamente associati durante la produzione di software per microcontrollori; ogni workspace viene persistito su disco con un file di suffisso .mcw; nella figura notiamo come a sinistra in alto, subito sotto la barra dei comandi troviamo indicato il workspace correntemente aperto
- Il workspace contiene il progetto, che è il diretto contenitore dei documenti che contengono il codice che produce, una volta compilato, il firmware; ogni progetto viene persistito su disco con un file di suffisso .mcp; a sinistra in figura vediamo il progetto espanso con la classica struttura ad albero che mostra i file di codice organizzati in una struttura di cartelle su disco
- Tra le cartelle presenti in un progetto segnaliamo la cartella contenente i file di codice sorgente C relativo all’implementazione delle funzioni, denominata ‘Source Files’ e quella contenente i file header (‘Header Files’), tipicamente utilizzati per dichiarazioni e direttive del precompilatore
- La compilazione del progetto avviene tramite la voce di menù Project/Make (o, in alternativa, Project/Build All, che rigenera anche tutti i file intermedi)
- Se non ci sono errori di compilazione, Il compilatore genererà un file con suffisso .hex, che contiene in forma binaria i dati che dovranno essere “scritti” nella memoria flash del microcontrollore
- La scrittura, detta “programmazione” in gergo, viene effettuata tramite un dispositivo esterno al microcontrollore che può assumere la funzione di semplice programmatore o di debugger. La selezione del dispositivo in questione viene effettuata in MPLAB IDE tramite i due menù “Programmer” e “Debugger”. Nei nostri esempi utilizzeremo il debugger integrato nelle schede PIC32 Starter Kit, pertanto sarà necessario scegliere la voce “PIC32 Starter Kit” dal menù “Debugger” di MPLAB IDE
Scrittura del Firmware
Ci proponiamo ora di realizzare un semplice Firmware dimostrativo che ci permetterà di sperimentare con il funzionamento della scheda Tipo 1. Il programma in questione permetterà l’apertura/chiusura dei relé sia da pulsante (utilizzando un pattern differente per ciascuno dei 3 pulsanti dello Starter Kit) che da comandi terminale ricevuti via porta seriale. L’applicazione invierà inoltre comunicazioni di debug attraverso una seconda porta seriale.
Il progetto completo può essere scaricato qui.
La struttura del progetto è la seguente:
- Main – Contiene la configurazione dei parametri di esecuzione del firmware (come ad es. la frequenza del clock che gestisce le periferiche del microcontrollore), le funzioni di inizializzazione dei vari componenti utilizzati (porte seriali, porte I/O, interrupt, cache, ecc.) e, soprattutto, la definizione della funzione main(), il cosiddetto entry-point del firmware, ossia la prima funzione eseguita una volta che il microcontrollore viene alimentato (o resettato)
- Explore – Contiene alcune funzioni di utilità generale, quali quelle per la gestione dei ritardi, scritte da Lucio Di Jasio, autore del libro “Exploring the PIC32” edito da Newnes/Elsevier
- Shell – Contiene le funzioni relative al parsing dei comandi ricevuti via terminale tramite porta seriale
- Uart_Helpers – Contiene le funzioni per l’utilizzo delle porte seriali/UART
- CNSKit_Helpers – Contiene le funzioni per la gestione delle notifiche di cambiamento relative agli ingressi digitali attivati
Esplorando il codice relativo a main.c, che costituisce il flusso principale dell’applicazione, è possibile notare che la parte di inizializzazione dei parametri di funzionamento del microcontrollore è la seguente:
1: //
2: //
3: // Section: Configuration bits
4: //
5: //
6: #pragma config FPLLODIV = DIV_1, FPLLMUL = MUL_20, FPLLIDIV = DIV_2,
7: #pragma config FCKSM = CSECME, FPBDIV = DIV_8
8: #pragma config OSCIOFNC = ON, POSCMOD = XT, FSOSCEN = ON, FNOSC = PRIPLL
9: #pragma config CP = OFF, BWP = OFF, PWP = OFF
10: //
11: //
12: // Section: System Macros
13: //
14: //
15: #define GetSystemClock() (80000000ul)
16: #define GetPeripheralClock() (GetSystemClock()/(1 <<
17: #define GetInstructionClock() (GetSystemClock())
Le parti più significative da notare sono:
· (Riga 16) quella che, in funzione della frequenza di lavoro del microcontrollore (80 MHz in questo caso), permette di ottenere un riferimento temporale preciso
· (Riga 17) quella che, in funzione del moltiplicatore utilizzato internamente, definisce il clock utilizzato dalle periferiche del microcontrollore, quali le porte UART
L’inizializzazione dell’ambiente di esecuzione del firmware (stati di attesa, cache, abilitazione interrupt) viene effettuata dalla seguente funzione:
1: void initSystem()
2: { 3: SYSTEMConfigWaitStatesAndPB( GetSystemClock() );
4:
5: // Enable the cache for the best performance
6: CheKseg0CacheOn();
7:
8: // enable multi-vector interrupts
9: INTEnableSystemMultiVectoredInt();
10: }
La funzione seguente invece determina quali porte I/O verranno utilizzate, in che direzione (ingresso o uscita) e, nel caso delle uscite, con quali stati logici iniziali:
1: void initPorts()
2: { 3: // Connettore AUX
4: mPORTBSetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2 | BIT_3);
5: mPORTBClearBits(BIT_0 | BIT_1 | BIT_2 | BIT_3);
6:
7: // Relays
8: mPORTESetPinsDigitalOut(BIT_0 | BIT_1 | BIT_2 | BIT_3);
9: mPORTEClearBits(BIT_0 | BIT_1 | BIT_2 | BIT_3);
10:
11: // Starter Kit
12: mPORTDSetPinsDigitalIn(BIT_6 | BIT_7 | BIT_13);
13: }
La funzione main() consiste in una parte di inizializzazione ed una, eseguita indefinitamente (ossia fino a che il microcontrollore non viene resettato o disalimentato).
La parte di inizializzazione è costituita da:
1: initSystem();
2: initPorts();
3: initUart(UART1,BAUD_RATE_UART1,GetPeripheralClock());
4: initUart(UART3A,BAUD_RATE_UART3A,GetPeripheralClock());
5: initSKitCN();
6:
7: register_cn_callback(cn_callback);
8:
9: SendDataBufferUartId(UART1,g_sSplash,sizeof(g_sSplash));
10: SendDataBufferUartId(UART3A,g_sSplash,sizeof(g_sSplash));
Di queste:
· (Righe 3 e 4) inizializzano le porte UART indicando quali moduli attivare (1 e 3A in questo caso) e con quale baud rate (tramite costanti che sono inizializzate nel progetto di esempio a 9600 bps)
· (riga 5) inizializza la gestione delle notifiche di cambiamento degli ingressi digitali abilitati
· (riga 7) “registra” quale funzione deve essere invocata dal sistema di gestione delle notifiche di cambiamento degli ingressi a seguito del rilevamento di un cambiamento (fronte di salita, discesa o entrambi sono stabiliti dall’implementazione della funzione initSKitCN()
· (righe 9 e 10) inviano alle due porte seriali inizializzate delle stringhe di “benvenuto” (o splash, come viene chiamato in gergo)
La parte relativa al ciclo infinito è invece rappresentata dal seguente frammento di codice:
1: while(1)
2: { 3: if(KbHitUartId(UART3A))
4: { 5: GetDataBufferUartId(UART3A,buf,sizeof(buf));
6: shellretval=shell_exec_cmd(buf);
7: sprintf(debugbuf,"Shell returned %u\r",shellretval);
8: SendDataBufferUartId(UART1,debugbuf,strlen(debugbuf));
9: }
10:
11: PORTB=(counter++) & 0xF;
12:
13: Delayms(1);
14: }
Il corpo del ciclo è essenzialmente costituito da:
· (Riga 5) lettura dei dati di ingresso dalla porta seriale 3A
· (Riga 6) parsing della stringa ricevuta
· (Righe 7 e 8) debug sulla porta seriale 1
La parte più interessante dell’applicazione è costituita dalla funzione di callback (ossia non chiamata direttamente da codice nostro ma da parte del sistema, in questo caso attraverso un gestore di interrupt di tipo “CN” (ossia “change notification”):
1: void cn_callback(DWORD change_mask,DWORD state_mask)
2: { 3: if(change_mask & BIT_13)
4: { 5: if(!(state_mask & BIT_13))
6: { 7: PORTSetBits(IOPORT_E,BIT_0 | BIT_1 | BIT_2 | BIT_3);
8: }
9: else
10: { 11: PORTClearBits(IOPORT_E,BIT_0 | BIT_1 | BIT_2 | BIT_3);
12: }
13: }
14:
15:
16: if((change_mask & BIT_6) && !(state_mask & BIT_6))
17: { 18: PORTToggleBits(IOPORT_E,1<<g_relays++);
19:
20: if(g_relays>3) g_relays=0;
21: }
22:
23:
24: if((change_mask & BIT_7) && !(state_mask & BIT_7))
25: { 26: PORTToggleBits(IOPORT_E,BIT_0 | BIT_1 | BIT_2 | BIT_3);
27: }
28: }
In questa funzione, che riceve in ingresso rispettivamente una bitmask relativa agli ingressi che hanno registrato un cambiamento ed una bitmask relativa ai loro stati correnti, avviene gran parte della logica dell’applicazione, in particolare:
· (Riga 3) se viene premuto o rilasciato il pulsante N.3 dello Starter Kit (corrispondente al bit 13 della porta D, abbreviato RD13), il programma verifica lo stato del pulsante (0àpremuto, 1àrilasciato, ossia a logica negativa, dettata dall’utilizzo delle resistenze di pullup interne al microcontrollore per questi ingressi) e attiva/disattiva simultaneamente tutti i relé rispettivamente se si tratta di una pressione o di un rilascio del pulsante
· (Riga 16) se viene premuto il pulsante N.1 (RD6), viene scambiato lo stato del relé rappresentato dal valore della variabile intera g_relays, che varia in maniera circolare tra 0 e 3
· (Riga 24) se viene premuto il pulsante N.2 (RD7), viene scambiato lo stato di tutti i relé
Programmazione ed esecuzione del firmware
Per vedere il firmware in azione è sufficiente effettuare la compilazione (tramite la voce di menu Project/Make) e, se non ci sono errori, scegliere la voce di menù “Program All Memories”.
A programmazione avvenuta il firmware è in realtà fermo, in attesa che l’ambiente di sviluppo dia il via effettivo. Questo comportamento è giustificato dal fatto che il PIC32 Starter Kit viene visto da MPLAB IDE come un debugger anziché come un programmatore vero e proprio. L’avviamento del firmware avviene quindi tramite l’azione esplicita del comando Run, rappresentato sulla toolbar dal triangolino celeste (il simbolo “Play” dei registratori, per intenderci).
Se non avete a disposizione la scheda, potete dare un’occhiata al firmware in funzione in questo video YouTube.
Attenzione! Se volete utilizzare il firmware al di fuori dell’ambiente di sviluppo, come tipicamente avviene una volta che avete già effettuato il debugging e ne siete soddisfatti, dovrete aggiungere la direttiva:
#pragma config DEBUG = OFF
nella parte di inizializzazione del firmware e successivamente compilare l’applicazione in modalità “Release” anziché “Debug”, agendo sull’apposita Combo presente sulla toolbar di MPLAB IDE. L’ambiente di sviluppo segnalerà diversi messaggi di avvertimento, dei quali potremo prendere atto senza particolari rischi. A seguito della programmazione del microcontrollore stavolta il firmware partirà automaticamente, per rimanere in funzione in maniera autonoma per ore, giorni ,mesi o addirittura anni!