- Elettronica Open Source - https://it.emcelettronica.com -

Free RTOS con PIC 32 Starter Kit

Un porting del noto sistema operativo per una particolare configurazione hardware del PIC32. Come adattarlo al PIC32 Starter Kit, la scheda di sviluppo dotata di programmatore e debugger JTAG.

Nel novembre 2007 Microchip ha presentato il suo primo microcontrollore a 32 bit: il PIC32 [1], una MCU il cui core è basato su MIPS 4K. La scelta di basare questo nuovo prodotto su un’architettura consolidata, anziché proporre una MCU con un’architettura proprietaria, dovrebbe facilitare lo sviluppo di nuove applicazioni. Con la potenza delle nuove MCU a 32 bit, si fa sempre più stringente la necessità di utilizzare un sistema operativo per utilizzare al meglio la possibilità di eseguire più task contemporaneamente in maniera efficiente ed affidabile. Tuttavia, la complessità del core rende la programmazione assembly del PIC32 tutt’altro che banale. Può essere utile allora utilizzare un sistema operativo real time RTOS commerciale. In quest’articolo esamineremo un RTOS [2] gratuito e utilizzabile anche a fini commerciali: FreeRTOS, del quale è stato realizzato  il porting per una particolare configurazione hardware del PIC32. Vedremo come adattare questo porting in modo da poterlo utilizzare con il PIC32 Starter Kit, una scheda di valutazione e sviluppo economica che comprende un programmatore e debugger JTAG.

Principi  e componenti di un RTOS

Un RTOS è un sistema operativo real-time solitamente usato nei sistemi embedded, sebbene ne esistano anche per piattaforme generiche come i  PC. La definizione real time si riferisce alla capacità di rispondere ad eventi esterni o interni in maniera prevedibile entro un limite di tempo prefissato, comunque adatto a svolgere  i compiti del sistema. I componenti principali di un RTOS sono lo scheduler, i meccanismi  di inter-process Communication (IPC),  i driver delle periferiche ed il file system.

Lo scheduler e le politiche di scheduling

Lo scheduler si occupa di mandare in esecuzione i processi o task dell’applicazione in modo da condividere  il tempoCPU e le risorse del sistema.  I requisiti dei task determinano la scelta della politica di scheduling:

➤ task harreal time: processi che devono essere eseguiti in un tempo limite prefissato (deadline), pena l’integrità del sistema. Sono processi a massima priorità;

➤ task soft real time: processi per i quali il superamento  della deadline non pregiudica l’integrità del sistema;

➤ task non real time: hanno una priorità più bassa dei precedenti.

Come prevedibile, sono state sviluppate numerose politiche di scheduling più o meno adatte a realizzare lo scheduling di processi. L’algoritmo più noto per i task non real time è il round robin: un algoritmo di time-sharing in cui appunto il tempo CPU viene assegnato per un quanto di tempo fisso ad ogni task. Gli algoritmi per sistemi real time invece fanno riferimento in maniera diretta o indiretta alla priorità del task. Quindi, task a priorità più elevata avranno la precedenza rispetto a quelli con priorità minore. E’ difficile stabilire la priorità del task, in funzione del suo tempo d’esecuzione, della sua deadline, della periodicità o meno dello stesso. Solitamente è compito dello sviluppatore della particolare applicazione assegnare le priorità una volta per tutte ai task... e naturalmente testare il sistema!  Sebbene l’esposizione seguente sia valida in linea generale per qualsiasi OS multitasking, faremo riferimento nello specifico all’implementazione in FreeRTOS, un task può trovarsi in un dato istante in uno degli stati seguenti:

➤ Running: il  task è effettivamente in esecuzione e sta impiegando tempo-CP;

➤ Ready: il task è pronto per andare in esecuzione non appena lo scheduler lo stabilirà. Ciò significa che nel frattempo un altro task sarà in esecuzione.  Il passaggio dallo stato di ready a running e viceversa viene controllato dallo scheduler sulla base delle politiche di scheduling;

➤ Blocked: a partire dallo stato di running il task entra nello stato blocked se deve rimanere in attesa che si verifichi un evento temporale o su un qualche tipo di risorsa (come una coda o un semaforo). Ad esempio se il task richiama la funzione di sistema vTaskDelay(time), entrerà nello stato blocked finchè non sarà trascorso il tempo time. Una volta che si verifichi l’evento richiesto il task tornerà nello stato ready;

