• 990.020 MuIN USB - Usare la MuIN USB come demoboard

    La 990.020 MuIN USB viene fornita con un firmware precaricato che consente di svolgere numerose operazioni in ambito sia robotico che non: controllo servocomandi, PWM, lettura grandezze analogiche, comunicazione con sensori I2C ecc. Se non vogliamo utilizzare il firmware fornito di serie ma piuttosto incominciare a muovere i primi passi nel mondo dei pic18, scrivendo da noi i nostri programmi, è possibile farlo in maniera più che semplice.

    Ricordiamo, difatti, che il firmware di serie della MuIN USB comprende anche un comodo bootloader, che ci permette di caricare i programmi senza dover acquistare un programmatore, ma unicamente sfruttando la connessione USB e il software gratuito HID Bootloader (che potete scaricare nella sezione downloads).
    Le modalità con cui caricare un nuovo firmware tramite booloader sono illustrate in questo articolo
    In aggiunta, disponendo la MuIN USB della connessione USB è possibile alimentare la scheda dalla porta USB (leggere il manuale d'uso).

    Andiamo quindi a descrivere due semplici programmi che fanno uso di tale caratteristica. E' possibile scaricare questi due programmi di esempio, utilizzabili anche come "scheletro" delle nostre applicazioni, in fondo all'articolo. Entrambi i programmi eseguono un compilto basilare: far lampeggiare il led posto di serie sulla scheda e collegato alla porta RA4. Il compito è eseguito dai due programmi di esempio in maniera differente: il primo lo esegue tramite semplici cicli di ritardo e il secondo tramite le interruzioni (o interrupts).

    Per poter compilare questi programmi è necessario avere installato MPLAB IDE e MPLAB C18.

    Incominciamo con una breve descrizione della struttura di base di questi due programmi di esempio.

    Il nostro programma principale deve sempre cominciare con l'inclusione del file header che contiene i nomi mnemonici e le definizioni del pic montato sulla scheda:

    Code: [View]
    #include <p18f2550.h>
    Negli esempi è stata quindi inclusa una macro che permette di specificare al compilatore se riservare o meno la memoria necessaria al bootloader. L'istruzione successiva che incontriamo è pertanto la seguente:

    Code: [View]
    #define PROGRAMMABLE_WITH_USB_HID_BOOTLOADER
    Lasciando la riga così com'è, il compilatore riserva la memoria necessaria al bootloader. Commentando la riga, invece, non viene riservata memoria per il bootloader e il programma, quindi, dovrà essere necessariamente caricato sul picmicro mediante un programmatore. Se volete utilizzare il bootloader fornito di serie lasciate la riga così com'è.
    Si ricorda che, quando si fa uso del bootloader, bisogna anche utilizzare un file linker che tiene appunto conto della presenza di un bootloader. Negli esempi è già incluso il linker modificato. Qualora non si voglia usare il bootloader basta non includere nessun linker: il compilatore includerà quello standard per il pic in uso.
    Incontriamo quindi le direttive #pragma che ci permettono, tra le altre cose, di specificare i fuses di configurazione. In realtà utilizzando il bootloader non è necessario specificare i fuses di configurazione dal momento che sono già stati preprogrammati sulla scheda e non bisogna riprogrammarli in quanto il bootloader potrebbe non funzionare più.

    I fuses sono stati comunque inclusi a titolo di esempio e nel caso in cui non si voglia far uso del bootloader (in ogni caso, anche se presenti nel programma, non verranno programmati dal bootloader a meno che non siate voi a specificarlo esplicitamente nel programma HID Bootloader: opzione disattivata di default che si consiglia di non attivare!).

    Di particolare interesse sono le prime 4 direttive che permettono di specificare le frequenze di funzionamento:

    Code: [View]
    #pragma config PLLDIV   = 3         // (12 MHz crystal on MuIN USB)
    #pragma config CPUDIV   = OSC1_PLL2   
    #pragma config USBDIV   = 2         // Clock source from 96MHz PLL/2
    #pragma config FOSC     = HSPLL_HS
    Con il valore HSPLL_HS stiamo specificando di voler utilizzare un quarzo/oscillatore ad alta velocità (ovvero superiore a 4MHz) combinato con l'utilizzo del PLL. Il PLL è una circuiteria interna al pic che permette di moltiplicare il valore di frequenza dell'oscillatore. Ricordo che, sulla MuIN USB, è montato un quarzo da 12MHz. Il PLL del 18F2550 richiede che la frequenza in ingresso sia di 4MHz e fornisce in uscita una frequenza di 96MHz che poi verrà ancora ulteriormente divisa.

    Per tale motivo, in ingresso al PLL abbiamo un prescaler che permette di dividere la frequenza in ingresso per portarla a 4MHz: avendo un quarzo da 12MHz e dovendo ottenere 4MHz, il prescaler deve essere impostato per fornire una divisione per 3, questo è il senso della word di configurazione PLLDIV = 3.

    Abbiamo detto che in uscita dal PLL abbiamo 96MHz. Questa frequenza sarà ulteriormente divisa per poter alimentare separatamente la CPU e il modulo USB. Il funzionamento del modulo USB richiede una frequenza di 48MHz, questo è il motivo per il quale la word USBDIV è impostata su 2 (96/2=48). La CPU, invece, ha vari valori di divisione tra cui scegliere, il minimo valore è ancora 2 (CPUDIV = OSC1_PLL2) e scegliamo questo. La CPU lavorerà quindi a 48MHz anche se il quarzo è soltanto da 12MHz!

    Come detto tali impostazioni vengono programmate unicamente se non si fa uso del bootloader, in ogni caso queste sono anche le impostazioni già preprogrammate sulla scheda. Con un clock (o meglio una FOSC) di 48MHz, avendo i picmicro serie 18 una frequenza di esecuzione delle istruzioni pari a FOSC/4, otteniamo che il tempo necessario per un ciclo completo di istruzioni (Tcy) è pari a 4/FOSC = 0,083uSec.

    Le successive istruzioni nel file di esempio permettono di rimappare la memoria programma in base alla scelta di fare uso o meno del bootloader. In particolar modo è importante rimappare i vettori di interruzione: bisognerà lasciare le istruzioni tal quale senza fare modifiche fino ad arrivare alle righe:

    Code: [View]
    #pragma code
        #pragma interrupt YourHighPriorityISRCode
        // Routine Interrupt ad alta priorità o Interrupt standard in modalità PIC16-compatibile
        void YourHighPriorityISRCode()
            {
            }
            
        #pragma interruptlow YourLowPriorityISRCode
        // Routine Interrupt a bassa priorità
        void YourLowPriorityISRCode()
            {
            }
    qui, all'interno delle funzioni YourHighPriorityISRCode() e YourLowPriorityISRCode() possiamo definire le nostre istruzioni per gestire i vettori di interruzione rispettivamente ad alta e bassa priorità. Nel caso in cui si lavori con la modalità di interrupt compatibile con i pic16 (modalità attiva di default), viene utilizzata unicamente la priorità alta.

    Passiamo a descrivere il funzionamento dei due esempi

    Esempio 1 - Lampeggìo led tramite ritardi

    Abbiamo detto che il led è collegato su RA4. La prima cosa che facciamo è quindi impostare RA4 come uscita, in realtà nel programma ho impostato tutto il banco A come uscita:

    Code: [View]
    TRISA=0x00;
    A titolo di esempio ho anche disattivato il modulo A/D altrimenti altre porte sul banco A (A4 esclusa, che non ha funzione analogica) sono impostate di default come analogiche e possono creare problemi a chi realizza i primi esperimenti facendo unicamente uso delle funzioni digitali:

    Code: [View]
    ADCON1 |= 0x0F;
    Entriamo quindi nel ciclo infinito, all'interno del quale ho definito 4 ritardi:

    Code: [View]
    Delay10KTCYx(100);
    Delay10KTCYx(100);
    Delay10KTCYx(100);
    Delay10KTCYx(100);
    La funzione Delay10KTCYx è una funzione fornita di serie con MPLAB C18 e che consente di ottenere un ritardo del numero di volte specificato moltiplicato 10mila (10K) cicli macchina (Tcy). In pratica una singola istruzione:

    Code: [View]
    Delay10KTCYx(100);
    fornisce un ritardo pari a 10000 x 100 x 0,083uS = 83000uS = 83mS. Avendo messo 4 istruzioni uguali si otterà un ritardo pari a 83x4=332mS.
    MPLAB C18 ha anche altre istruzioni per il ritardo, contenute nel file delay.h e ampiamente descritte nel manuale d'uso che si trova nella cartella "docs" del compilatore. In particolare per poter utilizzare queste istruzioni di ritardo è necessario includere in testa al programma la riga #include <delays.h>
    Dopo questo ritardo di 332mS incontriamo l'istruzione:

    Code: [View]
    LATAbits.LATA4^=1;
    Tale istruzione inverte lo stato della porta (o meglio del latch) RA4 (lo porta a livello logico 1 se era a livello logico zero e viceversa). Dopo tale istruzione, il programma è finito e trovandoci all'interno di un ciclo infinito, verrà eseguito daccapo a partire dai ritardi. La conseguenza è che vedremo il led stare acceso per 332mS e spento per altri 332mS. Otteniamo, cioè, un'onda quadra con un periodo di 664mS (che corrisponde ad una frequenza di 1,506Hz):


    Esempio 2 - Lampeggìo di un led tramite interrupt

    In questo esempio, dal momento che andremo a sfruttare l'interrupt sull'overflow del timer0, bisognerà includere anche la liberia timers che ci facilita il compito:

    Code: [View]
    #include <timers.h>
    Faremo in modo che l'interrupt sul timer0 si verifichi ogni millisecondo. Ci sono varie combinazioni di impostazioni utilizzabili, personalmente ho scelto:


    • timer0 in modalità ad 8 bit (quella classica usata anche sui pic12/16)
    • prescaler per timer0 impostato per 1:64
    • ciclo di clock per timer0 derivato dal clock interno (ovvero a 48MHz)
    • timer0 precaricato con il valore 68

    I calcoli per questi valori possono essere fatti mediante appositi programmi tra cui questo (che però è specifico per pic16) e questo (più completo). Un po' di teoria su come eseguire questi calcoli è illustrata in questo articolo.
    Il valore con cui caricare il timer0 l'ho definito con una costante in cima al programma:

    Code: [View]
    #define TMR0_preload 68
    Il timer viene quindi impostato all'avvio tramite l'apposita funzione:

    Code: [View]
    OpenTimer0(TIMER_INT_ON & T0_8BIT & T0_SOURCE_INT & T0_PS_1_64);
    viene quindi caricato il valore di partenza nel timer0:

    Code: [View]
    TMR0L=TMR0_preload;
    Usando il timer0 in modalità ad 8 bit, il valore viene caricato nel registro TMR0L e rimane quindi inutilizzato il registro TMR0H.
    e viene abilitato il flag di interrupt generale e quello su overflow del timer0:

    Code: [View]
    INTCONbits.TMR0IE = 1;
    INTCONbits.GIE = 1;
    Di default sui pic18 è abilitata la modalità di interrupt compatibile con i pic16: di conseguenza gli interrupt saranno tutti ad alta priorità e quindi le nostre funzioni andranno scritte nella funzione richiamata dal vettore ad alta priorità:

    Code: [View]
    #pragma interrupt YourHighPriorityISRCode
        // Routine Interrupt ad alta priorità o Interrupt standard in modalità PIC16-compatibile
        void YourHighPriorityISRCode()
            {
            
            // Questo interrupt scatta una volta al millisecondo
            if (INTCONbits.TMR0IF)
                {
                TMR0L=TMR0_preload;
                led_counter++;
                if (led_counter==250)
                    {
                    led_counter=0;
                    LATAbits.LATA4^=1;
                    }
                INTCONbits.TMR0IF=0;
                }
            }
    In pratica: ogni millisecondo (tempo al quale si verifica un interrupt) viene incrementato un contatore che ho chiamato led_counter. Quando tale contatore raggiunge il valore 250 (sono quindi passati 250mS), viene invertito lo stato del led e riazzerato il contatore. La conseguenza è che il led lampeggia con un periodo di 500mS e quindi una frequenza di 2Hz:


    Vedete che il main è difatto vuoto: vengono solo eseguite le routine di inizializzazione ma il ciclo infinito non esegue nessuna istruzione in quanto il led viene fatto appunto lampeggiare dalla routine di interrupt.