Primo appuntamento

Questo corso e’ indirizzato a chi ha avuto un interesse con cose come Arduino, senza averne mai approfondito la conoscenza.
E’ necessario avere praticita’ con il codice scritto in C e anche un minima conoscenza di elettronica di base, cosa sia ad esempio un resistore, un condensatore, ma puo’ anche essere un buon motivo per cominciare a capirci qualcosa, anche se molte delle cose che diremo all’inizio non saranno limpidissime.
Faremo ora e’ una introduzione ai microcontrollori e impareremo a configurare su GNU Linux un framework di lavoro che ci consentira’ di scrivere e realizzare dei piccoli progetti.

Innanzitutto: Cosa e’ un microcontrollore ?

Un microcontrollore è un circuito integrato che viene solitamente collegato ad altri componenti elettronici, ed e’ programmabile per gestire delle applicazioni specifiche.
E’ un sistema che integra in uno stesso chip il processore, la memoria permanente, la ram e i canali per I/O (solitamente chiamati pin).
Quando parliamo della CPU, invece, ci riferiamo al semplice processore di calcolo, che non e’ in grado, ad esempio, di conservare il programma che deve eseguire.
I microcontrollori moderni generalmente hanno due memorie differenti per memorizzare il programma e i dati, e sono indirizzabili separatamente.
Il programma, codificato con le sue istruzioni, viene solitamente conservato in una memoria flash che non e’ volatile, quindi sopravvive ai reboot.

Parleremo ora nello speficico di STM32, prodotto dalla ST Microelettronics.
Ne esistono diverse versioni; tipicamente vengono vendute su una board che monta anche dei comodi collegamenti per i pin, una porta usb, un led comodo per fare diagnostica di funzionamento e altre cose.
Utilizza un processore ARM che oramai ha conquistato il mondo dei dispositivi embedded, e si trova ovunque: negli smartphone, nei tablet, nelle automobili nei frigoriferi, monitor stampanti, praticamente dappertutto.
Nello specifico noi useremo questo, detto anche “Blue Pill” che ha un processore a 72Mhz di 32-bit, 64 kb di memoria flash, e 20 kb di RAM.

PRO
compatibile con Arduino IDE
hardware piuttosto evoluto: 32bit per un microcontrollore non sono pochi, parecchia memoria, ecc.
supporto usb
costo basso
il programmatore costa pochi euro e comunque puoi anche non usarlo
lo puoi programmare con gcc senza installare cose troppo strane
CONTRO
non è piccolissimo. i PIC o gli atmega sono molto più piccoli, quindi più adatti ad essere infilati in progetti elettronici
la base di utenti è grande ma non grandissima. sicuramente meno della base di atmega
la scheda funziona a 3.3V e il regolatore di tensione dall’USB che abbassa il voltaggio da 5V e’ fragile, dunque alimentando componenti esterni si rischia di bruciare tutta la scheda

RICHIAMI GENERALI DI TEORIA

Il sistema che andremo a programmare sara’ un solo eseguibile, e una volta caricato rimane la’ e viene eseguito ad ogni accensione del microcontrollore. Spesso si usa la parola “firmware” per questo tipo di programmi, dove tipicamente non occorre un vero sistema operativo come siamo abituati a pensarlo, quindi non avremo la possibilita’ di avere un interprete dei comandi o un gestore a finestre.
Prima di entrare nello specifico della programmazione, facciamo un breve ripasso di alcuni concetti chiave.

Programmare utilizzando i task

Le funzioni sono dei raggruppamenti di istruzioni che si occupano di svolgere una operazione piu’ o meno complessa, e il piu’ delle volte ricevono dei parametri in ingresso e restituiscono dei valori come risultato.
Una applicazione software viene solitamente eseguita creando un processo che si occupa di eseguire una serie ben definita di azioni, opportunamente definite in funzioni.
Generalmente un sistema complesso lo possiamo dividere in una serie di processi, che possono essere interconnessi tra di loro utilizzando delle risorse (come ad esempio degli spazi di memoria) per passarsi degli input e degli output.