➤ Suspended: un task può entrare in questo stato a partire da uno stato qualsiasi mediante la chiamata di sistema vTaskSuspend().  Il task verrà portato nello stato ready tramite una chiamata di sistema vTaskResume().

In figura 1 sono raffigurate le transizioni tra i diversi  stati.

Figura 1: stati dei task e transizioni fra stati in FreeRTOS.

Figura 1: stati dei task e transizioni fra stati in FreeRTOS.

Meccanismi di inter-process communication

I meccanismi di inter-process communication svolgono fondamentalmente due tipi di compiti diversi: la sincronizzazione tra task e la comunicazione tra gli stessi. Tra i meccanismi di sincronizzazione abbiamo:

➤ segnali;

➤ semafori;

➤ mutex

Tra i meccanismi  di comunicazione vi sono invece:

➤ shared memory;

➤ code;

➤ pipe con/senza nome;

➤ mailbox;

➤ socket (Unix o TCP/IP).

Molti RTOS però - tra cui anche FreeRTOS - non fanno una distinzione netta tra alcuni dei meccanismi di IPC. Vedremo più avanti, nel corso dell’analisi di FreeRTOS, le caratteristiche e l’uso di alcuni di questi meccanismi.

Driver  delle  periferiche

La gestione delle periferiche in un sistema multitasking è solitamente basata sugli interrupt: in questo caso il sistema invocherà la ISR relativa (il driver della periferica) al manifestarsi dell’ evento. Per quanto l’uso degli interrupt, in alternativa al polling della periferica, permette di evitare che un processo rimanga periodicamente in attesa dell’evento, presenta però il rischio di causare l’imprevedibilità del sistema.

FreeRTOS:  un kernel per sistemi  embedded

FreeRTOS è un Real Time Kernel. E’ in grado di usare diverse politiche di scheduling: preemptive, cooperativo o ibrido, sebbene sia normalmente configurato come preemptive. Ricordiamo che in un OS preemptive è lo scheduler che si occupa di decidere quale task deve andare in esecuzione, mentre in un OS cooperativo i  task stessi devono cedere periodicamente  il controllo della CPU, L’assegnazione del tempo-CPU ai task nello stato di ready è effettuata sulla base di due criteri:

➤ priorità del task, assegnata in fase di creazione dello stesso;

➤ time-sharing (round robin) fra task di eguale priorità.

FreeRTOS è stato progettato specificamente per sistemi embedded con risorse limitate ed è disponibile per svariati microcontrollori ad 8, 16 e 32 bit. Tra le implementazioni disponibili vi sono quelle per diversi micro basati su core ARM, tra cui anche l’ultimo Cortex-M3, quelle per micro AVR ad 8 ed a 32 bit di Atmel, e naturalmente implementazioni per le MCU Microchip delle famiglie PIC18, PIC24/dsPIC e per l’ultima nata PIC32. Il porting di FreeRTOS su micro differenti è facilitato dal fatto che il kernel in sè è scritto quasi totalmente in C: fanno eccezione alcune funzionalità di più basso livello che devono essere scritte necessariamente in assembly, come quelle che implementano il  context switching. La parte fissa del kernel è costituita da tre file principali, tasks.c, queue.c e list.c, contenuti nella directory Source. Un altro file, fornisce le funzionalità di gestione della memoria usate dal kernel: infatti le classiche malloc() e free() del C non sono adeguate per un RTOS, soprattutto in quanto non sono thread safe e neanche deterministiche. Per ogni MCU esiste un certo numero di file C e assembly specifici, contenuti nella directory Source/portable/<nome porting>. Esamineremo il contenuto di alcuni di questi file quando parleremo del porting per il PIC32. A partire dalla versione 4.8, FreeRTOS include nuove funzionalità di controllo dello stack overflow, una delle cause principali di instabilità di un kernel. Per usare queste funzionalità, l’applicazione deve includere una funzione di hook (in pratica una callback) il cui prototipo deve essere void vApplicationStackOverflowHook(void)

Gestione  dei task  in FreeRTOS

