Sviluppa rapidamente applicazioni basate su microcontroller in tempo reale con MicroPython

Di Jacob Beningo

Contributo di Editori nordamericani di Digi-Key

I sistemi embedded in tempo reale stanno diventando estremamente complessi e richiedono una conoscenza profonda non solo di microcontroller sofisticati a 32 bit, ma anche di sensori, algoritmi, protocolli Internet e applicazioni per utenti finali estremamente diverse. Considerati i cicli di sviluppo più brevi e la presenza di più funzioni, i team di sviluppo devono trovare dei modi per accelerare il progetto riuscendo però allo stesso tempo a portare il loro codice in nuovi prodotti: per questo, serve una piattaforma di sviluppo integrata e flessibile.

Per accelerare il processo di sviluppo, sono disponibili diverse piattaforme specifiche per microcontroller, ma il problema di queste soluzioni è che vincolano gli sviluppatori a un solo fornitore di microcontroller. Portare il software da una piattaforma a un'altra può essere dispendioso sia economicamente che in termini di tempo.

Una soluzione particolare e recente che sta diventando sempre più diffusa consiste nell'abbinare l'hardware di microcontroller a basso livello con un linguaggio di programmazione ad alto livello come Python. Questa soluzione si chiama MicroPython. Viene eseguita su parti di fornitori diversi di microcontroller ed è open source, il che permette realmente agli sviluppatori di usarla e personalizzarla in funzione delle proprie esigenze.

MicroPython.org la descrive come un'implementazione snella ed efficiente del linguaggio di programmazione Python 3 che include una piccola sottoserie della libreria standard Python ottimizzata per l'esecuzione in microcontroller e in ambienti con vincoli. MicroPython ha esordito come progetto kick-starter non solo riuscito, ma che ha raccolto anche un folto seguito e ora viene usato con successo in progetti in numerosi settori come quello industriale e spaziale.

Scegliere il microcontroller giusto

MicroPython può essere eseguito su diversi microcontroller e non vi sono limitazioni importanti alla sua portabilità su altri microcontroller, a condizione che dispongano di RAM, Flash e potenza di calcolo sufficienti per eseguire l'interprete. Ciò detto, se un microcontroller verrà usato per eseguire MicroPython uno sviluppatore dovrebbe fare attenzione a diversi requisiti chiave:

  • almeno 256 kbyte di flash
  • almeno 16 kbyte di RAM
  • un clock della CPU di almeno 80 MHz

Queste sono le raccomandazioni generali e gli sviluppatori possono discostarsene in base alle proprie esigenze applicative e al tempo che intendono dedicare alla personalizzazione del kernel MicroPython. Ad esempio, MicroPython può essere modificato per usare molto meno di 256 kbyte di Flash. Queste raccomandazioni sono intese a dare allo sviluppatore la migliore esperienza, nonché spazio per la crescita del suo codice applicativo.

MicroPython è già stato portato su diverse serie di microcontroller che possono essere usati come ottimo punto di partenza per il porting su una nuova piattaforma o per scegliere un microcontroller già supportato. Nella Figura 1 è mostrata la directory principale del codice sorgente MicroPython. Da qui, il lettore può vedere diversi microcontroller supportati, fra cui:

Immagine della struttura di esempio della directory delle cartelle

Figura 1: Struttura di esempio della directory delle cartelle che mostra le piattaforme di microcontroller disponibili che attualmente supportano MicroPython. Fra queste vi sono ARM, CC3200, esp8266, Microchip PIC e STM32. (Immagine per gentile concessione di Beningo Embedded Group)

Ogni cartella elencata nella directory radice è una cartella di alto livello che contiene i driver generali e il supporto per quella famiglia di chip. All'interno di ogni cartella possono esservi diverse schede di sviluppo o processori. Ad esempio, la cartella stmhal supporta schede di sviluppo come STM32F429 Discovery Board e STM32 IoT Discovery Node (STM32L) di STMicroelectronics, oltre a molte altre come la pyboard STM32F405 di Adafruit Industries. La cartella ESP8266 contiene supporto per la scheda di breakout Huzzah di Adafruit per ESP8266 e Feather Huzzah Stack Board.