Logicamente possiamo raggruppare un blocco di codice o di comandi e definirlo come “thread”.
Il sistema operativo e’ in grado di gestire contemporaneamente piu’ di un thread alla volta, assegnandogli opportunamente del tempo di processamento della CPU e switchando da un thread all’altro in modo da concedere a tutti un turno per eseguire il proprio lavoro.
Un thread semplicemente e’ definibile come una funzione che viene eseguita parallelamente alla funzione che l’ha chiamata, e la funzione chiamante a sua volta e’ in grado di sincronizzarsi con il thread invocato, per ricevere il suo stato di esecuzione.
Se un sistema non e’ in grado di far comunicare i thread tra di loro per la sincronizzazione, si preferisce usare il nome “task”.

Questa cosa di usare nomi come thread e task genera spesso confusione, ma solo quando diventa veramente necessario e’ il caso di andare ad approfondire e metabolizzare bene questo concetto.
Per ora ci basta ricordare che esistono sistemi “multiprogrammati”, e il multitasking e’ un modo di fare multiprogrammazione.
Il multithreading, invece, e’ un modo di fare il multitasking utilizzando i thread.

Semplificando possiamo dire che un task e’ un singolo thread che gira in un singolo processo, e nei nostri progetti parleremo semplicemente di “task” che saranno delle semplici funzioni scritte in C che non escono mai, e sono tipicamente implementate come un loop infinito.

Ora vediamo nella pratica come viene realizzato un sistema operativo RealTime. Usiamo l’espressione RealTime per definire un sistema che risponda a degli eventi esterni con una reazione che sia il piu’ veloce possibile, cioe’ il tempo impiegato per eseguire le operazioni necessarie a reagire a un evento deve essere minore del tempo in cui si riceve un evento dello stesso tipo.
Ad interagire con l’hardware abbiamo quello che viene definito uno “strato di astrazione” Hardware Abstraction Layer (HAL) e ci consente di trattare ad alto livello protocolli e oggetti differenti. Sentirete parlare di UART, USB, I2C, SPI, TIMERS, etc., ed avremo un modo comodo di utilizzarli.

Parleremo di due librerie che – combinati – ci danno un buon ambiente di programmazione:
LibOpenCM3 – un hardware abstraction layer
FreeRTOS – per la gestione di task multipli in parallelo

LIBOPENCM3

Quando operiamo con dei componenti esterni potrebbero essere necessarie delle routine di inizializzazione per permetterne il corretto funzionamento elettrico, che magari interessano una serie di registri e aree dei memoria da scrivere e leggere in un tempo ben definito.
Per operazioni simili a quelle descritte esiste la libreria opensource libopencm3 scritta in C, che definisce ed implementa costanti e funzioni che semplificano l’interazione con le periferiche, facendoci risparmiare tantissimo tempo invece di scriverle da zero.

Innanzitutto, nei progammi del laboratorio che vedremo piu’ tardi, illustreremo l’uso e la configurazione dei pin GPIO (General Purpose Input/Output).
void gpio_set(uint32_t gpioport, uint16_t gpios);
void gpio_clear(uint32_t gpioport, uint16_t gpios);
uint16_t gpio_get(uint32_t gpioport, uint16_t gpios);
void gpio_toggle(uint32_t gpioport, uint16_t gpios);
uint16_t gpio_port_read(uint32_t gpioport);
void gpio_port_write(uint32_t gpioport, uint16_t data);
void gpio_port_config_lock(uint32_t gpioport, uint16_t gpios);

In ogni funzione il primo parametro puo’ essere o GPIOA,GPIOB,GPIOC .. mentre i pin li selezioniamo con le macro definite GPIO0 fino a GPIO15.
Attenzione che non tutti i pin possono essere alimentati da una tensione a 5V, per ora ci basti sapere che tutti possono riceve 3.3V di segnale con una corrente di 25mA.