Il numero  di livelli di priorità dei task è definito dalla costante configMAX_PRIORITIES contenuta nel file di configurazione FreeRTOSConfig.h (lo si trova nella directory principale dell’applicazione demo). Ad ogni task si assegnerà in fase di creazione una priorità compresa tra 0 e configMAX_PRIORITIES-1. Solitamente il livello di priorità 0 è assegnato al task Idle, sempre presente ed il cui compito è quello di liberare la memoria assegnata a task terminati con la funzione vTaskdelete(). È anche possibile assegnare una funzione di hook a questo task in modo da porre ad esempio la MCU in power down: la funzione di hook sarà eseguita infatti solo quando non ci sono altri task con priorità maggiore. Il Tick interrupt si occupa di far uscire i task dallo stato suspended o blocked una volta che sia trascorso il tempo richiesto. L’interrupt è in pratica l’interrupt di un timer che viene richiamato con una frequenza determinata dalla costante configTICK_RATE_HZ contenuta  nel file FreeRTOSConfig.h. Il valore di default è pari a 1000 Hz, da cui una risoluzione temporale di 1 ms; questo valore è scelto per testare l’RTOS mentre solitamente è adeguato un valore di 5-10 ms. Ogni volta che viene richiamato, l’ISR del Tick interrupt deve stabilire se esiste un task che può essere posto nello stato di ready: questo causerà un context switch se il task ha una priorità maggiore di quello attualmente in esecuzione, oppure se è passato il quanto di tempo stabilito per il round-robin dei task con uguale priorità. All’uscita dell’ISR quindi la CPU eseguirà il task appena risvegliato. Quando si fa uso di un RTOS, le funzionalità dell’applicazione sono svolte all’interno di task indipendenti, ognuno dei quali è creato all’interno della funzione main() e continua ad essere eseguito in un loop infinito finché, se necessario, non sarà terminato tramite la funzione vTaskDelete(). Un task viene quindi creato tramite la funzione:

portBASE_TYPE xTaskCreate(
 pdTASK_CODE pvTaskCode,
 const portCHAR * const pcName,
 unsigned portSHORT usStackDepth,
 void *pvParameters,
 unsigned portBASE_TYPE uxPriority,
 xTaskHandle *pvCreatedTask
 );

dove il parametro pvTaskCode è un puntatore alla funzione che implementa il task, mentre pvParameters è un puntatore (a void) ai parametri della stessa. Con un opportuno cast è possibile passare parametri di qualsiasi tipo alla funzione. Nel caso del porting di FreeRTOS per PIC32, la funzione che implementa  il task deve avere il prototipo

portTASK_FUNCTION_PROTO
 ( vTaskCode, pvParameters );

e la funzione stessa sarà definita come

portTASK_FUNCTION( vTaskCode, pv-Parameters )
 {
 // loop infinito
 for( ;; )
 {
 /* codice applicativo del
 task */
 // ...
 }
 }

le macro portTASK_FUNCTION e portTASK_FUNCTION_PROTO semplificano l’uso di specifiche direttive del compilatore C necessarie per la definizione ed il prototipo delle funzioni task.

Meccanismi di inter-task communication

FreeRTOS fornisce diversi strumenti per l’inter-task communication:

➤ code;

➤ semafori binari e semafori contatori;

➤ mutex e mutex ricorsivi.

Tutti sono basati sulle code, inoltre è possibile derivare da essi altri metodi di ITC.

Code

Le code sono usate per scambiare messaggi o dati tra task oppure tra task e ISR. Possiamo immaginare una coda come un buffer di dimensioni prestabilite in grado di contenere un dato numero di elementi, ciascuno di dimensione prefissata. Tutte queste grandezze sono stabilite al momento della creazione della coda. Ad esempio supponiamo che un task che denominiamo produttore utilizzi la coda per immettere dei dati, mentre un altro task consumatore prelevi questi dati mano a mano che si rendano disponibili. Il task consumatore chiamerà la funzione:

xQueueReceive(
 xQueueHandle xQueue,
 void *pvBuffer,
 portTickType xTicksToWait
 );

dove xQueue è il  puntatore alla coda (possibilmente fornito come parametro all’atto della creazione del task), pvBuffer il buffer dove sarà copiato l’elemento letto, xTicksToWait  definisce il tempo durante il quale il task rimarrà nello stato blocked qualora lo coda fosse ancora vuota. Il  task produttore invia un nuovo elemento nella coda tramite la funzione:

portBASE_TYPE xQueueSendToBack(
 xQueueHandle xQueue,
 const void * pvItemToQueue,
 portTickType xTicksToWait
 );

Si noti che gli elementi sono inseriti nella coda per valore, non per riferimento  (è necessario per mantenere la coda thread safe), perciò qualora fosse indispensabile passare elementi di grandi dimensioni è preferibile usare dei puntatori.