Le schede di sviluppo che possono eseguire MicroPython non sono costose ed è una buona idea che lo sviluppatore ne acquisti diverse per rendersi conto di quanta memoria, spazio di storage e potenza di calcolo gli servono per un'applicazione. Ad esempio, uno sviluppatore potrebbe iniziare usando la pyboard STM32F405 e decidere che per funzioni e aggiornamenti a prova di futuro vuole passare a una STM32F429 nel suo prodotto finale. STM32F429 ha 2 Mbyte di Flash, 256 kbyte di RAM e una RAM speciale a stato di attesa nullo chiamata CCM.

Il codice applicativo MicroPython scritto da uno sviluppatore non deve necessariamente essere conservato nella flash interna del microcontroller. Il kernel di MicroPython deve trovarsi sul microcontroller ma il codice applicativo può trovarsi su un supporto di storage esterno, come la scheda microSD da 8 GB di Panasonic. L'uso di un dispositivo di storage di memoria esterno per conservare il codice applicativo offre l'opportunità di usare un microcontroller con meno memoria e, potenzialmente, risparmiare sui costi complessivi del sistema.

Ai blocchi di partenza con MicroPython

MicroPython è preinstallato nella pyboard STM32F405 di Adafruit. Tuttavia, qualsiasi altro kit di sviluppo o hardware personalizzato richiederà che lo sviluppatore scarichi il codice sorgente MicroPython, costruisca il codice sorgente per la scheda target e quindi esegua il flash del microcontroller con il software. Accedere al codice sorgente MicroPython è facile, dato che si trova tutto su GitHub. Per impostare la toolchain e configurare l'ambiente per la costruzione di MicroPython, uno sviluppatore deve seguire diversi passaggi. In questo esempio, costruiremo MicroPython per la scheda STM32F429 Discovery.

Prima di tutto uno sviluppatore dovrà creare una macchina virtuale basata su Linux oppure usare un'installazione Linux nativa. Una volta che Linux è disponibile da un terminale, lo sviluppatore installerà la toolchain del compilatore ARM utilizzando il seguente comando:

sudo apt-get install gcc-arm-none-eabi

 

Se l'installazione Linux è nuova, il sistema di controllo della revisione, git, potrebbe non essere installato. Git può essere installato dal terminale tramite il seguente comando:

 

sudo apt-get install git

 

Una volta che git è installato, la sorgente MicroPython può essere tolta dal repository eseguendo il seguente comando nel terminale:

 

git clone https://github.com/micropython/micropython.git

L'esecuzione del processo può durare alcuni minuti ma uno sviluppatore dovrebbe vedere la sequenza dimostrata (Figura 2).

Immagine della clonazione del repository MicroPython nel file system locale

Figura 2: Clonazione del repository MicroPython nel file system locale dove uno sviluppatore può poi costruire MicroPython per la propria scheda target o personalizzare il kernel per le proprie applicazioni. (Immagine per gentile concessione di Beningo Embedded Group)

Una volta clonata la sorgente MicroPython nel file system locale, lo sviluppatore deve passare a quella directory e quindi eseguire un "cd stmhal" nel terminale. La directory stmhal contiene il makefile per MicroPython per i microcontroller STM32. Nella cartella "boards" sono disponibili tutte le schede STM32 supportate al momento. Dal terminale si può quindi costruire qualsiasi scheda che si trovi nella cartella "boards". Ad esempio, si può costruire la scheda STM32F4 Discovery digitando il seguente comando:

make BOARD=STM32F4DISC

La costruzione da parte di MicroPython richiederà diversi minuti. Mentre la costruzione è in corso, uno sviluppatore vorrà installare in un microcontroller il tool DFU (aggiornamento del firmware del dispositivo) usato per programmare MicroPython su USB. È sufficiente installare il tool una volta, digitando il seguente comando in un terminale:

sudo apt-get install dfu-util

Al termine del processo di costruzione di MicroPython e una volta installato dfu-util, lo sviluppatore è pronto per caricare MicroPython nel proprio microcontroller. Per prima cosa dovrà mettere il microcontroller in modalità bootloader DFU. A tale fine occorre impostare i pin di boot in modo da caricare il bootloader interno su reset, invece di eseguire il codice dalla flash.

Con il microcontroller in modalità bootloader e collegato al computer host tramite USB, dfu-util può essere usato per scaricare MicroPython tramite il seguente comando:

dfu-util -a 0 -d 0483:df11 -D build-STM32F4DISC/firmware.dfu

Dfu-util utilizzerà il file dfu prodotto dal processo di compilazione. Il processo richiederà diversi minuti, dato che il microcontroller verrà completamente cancellato e riprogrammato. Sarà molto simile a quello illustrato (Figura 3). Non appena il tool ha finito, i ponticelli di boot dovrebbero essere adattati per caricare dalla flash interna, dopo di che il microcontroller può essere sottoposto a un ciclo di accensione e spegnimento. Ora MicroPython è in esecuzione nel microcontroller target.

Immagine di MicroPython che viene caricato in un microcontroller tramite dfu-util

Figura 3: Immagine di MicroPython che viene caricato in un microcontroller tramite dfu-util. (Immagine per gentile concessione di Beningo Embedded Group)

Interfacciamento di sensori e dispositivi collegati

Il vantaggio più grande dato dall'uso di un linguaggio di programmazione di alto livello come MicroPython per sviluppare software embedded in tempo reale è dato dal fatto che il software è indipendente dall'hardware sottostante. Ciò significa che uno sviluppatore può sviluppare uno script MicroPython da eseguire su una pyboard e, senza alcuna modifica o con modifiche limitate, eseguirlo anche su ESP8266 o sulla scheda STM32F4 Discovery. Esaminiamo che aspetto potrebbe avere uno script MicroPython di base che interfaccia un barometro BMP280 di Bosch Sensortec e un sensore della temperatura con un bus I2C e poi trasmette i dati su un collegamento seriale Bluetooth utilizzando il modulo RN-42 di Microchip Technology.

BMP280 è un barometro basato su I2C e un sensore della temperatura con un indirizzo slave predefinito per I2C di 119 decimale. Il modo più facile di interfacciarlo con la pyboard è di usare la scheda Gravity di DFRobot, che fornisce un connettore robusto per un facile accesso per alimentare il dispositivo e accedere a I2C. Lo sviluppatore può scegliere il bus I2C1 o I2C2 per collegare la scheda Gravity. Una volta che le schede sono collegate, lo script MicroPython è semplice.

Prima di tutto lo sviluppatore importa la classe I2C dalla libreria pyb. La libreria pyb offre accesso a funzioni periferiche del microcontroller come SPI, I2C e UART. Prima di poter usare qualsiasi periferica, lo sviluppatore deve rappresentare con un'istanza la classe della periferica per creare un oggetto che possa essere utilizzato per controllare la periferica. Una volta inizializzata la classe della periferica, lo sviluppatore può eseguire qualsiasi altra inizializzazione, come verificare che i dispositivi siano presenti prima di entrare nel loop dell'applicazione primaria. Il codice dell'applicazione primaria campionerà quindi il sensore una volta al secondo. Viene illustrato un esempio di come farlo (Listato 1).

Copy
from pyb import I2C
 
GlobalTemp = 0.0
GlobalBarometer = 0.0
 
# Initialize and Instantiate I2C peripheral 2
I2C2 = I2C(2,I2C.MASTER, baudrate=100000)
 
while True:
            SensorSample()
            pyb.delay(1000)
 
def SensorSample():
            #Read the Temperature Data
            TempSample = I2C2.readfrom_mem(119, 0xFA,3)
 
            #Read the Pressure Data
            PressureSample = I2C2.readfrom_mem(119, 0xF7,3)