FreeRTOS

E’ un sistema che implementa funzioni per fare multitasking, sincronizzazione, code, strutture dati, ecc.
Per ora tralasciamo tutte le problematiche di sincronizzazione per i task (atomicita’ delle istruzioni, zone critiche, etc.)

I due tipi di processo che generalmente troviamo nei sistemi embedded RT sono i task e le ISR (Interrupt service routine).
Fondamentalmente le ISR e task sono simili, con la differenza che le ISR non possono andare in wait, ma possono essere eseguite per poi terminare.
Nelle applicazioni embedded gli interrupt sono la principale sorgente di eventi. Gli interrupt risvegliano direttamente le ISR che a loro volta possono portare un task in uno stato “running”.

Con che priorita’ vengono eseguite le funzioni ?
Il livello di priorita’ piu’ bassa e’ generalmente riservato a quello che viene chiamato “idle task”. E’ il task che viene eseguito quando gli altri tak e le ISR non sono pronte per l’esecuzione. Le regole per lo scheduler sono veramente semplici: il task che verra’ eseguito sara’ quello che avra’ il livello di priorita’ piu’ alto, e vale chiaramente anche per le ISR.
In questo modello di scheduling i task sono fissati in un range di interi ben definiti, da un minimo a un massimo. L’intervallo di per se’ non e’ importante. Generalmente le ISR sono considerati dei task la cui priorita’ e’ immediatamente piu’ alta a quella dei semplici task.

In sostanza usare FreeRTOS permette di accedere a:
- preemptive multitasking, quindi il risveglio dei task e’ generalmente predicibile
- code
- mutexes e semafori
- timer

Per quanto riguarda i timer, conviene soffermarcisi un attimo in quanto nell’elettronica digitale molto e’ legato al tempo. Timer, contatori, frequenza, periodo dell’impulso, clock, sono alcune parole tipiche di questo argomento. I microcontrollori hanno bisogno di un battito che segni il tempo, e questo viene da quello che comunemente chiamiamo il clock. L’STM32 integra diversi timer progettati per diverse applicazioni, come ad esempio misurare il periodo d’onda di una funzione o generare degli impulsi modulati a periodo (PWM oulse widht modulation), e sono in grado di generare degli eventi in determinate circostanze. Generalmente i timer dei microcontrollori sono abbastanza semplici, ma quelli di STM32 sono particolarmente elaborati e complessi (nel libro che vi lasciamo come riferimento circa un quarto delle pagne e’ dedicato a questo argomento).
Per riassumere i timer di STM32 li possiamo dividere in due categorie:
Basic Timers
Advanced Timers, General Purpose Timers

I timer general purpose li possiamo usare per generare degli impulsi PWM o catturare un input, o per generare segnali.
Gli advanced timers sono simili a quelli general purpose, ma hanno l’aggiunta di essere in grado di generare un segnale PWM complementare, come la possibilita di avere una scadenza per questi segnali. Queste funzionalita’ sono utili per applicazioni che controllano motori, inverter, alimentatori switched e altri componenti di potenza.

I timer sono caratterizzati da un registro a 16-bit di “auto reload” e un altro sempre a 16-bit di prescaler.
Esiste un registro principale, il counter, che che e’ strettamentamente legato a quello di auto-reload.
Il prescaler serve a dividere il segnale del clock in periodi, secondo le necessita’ di programmazione.
Tutti e tre i registri possono letti e scritti dal software.

  1. Upcounter TIM_CLK
    - Counter Register (TIMx_CNT)
    - Prescaler Register (TIMx_PSC):
    - Auto-Reload Register (TIMx_ARR)

Con l’eccezione dei basic timers, tutti i timer di STM32 hanno quattro canali indipendenti di I/O (TIMx_CH1 – TIMx_CH4). Questi canali possono essere usati per:
Input Capture (Eg: misurare il periodo di un segnale)
Output Compare or PWM
One Pulse Mode