Semafori

I semafori  binari sono lo strumento principale per ottenere la sincronizzazione fra task oppure fra task e ISR. Possono essere anche usati per ottenere la mutua esclusione fra task, sebbene a questo scopo sia preferibile  utilizzare i mutex. Un semaforo binario è creato tramite la funzione vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore ) che ritorna un handle al semaforo. Vediamo un esempio d’uso di un semaforo per sincronizzare un task con un ISR: supponiamo di avere un task che gestisce i caratteri ricevuti da un UART. Il task si porrà nello stato blocked chiamando la funzione:

xSemaphoreTake(
 Xsemaphorehandle xSemaphore,
 portTickType xBlockTime
 )

con la quale rimarrà in attesa di ottenere il semaforo. Nel frattempo il tempo-CPU sarà reso disponibile per altri task. L’ISR che gestisce l’UART, alla ricezione di un carattere rilascerà il  semaforo (risvegliando quindi il task precedente) tramite la funzione:

xSemaphoreGiveFromISR(
 xSemaphoreHandle xSemaphore,
 portBASE_TYPE *pxHigherPriorityTaskWoken
 )

dove il parametro pxHigherPriorityTaskWoken sarà posto al valore pdTRUE se il rilascio del semaforo causa lo sblocco di un task con priorità superiore a quella del task attualmente in esecuzione. Se così fosse, alla fine della ISR sarà necessario eseguire un context switch (vedremo un esempio specifico per i PIC32). I  semafori contatori sono dei semafori contenenti più di un elemento. Sono usati solitamente per contare e processare degli eventi oppure per gestire risorse (ad esempio buffer di memoria).

Mutex

I mutex sono semafori binari che includono un meccanismo di priority inheritance. Per questo motivo sono particolarmente adatti per gestire la mutua esclusione fra task. Un mutex viene creato tramite la funzione

xSemaphoreHandle xSemaphoreCreateMutex(void )

che ritorna un hanlde al mutex appena creato. Un mutex usato per ottenere la mutua esclusione nell’accedere ad una risorsa condivisa tra più task, può essere visto come un token che viene passato fra i task. Così il task che vuole accedere alla risorsa cercherà di ottenere il mutex tramite la funzione:

xSemaphoreTake(
 xSemaphoreHandle xSemaphore,
 portTickType xBlockTime
 )

una volta che avrà ottenuto il mutex potrà usare la risorsa, ad esempio una periferica, e quando avrà finito dovrà rilasciare il mutex usando la funzione

xSemaphoreGive( xSemaphoreHandle
 xSemaphore )

Queste funzioni sono le stesse usate per i semafori, sebbene l’handle si riferisca ad un oggetto con proprietà differenti. La differenza principale tra mutex è se

mafori è però l’esistenza per i mutex di un meccanismo di ereditarietà della priorità (priority inheritance). Poniamo  il caso che un task a bassa priorità stia bloccando una risorsa cui accede tramite un mutex: se nel frattempo un task a priorità più elevata cerca di ottenenere il mutex, il kernel può temporaneamente innalzare la priorità del task bloccante in modo da accelerarne l’esecuzione. Lo scopo di questo meccanismo è quello mantenere bloccato il task a priorità elevata per il minor tempo possibile per minimizzare l’inversione di priorità che si verifica. Si noti però che un RTOS del tipo hard real time dovrebbe evitare in linea di principio che si verifichi l’inversione di priorità. Un mutex ricorsivo, infine può essere ottenuto più volte di seguito dal task proprietario e va rilasciato uno stesso numero di volte prima che sia reso disponibile ad un altro task.

Il porting  di FreeRTOS per i PIC32

Vediamo ora quali sono le caratteristiche specifiche del porting di FreeRTOS per i PIC32.

Configurazione di FreeRTOS  per PIC32

Nel file Fr eeR TOSConfig.h (in Demo\PIC32MX_MPLAB )  sono  presenti  le principali voci di configurazione di FreeRTOS che andranno eventualmente modificate in funzione dell’applicazione. Tr a le voci troviamo la define configTICK_RATE_HZ della quale abbiamo già discusso. La define configKERNEL_INTERRUPT_PRIORITY stabilisce la priorità dell’interrupt del kernel (il tick interrupt), mentre configMAX_SYSCALL_INTERRUPT_PRIORITY setta il livello massimo di priorità delle ISR dalle quali possono essere richiamate le funzioni di FreeRTOS che usano le code ed i semafori  (i mutex non possono essere usati all’interno di una ISR). Se ad esempio, come nel caso del porting per PIC32, le priorità sono state definite come segue:

#define configKERNEL_INTERRUPT_PRIORITY 1
 #define configMAX_SYSCALL_INTERRUPT_
 PRIORITY 3

avremo che le ISR definite con priorità 2 e 3 non saranno mai interrotte dall’attività del kernel e potranno usare le code ed i semafori. Anche le ISR con priorità maggiore di 3 non saranno interrotte dal kernel ma non potranno usare code e semafori. L’uso di un valore per configMAX_SYSCALL_INTERRUPT_PRIORITY maggiore di

configKERNEL_INTERRUPT_PRIORITY

ci permette di avvalerci di un’altra possibilità: l’annidamento degli interrupt (interrupt nesting). Assegnando priorità diverse agli interrupt, è quindi possibile che un interrupt sia interrotto a sua volta da un altro con priorità più elevata. Tuttavia tale possibilità va usata con accortezza, in quanto diminuisce la prevedibilità del sistema e richiede un uso maggiore di memoria per lo stack.

ISR con il PIC32

La scrittura delle interrupt service routines che non si innestano andrà scritta normalmente, secondo le specifiche del compilatore C32 dei PIC32. Diversamente, le ISR che possono essere innestate –all’interno delle quali può avvenire un context switchandranno scritte avvalendosi di un wrapper assembly. Ad esempio, per definire l’ISR dell’UART2, il wrapper avrà la forma riportata nel listato 1.

...
#include “ISR_Support.h” // portSAVE_CONTEXT e
                       // portRESTORE_CONTEXT
.set   nomips16
       .set   noreorder
       .extern vU2InterruptHandler // l’handler scritto in C
       .extern xISRStackTop
       .globalvU2InterruptWrapper

       .set      noreorder
       .set      noat
       .ent      vU2InterruptWrapper
vU2InterruptWrapper:
       portSAVE_CONTEXT
       jal vU2InterruptHandler       // richiama l’handler
       nop
       portRESTORE_CONTEXT
       .end           vU2InterruptWrapper
Listato 1

L’interrupt handler vero e proprio sarà invece scritto in C, come riportato nel listato 2.