Listato 1: Script MicroPython che inizializza la periferica I2C e comunica con la scheda Gravity di DFRobot per acquisire i dati del sensore della temperatura e del barometro. (Codice per gentile concessione di Beningo Embedded Group)

Campionare i dati dei sensori e non utilizzarli non è una dimostrazione molto utile di quanto possa essere potente MicroPython nelle mani di un team di sviluppo. Molti team di sviluppo si trovano di fronte a sfide tecniche quando devono collegare i propri dispositivi con sensori a Internet o a un hub di sensori locale tramite Bluetooth.

Un modo facile di aggiungere funzionalità Bluetooth a un progetto consiste nell'usare RN-42. RN-42 può essere impostato su una modalità in cui il microcontroller invia semplicemente i dati UART che dovrebbero essere trasmessi su Bluetooth e RN-42 gestisce l'intero stack Bluetooth (Figura 4).

Immagine di una pyboard che esegue MicroPython collegato al modulo RN-42 Bluetooth su UART

Figura 4: Collegamento di una pyboard che esegue MicroPython su un modulo RN-42 Bluetooth su UART. (Immagine per gentile concessione di Beningo Embedded Group)

Una volta collegata la scheda Bluetooth, uno sviluppatore può creare uno script molto semplice che prende i dati ricevuti dal sensore e li trasmette su Bluetooth a un dispositivo mobile che potrebbe poi salvarli o inoltrarli al cloud per un'ulteriore analisi. È illustrato lo script di esempio (Listato 2). In questo esempio, UART1 è configurato per la trasmissione a 115.200 bps, 8 bit, nessuna parità e un solo bit di stop.

Copy
from pyb import uart
from pyb import I2C
 
GlobalTemp = 0.0
GlobalBarometer = 0.0
 
# Initialize and Instantiate I2C peripheral 2
I2C2 = I2C(2,I2C.MASTER, baudrate=100000)
 
# Configure Uart1 for communication
Uart1 = pyb.UART(1,115200)
Uart1.init(115200, bits=8, parity=None, stop=1)
 
while True:
            SampleSensor()
            pyb.delay(1000)
 
def SensorSample():
            #Read the Temperature Data
            TempSample = I2C2.readfrom_mem(119, 0xFA,3)
 
            #Read the Pressure Data
            PressureSample = I2C2.readfrom_mem(119, 0xF7,3)
 
            #Convert Sample data to string
            data = “#,temperature=”str(TempSample)+”,pressure”+str(PressureSample)+”,#,\n\r”
 
            #Write the data to Bluetooth
            Uart1.write(data)

Listato 2: Esempio di script MicroPython che inizializza UART1 e comunica con un dispositivo esterno. (Codice per gentile concessione di Beningo Embedded Group)

Oltre alla facile portabilità del codice applicativo su altre piattaforme hardware, l'applicazione usa librerie comuni e funzionalità che sono già state implementate per aiutare gli sviluppatori ad accelerare il proprio lavoro di sviluppo. La creazione dell'applicazione di cui sopra può richiedere un'ora o anche meno, non una settimana o più che sarebbe necessaria se uno sviluppatore dovesse iniziare dai livelli software più bassi.

Suggerimenti e consigli per lo sviluppo di software in tempo reale

Sviluppare applicazioni embedded con MicroPython è facile, ma ottenere prestazioni in tempo reale dal sistema potrebbe non essere così semplice come si potrebbe essere indotti a pensare. Anche se MicroPython offre vantaggi enormi in termini di semplificazione e riutilizzo del codice, ottenere dal sistema una temporizzazione prevedibile e coerente può essere un'impresa ardua se lo sviluppatore non è al corrente di alcuni fatti e librerie interessanti.

MicroPython include un garbage collector che viene eseguito in background e riesce a gestire heap e altre risorse di memoria. Il garbage collector è non deterministico, pertanto gli sviluppatori che si aspettano un comportamento deterministico possono mettersi nei guai se si avvia in una sezione time-critical. Esistono diverse raccomandazioni a cui gli sviluppatori dovrebbero attenersi per assicurarsi che questo non accada.