Teoria pratica

Teoria toolchain

(20min) la compilazione: cenni generali, cose a cui stare attento nello specifico stiamo cross-compilando installare tutto impostazione dei programmi: si tende a copiare tutto dentro la cartella del singolo progetto, per poter personalizzare tutto in modo rozzo ma rapido compilazione con make flahsare dfu-util per caricare tutto impacchettato nel makefile con make upload

Esempi programmi

Tutti questi esempi hanno un corrispettivo in una cartella di programmi che abbiamo distribuito nel workshop (TODO: fai upload)

I piu semplici
Led blink: facciamo blinkare un led, compiliamo flashamo Button led 1: accendi un led con un pulsante
Introduciamo un RTOS
blink + Button con RTOS: fai blinkare un led mentre ne accendi un altro con un pulsante primi vantaggi di un RTOS: i task spesso è più facile pensare a funzionalità “indipendenti” che scrivere un codice intricato che le faccia tutte. in realtà fa tante altre cose utili che vedremo meglio la prossima volta
Gli interrupt
Button blink 3 con Interrupt

Laboratorio

mettiamo tutto in pratica, installiamo tutto, compiliamo i primi programmi.

Gli interrupt mostrano i loro difetti! serve il “debouncing” (hardware o software)

Il bootloader

In generale per caricare un programma su un stm32 serve usare il programmatore st-link v2.
we.riseup.net/assets/486939/stm32-stlin...
Il programmatore costa poco e funziona bene, ma potremmo anche farne a meno se abbiamo gia’ caricato sul microcontrollore un bootloader.
Il bootloader e’ un software particolare che ci consente di riprogrammare agevolmente al momento del boot l’STM32 sfruttando la porta USB, oppure di saltare direttamente all’esecuzione di un programma che era stato precedentemente caricato.
Vediamo ora come e’ possibile riservare una prima parte della memoria flash per il bootloader (solitamente molto piccola, si va dai 256bytes ai 4KB).

Innanzitutto scarichiamo il programma che copieremo sul microcontrollore:
wget ‘https://github.com/rogerclarkmelbourne/STM32duino-bootloader/blob/master/binaries/generic_boot20_pc13.bin?raw=true’

Metodo con ST-LINK

st-flash write generic_boot20_pc13.bin 0×8000000
Dovreste vedere che il led verde fa un blink caratteristico per meno di un secondo. Lo rifà ogni volta che premete reset o che staccate e riattaccate la corrente. Se lo fa, ha funzionato tutto.
Se non lo fa .. capita ! Da alcuni computer questa procedura non ci funziona e non capiamo perché. Prova con il metodo successivo.
Se funziona, invece, scollega l’stlink e collega il cavo microusb: da questo momento possiamo caricare i nostri programmi via USB.

Metodo con usb-ttl

se non avete un stlink, o se sul vostro computer non funziona bene, potete usare un usb-ttl (esempi: cp2102, ft232, pl2303). In questo caso il programma che vi serve è stm32flash (apt-get install stm32flash).
Il vantaggio è che un usb-ttl è un oggetto più diffuso,
Collegate il tx del vostro usb-ttl al pin A9 e l’rx al pin A10. Collegate anche la massa. Collegate l’usb-ttl al computer. Dovrebbe comparire il device /dev/ttyUSB0.
Spostate il jumper BOOT0 al valore 1, premete reset, quindi fate:
stm32flash -w generic_boot20_pc13.bin -v /dev/ttyUSB0
a questo punto spostate il jumper BOOT0 al valore 0, ri-premete reset.
Dovreste vedere il led verde sulla board blinkare in modo “caratteristico” per meno di un secondo. Ci siete riusciti!
Staccate l’usb-ttl e collegate il cavo microusb: da questo momento possiamo caricare i nostri programmi via USB.