...
// The UART interrupt handler.
void __attribute__( (interrupt(ipl1), vector(_UART2_VECTOR)))
vU2InterruptWrapper( void );
//
void vU2InterruptHandler( void )
{
static portCHAR cChar;
static portBASE_TYPE xHigherPriorityTaskWoken;
  xHigherPriorityTaskWoken = pdFALSE;
// se è un interrupt UART RX
if( mU2RXGetIntFlag() )
{
      while( U2STAbits.URXDA ) {
              /* legge il carattere e lo copia nella coda */
              cChar = U2RXREG;
              xQueueSendFromISR(xRxedChars,&cChar,
              &xHigherPriorityTaskWoken );
      }
      mU2RXClearIntFlag();
  }
/* eventuale context switch */
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
Listato 2

Notiamo che l’ultima istruzione dell’interrupt handler deve essere necessariamente portEND_SWITCHING_ISR ( xHigherPriorityTaskWoken ); il cui parametro indica se è necessario un context switch: osserviamo che il suo valore è quello ritornato dalle funzioni dell’API per le code ed i semafori. Nell’esempio,  se la chiamata della funzione xQueueReceiveFromISR() ha come conseguenza di risvegliare un task con priorità maggiore, sarà necessario un context switch.

Sezioni  critiche del codice

Le sezioni critiche, cioé le parti di codice che non devono essere interrotte, pena l’instabilità del sistema o un comportamento erratico dell’applicazione, vanno scritte interponendole tra le funzioni:

portENTER_CRITICAL();

e

portEXIT_CRITICAL();

Queste funzioni disabilitano e riabilitano gli interrupt.

La  demo  per  il  PIC32   Starter Kit

FreeRTOS ha sviluppato il  porting per PIC32 per la configurazione hardware costituita da:

➤ modulo PIM con PIC32MX460F512L;

➤ scheda di sviluppo Explorer 16;

➤ debugger ICD2 o Real ICE.

Per adattare  il porting per il PIC32 Starter Kit, ho modificato i seguenti  file, compresi nel file zip scaricabile dal sito della rivista:

➤ par_test.c;

➤ serial.c;

➤ main.c;

➤ flash.c;

➤ comtest.c;

➤ FreeRTOSconfig.h.

Inoltre, per poter usare la libreria d’utilità per il debug tramite JTAG, ho aggiunto al progetto MPLAB i file db_utils.a e db_utils.h. Tramite questa utility potremo usare la finestra di output di MPLAB al posto del display LCD della scheda Explorer 16, così da visualizzare  i messaggi di diagnostica della demo, come si può vedere in figura 2.

Figura 2: FreeRTOS eseguito in MPLAB. Si noti l’output della utility di debug.

Figura 2: FreeRTOS eseguito in MPLAB. Si noti l’output della utility di debug.

La demo, così modificata,  effettuerà i seguenti test (si veda il file main.c):

➤ flash task: serve più che altro a testare il corretto funzionamento della scheda facendo lampeggiare  il LED1 con un periodo di 800 ms;

➤ semaphore test: crea due coppie di task ognuna delle quali usa un semaforo per accedere ad una variabile;

➤ COM test: crea due task, uno trasmittente ed un altro ricevente, che utilizzano un driver per l’UART2 del PIC32 per inviare e ricevere caratteri. L’interrupt del driver ha priorità 2, perciò non è interrotto dal kernel ma può usare una coda per far comunicare fra loro i task TX ed RX. Il  LED2 del PIC32 Starter Kit lampeggerà molto brevemente alla ricezione di un carattere corretto. Per poter usare lo Starter Kit senza la necessità di un connettore tra i terminali  TX ed RX della UART2, ho configurato l’UART in modalità loopback (si veda il file serial.c);

➤ interrupt queue test: crea una coda cui possono accedere sia in lettura che in scrittura 3 task e 2 interrupt, rispettivamente del timer3 e del timer4. Gli interrupt sono stati settati con priorità 2 e 3 in modo da testare il nesting;

➤ check task: questo task si occupa di controllare che gli altri test continuino a funzionare correttamente.

Il task è eseguito in condizioni normali ogni 2 secondi: se sono rilevati degli errori, l’utility di debug visualizzerà una stringa con l’indicazione del test difettoso. Inoltre, in assenza di errori il LED3 lampeggerà  con  un  periodo  di  2  se

condi, altrimenti lampeggerà con un periodo di 500 ms;

➤ High Frequency Timer: un timer che genera un interrupt ad alta frequenza (20 KHz). All’interrupt è assegnata priorità 4, quindi non sarà mai interrotto dal kernel. Ogni 20000 interrupt (e quindi ogni secondo) sarà incrementato un contatore il cui valore sarà visualizzato tramite la utility di debug. La demo è stata sviluppata nell’ambiente di sviluppo MPLAB v. 8.10, con il compilatore C32 v. 1.02. Il programma è compilabile anche con la versione di valutazione di C32 limitata a 64 KB di codice generato. Fino alla versione 5 di FreeRTOS, era possibile testare il porting per PIC32 anche senza una scheda hardware, con il solo simulatore di MPLAB. Era anche possibile utilizzare l’UART1 I/O del simulatore per visualizzare  l’output della demo (purchè si ridefinissero le funzioni weak di I/O in modo da usare la UART1) al posto della utility JTAG, che attualmente funziona solo con lo Starter Kit. Dalla versione 5.0.2 purtoppo ciò non è più praticabile a causa delle nuove funzioni di context switching da ISR -aggior nate in modo da permettere  il nesting degli interrupt che rendono la simulazione del tutto inaffidabile. Un’ulteriore notazione va fatta riguardo il settaggio della frequenza del PBCLK. Com’è noto il PIC32 usa due frequenze di clock inter ne: il SYSCLK per il core ed il PBCLK per la maggior parte delle periferiche. Con le prime versioni del compilatore C32, la frequenza massima del PBCLK era pari al SYSCLK/2, mentre attualmente è pari al SYSCLK. Per settare il PBCLK ad un rapporto diverso da uno, è opportuno usare la funzione di libreria:

SYSTEMConfig(unsigned int sys_clock, unsigned int flags);

anzichè la funzione

SYSTEMConfigPerformance(unsigned int sys_clock);

usata nel porting originale, che forza PBDIV ad 1. In caso contrario, tutte le temporizzazioni risulteranno errate.