Rimappare le periferiche sui pic a 16bit (dsPic/PIC24). La funzione Peripheral Pin Select (PPS)
Ho cominciato da poco a cimentarmi con i dsPic e ho deciso quindi di pubblicare, come mio solito, degli appunti ad ogni step, in maniera tale che possano essere d’aiuto a chi vuole cominciare ad entrare in questo nuovo mondo. Lo scopo è come sempre anche quello di creare un nuovo argomento di discussione in maniera tale da far entrare in gioco i più esperti e non di modo che si possano condividere conoscenze ed esperienze.
Questo e quelli che seguiranno, quindi, non vorranno essere delle vere e proprie lezioni di programmazione dei dsPic, ma degli appunti personali, scritti da una persona che dai pic 10/12/16/18 vuole passare ai pic a 16 bit. Lavorando con i pic ad 8 bit è normale che prima o poi venga la voglia di fare il “salto di qualità” verso qualcosa di più complesso o solo per la voglia di imparare, quindi le mie esperienze saranno più rivolte verso chi già sa programmare i pic ad 8 bit in quanto mi limiterò ad analizzare le differenze.
Abituato con i pic ad 8 bit devo dire che è “quasi” tutto un’altro mondo. Le differenze rispetto ai pic ad 8 bit sono davvero parecchie: la complessità e il numero di periferiche a disposizione, l’elevato numero di impostazioni e settaggi per utilizzarli e alcune piccole cose a livello di programmazione. Ma la sostanza in effetti non cambia: programmiamo sempre in C anche se per i pic a 16bit (ovvero dsPic30, dsPic33, pic24F, pic24H) si utilizzerà il C30, sempre disponibile gratuitamente per il download in versione student edition (links nella pagina PicMicro).
Cominiciamo quindi con una delle prime caratteristiche salienti: la funzione Peripheral Pin Select.
Indice dei contenuti
C’è una periferica qua e devo metterla là
Molti pic a 16bit hanno questa interessante funzione che permette di piazzare fisicamente una determinata periferica sul pin che vogliamo (non tutte le periferiche, per carità, alcune difatti hanno un’assegnazione fissa come siamo già abituati). Questa funzione è davvero molto comoda perchè in fase di progetto non saremo più vincolati ad utilizzare dei pin prefissati ma possiamo scegliere, ad esempio, l’UART su quali pin deve stare, così come tante altre periferiche. Tale funzione ci permette addirittura di multiplexare su un unico pin anche più di una periferica!
Prendiamo un dsPic di esempio, pensato per scopi generici (GP : General Purpose. I dsPic, difatti, sono classificati in base allo “scopo” per il quale sono stati creati: controllo motori, processori digitali audio ecc): il dsPIC 33FJ128GP802. Tale dsPic ha addirittura due periferiche UART ed è con queste che farò l’esempio.
Andiamo quindi a vedere come sempre il datasheet per capire dove sono posizionati i pin Rx e Tx di queste due periferiche:
A parte il fatto che, come vedete, ogni pin ha una miriade di funzioni, all’inizio si rimane spaesati pensando al fatto che questo dsPic ha 2 periferiche UART ma sul pinout dell’integrato non ci sono i pin destinati ad esse! Ma la cosa bella è che nel datasheet non c’è scritto nulla al riguardo della apparente “scomparsa” di questi due pin !
In realtà la soluzione a questo mistero è un po’ più macchinosa da cercare rispetto a come siamo abituati normalmente. Data l’enorme complessità delle periferiche presenti sui dsPic, la Microchip ha pensato, giustamente, di non riscrivere tutto il malloppo di informazioni per ogni dsPic sul proprio datasheet, ma di organizzare le informazioni comuni a tutti in una pagina apposita:
http://www.microchip.com/stellent/idcplg?IdcService=SS_GET_PAGE&nodeId=2573
In questa pagina sono raggruppati, in pdf separati, i vari capitoli del dsPic33F Family Reference Manual.
Dopo tutto quello che vi ho detto all’inizio, potete capire quindi che l’UART su questo dsPic non ha un’assegnazione di default: ovvero al reset l’UART “non ha sbocchi”, dovremo essere noi, da codice, a dire al Pic quali pin utilizzare per l’UART (UART1 o UART2 o entrambe).
Il documento di riferimento per la selezione dei pin per le periferiche è quello indicato appunto come I/O Ports with Peripheral Pin Select. In questo caso poi, specifico dell’UART, per i settaggi dovremo andarci a leggere anche il capitolo relativo all’UART (difatti sui dsPIC abbiamo una UART coi fiocchi: non ci sono solo Rx e Tx ma anche i pin per il controllo di flusso e tante altre caratteristiche aggiuntive), ma di questo non ci occuperemo in questo articolo.
Prima di iniziare chiariamo innanzitutto alcuni punti:
- I pin sui quali si possono rimappare le periferiche sono contrassegnati con RPn. Vedete che nel datasheet del dsPic preso ad esempio, i pin RPn sono associati ai pin RBn.
- Le periferiche che possono essere rimappate sono solo quelle digitali. Le funzioni analogiche quindi hanno sempre un’assegnazione dei pin fissa.
- Sebbene il remapping delle periferiche è destinato solo a quelle digitali, alcune di esse hanno assegnazione fissa e non possono essere rimappate perchè richiedono una circuiteria speciale. L’I2C è una di queste.
- Le periferiche che hanno la possibilità di essere rimappate, non hanno mai nessun pin assegnato di default, ecco perchè, ad esempio, l’UART o l’SPI non appaiono sul pinout.
Periferica di Input e periferica di Output
La modalità con cui una periferica viene associata ad un pin è diversa a seconda che la funzione associata sia una funzione di input o di output. Per le funzioni di input viene assegnata la periferica al pin (il remapping viene effettuato sulla base della periferica), per le funzioni di output viene assegnato il pin alla periferica (remapping effettuato sulla base del pin). Prima che andiate nel pallone vediamo cosa vuol dire tutto questo prendendo ad esempio proprio l’UART che ha sia funzioni di input (RX) che di output (TX).
Nel caso della funzione di input, abbiamo detto che prendiamo come base la periferica e quindi tra tutti i pin disponibili ne scegliamo uno e lo associamo alla periferica, che in questo caso è la UART1-RX (trattiamo la funzione di ricezione della UART come se fosse una periferica a sè stante):
Come vedete, sulla sinistra abbiamo i vari pin RPn, che sono quelli disponibili per il remapping, e sulla destra abbiamo la periferica U1RX (UART1 – funzione di ricezione RX). Possiamo scegliere uno qualsiasi dei pin ed associarlo quindi alla periferica.
La funzione di remapping per i pin di input viene effettuata tramite i registri RPINRx (dove x è un numero variabile). Ognuno di questi registri permette il remapping di 1/2 periferiche secondo la tabella:
Vediamo quindi che per rimappare la funzione di ricezione della UART1 (UART1 Receive) dovremo sfruttare il registro RPINR18, andando a modificare i bit indicati come U1RXR<4:0>.
Per la UART2 si utilizza il registro RPINR19 -l’ho trovato su alcuni esempi tramite google-, ma non capisco come mai su tale documento non è menzionato, vogliano gli esperti illuminarmi su tale mancanza.
Il registro RPINR18 è così strutturato (non dimentichiamoci che stiamo parlando di pic a 16 bit, per cui tutti i registri sono a 16bit):
come vedete tale registro permette il remapping di due funzioni di ricezione della UART: RX e CTS. Per impostare la funzione RX della UART su un pin dobbiamo quindi modificare i bit da 0 a 4 di tale registro. Essendo il C30 organizzato in strutture, ci basterà scrivere semplicemente:
RPINR18bits.U1RXR = 6; |
In questo modo abbiamo associato la funzione RX della UART1 al pin denominato RP6. Basta difatti mettere nell’assegnazione il numero del pin RP che vogliamo.
Vediamo ora come associare una funzione di output. Abbiamo detto che per l’output è la periferica che viene assegnata al pin:
A sinistra abbiamo tutte le periferiche di output e quindi a sinistra il pin che sarà destinato all’uscita delle periferiche. In questo caso la selezione viene effettuata mediante i registri RPORx. Ognuno di questi registri controlla 2 pin. La funzione da associare al pin viene selezionata mediante i valori in tabella:
Ancora una volta in questa tabella manca la UART2 quindi metto un appunto qui. Per la funzione TX della UART2 il valore da utilizzare è 5 (00101).
Dovremo quindi andarci a trovare il registro RPORx relativo al pin su cui desideriamo rimappare la periferica e andare quindi a modificare i bit RPnR ed impostarli sul valore relativo alla periferica che vogliamo. Supponiamo di voler mappare la funzione TX della UART1 sul pin RP5, il registro che controlla la mappatura delle periferiche di output su RP5 è il RPOR2:
Vediamo quindi che, per il pin RP5, dobbiamo modificare i bit da 8 a 12, ed assegnare loro il valore relativo alla funzione U1TX. In C30 scriveremo quindi:
RPOR2bits.RP5R = 0b00011; |
Questi esempi dovrebbero essere abbastanza chiari per capire come assegnare una qualsiasi periferica sul pin che vogliamo. All’inizio può sembrare un po’ ostico ma ci farete l’abitudine.
La periferica è bloccata!
La questione però non è conclusa: difatti per poter eseguire l’assegnazione delle periferiche è necessario eseguire un’operazione di sblocco: il remapping delle periferiche è difatti bloccato da un bit, denominato IOLOCK (Peripheral Pin Select Lock Bit), nel registro OSCCON (Oscillator Control Register). Tale funzione va eseguita necessariamente in assembler o tramite alcune macro, denominate builtin, già fornite col C30 (MPLAB® C Compiler for PIC24 MCUs and dsPIC® DSCs User’s Guide – Appendice B – Builtin functions). L’esempio di utilizzo si trova sempre nel Reference Manual.
In pratica sarà necessario eseguire in sequenza:
- Sblocco della funzione PPS
- Remapping delle periferiche che vogliamo
- Blocco della funzione PPS
In C30, un esempio di codice è il seguente:
__builtin_write_OSCCONL(OSCCON & ~(1<<6)); // sblocca i registri PPS RPINR18bits.U1RXR = 6; // assegno RX della UART1 al pin RP6 RPOR2bits.RP5R = 0b00011; // assegno TX della UART1 al pin RP5 __builtin_write_OSCCONL(OSCCON | (1<<6)); // blocco i registri |
Su altri esempi, risalenti a vecchia documentazione, la funzione di blocco e sblocco viene invece eseguita in assembler, le due modalità sono equivalenti:
// sblocco registri PPS asm volatile ( "mov #OSCCONL, w1 \n" "mov #0x45, w2 \n" "mov #0x57, w3 \n" "mov.b w2, [w1] \n" "mov.b w3, [w1] \n" "bclr OSCCON, #6 "); // blocco registri PPS asm volatile ( "mov #OSCCONL, w1 \n" "mov #0x45, w2 \n" "mov #0x57, w3 \n" "mov.b w2, [w1] \n" "mov.b w3, [w1] \n" "bset OSCCON, #6"); |