Prima di tutto, possono importare la libreria di raccolta degli scarti, gc, e usare i metodi abilita e disabilita per controllare quando il garbage collector è abilitato o disabilitato. Lo sviluppatore può disabilitare la raccolta prima di una sezione critica e abilitarla successivamente come illustrato (Listato 3).

Copy
import gc
 
gc.disable()
 
#My time critical code
 
gc.enable()

Listato 3: Disabilitazione del garbage collector MicroPython prima di una sezione di codice time-critical. (Codice per gentile concessione di Beningo Embedded Group)

In secondo luogo, lo sviluppatore può anche controllare manualmente il processo di raccolta degli scarti. Quando crea e distrugge oggetti, alloca memoria nell'heap. Il garbage collector viene eseguito e libera lo spazio inutilizzato. Dato che lo fa a intervalli irregolari, gli sviluppatori possono usare il metodo di raccolta per eseguire la raccolta degli scarti periodicamente al fine di assicurare che lo spazio sull'heap non si riempia. In questo caso, i cicli di raccolta possono durare da 10 millisecondi a meno di un millisecondo l'uno. Il richiamo manuale della raccolta assicura inoltre che l'applicazione dello sviluppatore abbia il controllo sul codice temporizzato in modo non deterministico. Questo gli consente di decidere quando eseguire la raccolta e assicura alla sua applicazione prestazioni in tempo reale.

Esistono diverse altre best practice che gli sviluppatori interessati a scrivere codice in tempo reale possono seguire. Tra i vantaggi:

  • Uso di buffer preallocati per i canali di comunicazione
  • Uso del metodo readinto quando si utilizzano periferiche di comunicazione
  • Possibilità di evitare la documentazione Python tradizionale utilizzando ###
  • Riduzione al minimo della creazione e distruzione di oggetti durante il runtime
  • Monitoraggio dei tempi di esecuzione delle applicazioni

Gli sviluppatori interessati a capire altre "best practice" possono leggere la documentazione sull'ottimizzazione MicroPython qui.

Conclusione

MicroPython è una piattaforma interessante per gli sviluppatori che desiderano implementare applicazioni in tempo reale indipendenti dall'hardware del microcontroller sottostante. Gli sviluppatori possono scrivere i propri script Python ad alto livello utilizzando le librerie standard fornite in MicroPython ed eseguirli su qualsiasi microcontroller supportato. Questo offre loro molti vantaggi, fra cui:

  • Miglior riutilizzo dell'applicazione
  • Riduzione del tempo di immissione sul mercato
  • Disaccoppiamento dell'applicazione dall'hardware

MicroPython non è perfetto per qualsiasi applicazione ma finora ha avuto successo in quelle industriali e spaziali, assieme a una rapida prototipazione e dimostrazione della validità di un progetto in fase di sviluppo.

Esonero della responsabilità: le opinioni, le convinzioni e i punti di vista espressi dai vari autori e/o dai partecipanti al forum su questo sito Web non riflettono necessariamente le opinioni, le convinzioni e i punti di vista di Digi-Key Electronics o le sue politiche.

Informazioni su questo autore

Jacob Beningo

Jacob Beningo è un consulente software embedded e attualmente lavora con clienti in più di una decina di paesi per trasformare radicalmente le loro attività migliorando la qualità dei prodotti, i costi e il time-to-market. Ha pubblicato più di 200 articoli sulle tecniche di sviluppo di software embedded, è un relatore e un istruttore tecnico e ha conseguito tre lauree, tra cui un master in ingegneria presso University of Michigan. Risponde all'indirizzo jacob@beningo.com, ha un sito web personale www.beningo.com e produce una Newsletter mensile Embedded Bytes cui è possibile iscriversi.

Informazioni su questo editore

Editori nordamericani di Digi-Key