Sviluppare in C per STM8

Di recente ho fatto una ricerca in Internet per trovare il microcontrollore più economico. Tale ricerca è stata effettuata sui siti dei maggiori distributori professionali di componenti elettronici ed il risultato è stato chiarissimo: il micro più economico (per quantità da produzione) è lo STM8.

Le versioni disponibili sono tante, ma rimanendo sul principio del “very cheap”, ho selezionato due modelli. Il primo è lo STM8S001J3M3 (8 pin) e il secondo lo STM8S103F3P6 (20 pin). Per vostra informazione, il secondo si trova anche montato su delle piccole schede di sviluppo, di manifattura Cinese, disponibili on-line a 1 Euro! Sì, con 1 Euro comprate una scheda di sviluppo che comprende: il microcontrollore, una presa microUSB per l’alimentazione, un regolatore di tensione a 3.3V, un pulsantino di reset e un paio di leds. La scheda ha inoltre i connettori “strip” a passo 2.54mm per collegare le vostre periferiche agli I/O disponibili.

Per poter caricare e testare i vostri programmi sul microcontrollore, avete poi bisogno dello strumento hardware che svolge le funzioni di programmer / debugger. Bene, questo strumento (che potete trovare cercando: ST-Link V2) si compra on-line, sempre di produzione Cinese, a meno di 2 Euro! Sì, programmare e fare debug in real-time per meno di 2 Euro! E’ incredibile. Io iniziai la mia carriera sul sistema di sviluppo Intel nel 1977 e ricordo che il “blue box” costava quanto un’automobile di media cilindrata… Se siete curiosi di vedere com’era fatto, potrete trovarlo in una foto del mio ufficio di progettazione (nel 1979) a destra di un me stesso assai più giovane e magro 🙂 Quell’oggetto che sembra un PC, ma non lo è, perché il PC ancora non esisteva, è in effetti il sistema di sviluppo Intel per microprocessori 8080/8085.

Torniamo ad oggi: ho comprato il modulo con il microcontrollore e quello con il programmatore, ma poi ho deciso di montare anche un circuitino tutto mio su una scheda per prototipi. La ragione di questa decisione è la necessità di testare il micro più piccolo, quello con soli 8 pin, che dovrà andare a sostituire modelli analoghi in alcuni miei progetti di produzione industriale.

Il sistema di sviluppo completo. Programmer/Debugger e microcontrollore STM8S001J3M3

Il microcontrollore piccolo (STM8S001J3M3) ha alcune limitazioni e presenta dei rischi. Se programmato con impostazioni errate delle porte di I/O, diventa irrecuperabilmente bloccato e non più riprogrammabile! Questo problema non si presenta con il modello a 20 pin e più avanti vedremo perché.

Gli strumenti software

Ora che abbiamo tutto il necessario per quanto riguarda l’hardware, possiamo pensare al software. Anche in questo caso avremo delle liete sorprese. Tutto (e dico tutto) è disponibile gratuitamente! Abbiamo a disposizione l’ambiente di sviluppo (IDE) e il software per caricare il programma sul microcontrollore forniti direttamente dalla ST e il compilatore C (completo e senza limitazioni) fornito dalla Cosmic. Per scaricare i pacchetti software è necessaria una registrazione, ma è gratuita. Quindi, facciamo i conti: 1 Euro per il modulino di test, 2 Euro per il programmatore e zero Euro per il software di sviluppo… Con 3 Euro possiamo scrivere software in C ed effettuare il debug in real-time. Beh, non è caro! 🙂

Ecco i link che vi propongo per leggere la documentazione (abbondante) e scaricare il software:

-sito principale con le informazioni ed i vari documenti disponibili: http://www.st.com/en/development-tools/stm8-software-development-tools.html

-pagina di download del programmatore STVP-STM8: http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm8-software-development-tools/stm8-programmers/stvp-stm8.html

-pagina di download dell’ambiente di sviluppo IDE STVD-STM8: http://www.st.com/content/st_com/en/products/development-tools/software-development-tools/stm8-software-development-tools/stm8-programmers/stvd-stm8.html

-pagina di download del compilatore C di Cosmic: http://cosmicsoftware.com/download_stm8_free.php

Come anticipato, per poter effettuare i downloads sarà necessaria una rapida registrazione gratuita sia sul sito ST che sul Cosmic.

Oltre al software scaricato, conviene procurarsi subito tre PDF molto utili. Il primo è il datasheet del componente e si chiama stm8s001j3.pdf; il secondo è la lista dettagliata dei registri delle periferiche e si chiama RM0016.pdf; il terzo è il manuale di programmazione con le istruzioni assembler e si chiama PM0044.pdf. Inserendo i nomi elencati in un motore di ricerca, troverete i link per il download. Probabilmente l’ultimo file non vi servirà per scrivere applicazioni “normali”, ma se volete capire bene come funziona un micro (qualsiasi micro), date sempre una lettura approfondita alle sue istruzioni assembler, ai modi di indirizzamento, eccetera.

Installazione

Per prima cosa installiamo il compilatore Cosmic C. Ad installazione avvenuta vedremo che abbiamo a disposizione sia il compilatore per STM8 (completo e senza limiti) che quello per STM32. Quest’ultimo, però, ha un limite di 32K (sufficienti, comunque, per molte applicazioni). Si noti che il compilatore installato ha anche un suo IDE (chiamato IDEA-STM8) e potrebbe quindi essere usato come strumento di sviluppo completo, senza usare i software ST. Io ho deciso di usare l’IDE ST, ma è stata solo una scelta personale. Nulla vieta di utilizzare l’ambiente Cosmic, ma gli esempi che pubblicherò di seguito sono stati scritti per quello ST.

Oltre ad aver fatto la registrazione al sito Cosmic, dovremo ora ottenere la licenza (gratuita) per il compilatore. Per far ciò dobbiamo andare nella cartella dove è installato il compilatore e cercare il programma apposito:

C:\Program Files\COSMIC\FSE_Compilers\CXSTM8\LmregFSE.exe

Ovviamente, il percorso è valido se il programma è stato installato su C:\Program Files. Lanciando l’applicazione, troveremo dei campi da riempire.

Inseriamo quindi i nostri dati e clicchiamo il pulsante On the Web. Riceveremo quindi sulla nostra email un file di licenza da copiare nella directory di installazione del compilatore (notate che ho salvato anche il file lic-data che contiene i dati inviati per ottenere la licenza, giusto per avere un riferimento futuro).

Se non eseguiamo questa operazione oppure se copiamo il file di licenza su di un altro computer, all’atto della compilazione di un sorgente in C riceveremo questo errore (cliccare l’immagine per ingrandire):

Quindi, se cambiamo PC e reinstalliamo il compilatore, dovremo eseguire nuovamente la fase di richiesta della licenza.

Ora installiamo i due software ST che abbiamo scaricato in precedenza e cioè il programmatore e l’IDE. Qui non dovremo effettuare alcuna richiesta di licenza. Al termine di tutte le installazioni troveremo sul nostro desktop tre nuove icone.

Lanciamo l’IDE (icona centrale) e iniziamo a prendere confidenza con l’ambiente. Nella figura sottostante vediamo il programma aperto sullo spazio di lavoro (workspace) chiamato Test. In genere, per progetti semplici come sono quelli da pochi KB di codice, io uso lo stesso nome sia per lo spazio di lavoro che per il progetto. Proviamo quindi ad aprire il tab Project e selezioniamo dalla lista la voce Settings.

Si aprirà quindi la finestra delle impostazioni. Nella figura successiva è mostrato il primo tab (General) relativo alla configurazione Release, cioè quella finale, destinata alla produzione. In alto a sinistra (dove c’è scritto Settings for: Release) è possibile cambiare la selezione in Debug, quella che sarà utilizzata nella fase di sviluppo del programma.

Possiamo lasciare gran parte delle impostazioni standard, limitandoci a scegliere solo il tipo di microcontrollore da usare (tab MCU Selection), permettendo così al sistema di utilizzare i valori di default ad esso collegati.

Hardware

Ora possiamo iniziare con l’hardware. Lo schema del circuito minimo è quello visibile nella figura sottostante (cliccare l’immagine per ingrandirla).

Come vedete, è davvero molto semplice e si può montare su una scheda per prototipi, utilizzando per il microcontrollore un adattatore da SO8N a DIL, reperibile on-line a costo minimo. Se invece vi dilettate di fotoincisione, potete usare il piccolo PCB che ho disegnato.

Il PDF in scala 1:1 per effettuare la fotoincisione (singola faccia, lato rame) può essere
scaricato da questo link (file Top.pdf)

Come si vede dallo schema elettrico, i (pochi) pin di I/O disponibili sono assegnati a più porte del micocontrollore. Questo significa che i pin sono fisicamente collegati a più I/O in parallelo.Questo non è un problema al momento del reset, in quanto tutte le linee di I/O del micro si settano automaticamente come inputs ad alta impedenza. Ovviamente, se durante la fase di inizializzazione dell’I/O del nostro programma mettiamo due linee parallelate in stato di Output push-pull e diamo ad una il valore “basso” e all’altra “alto”, facciamo una gran brutta cosa! Bisogna quindi fare attenzione. Da un certo punto di vista, il fatto di avere delle linee di I/O parallelate può essere un vantaggio: per esempio, possiamo ottenere da un pin di output una corrente maggiore se pilotiamo contemporaneamente due o più porte. Se usiamo il pin 7 e settiamo le porte PC3, PC4 e PC5 come outputs push-pull (pilotando le linee contemporaneamente tutte alte o basse), potremo ottenere una corrente tripla, rispetto a quella di una singola porta.

Questo può essere molto utile, per esempio, per pilotare un mosfet di potenza. Il problema dei pin parallelati si fa sentire in modo pesante sul pin 8, non a caso il più parallelato di tutti. Ci sono ben 4 porte del micro collegate allo stesso pin e purtroppo è anche il pin di SWIM, cioè l’interfaccia di programmazione / debug, quella che ci permette di scaricare / debuggare il nostro firmware. Cosa succede se nel nostro programma assegniamo ad una delle porte PC6, PD1, PD3 e PD5 la funzione di output push-pull? Semplice: lo SWIM non funziona più e il micro è bloccato per sempre. Ho tentato mille manovre, sperando di recuperarlo (sì, ho fatto un errore anch’io) e non è stato possibile. Ho tentato anche di alimentare il micro sotto la tensione di brown-out per sfruttare il reset in tale condizione, ma non c’è stato niente da fare. Attenzione: se notate, la porta PD5 è usata anche come linea di trasmissione della UART e così se userete la porta seriale nel vostro programma, al momento del set-up del relativo registro di controllo automaticamente bloccherete il micro e dovrete dissaldarlo e buttarlo via (è quello che è successo a me).
La ST suggerisce (fatelo) di inserire all’inizio del programma, proprio al reset, una pausa di circa 5 secondi prima di fare qualsiasi operazione di inizializzazione di porte e registri. In questo tempo lo SWIM funzionerà di sicuro e quindi vi sarà possibile riscrivere un programma opportunamente corretto, in caso di errori. Ovviamente è una cosa un po’ limitativa, perché 5 secondi di attesa al power-on sono inaccettabili per qualsiasi progetto industriale, ma quando questo sarà completamente testato, potrete rimuovere la pausa iniziale di 5 secondi, con la consapevolezza, però, di avere un micro OTP (One Time Programmable). Un vero peccato, specie perché la UART a bordo di un micro così piccolo ed economico è un bel “plus”. Si noti che la UART può essere “rimappata”su un altro pin (il 5, connesso a PA3). Infatti, dovendo usare la UART, la mia idea è stata subito quella di rimappare su PA3 lo UART1_TX eliminando così il problema della sovrapposizione con lo SWIM. Il remap si può eseguire tramite i registri di configurazione (con il programmatore) e quindi credevo di aver risolto i miei problemi. Purtroppo, dopo aver fatto il remap, il segnale di UART1_TX era presente (trasmissione dei caratteri OK), ma la ricezione sul pin 1 (PD6) non funzionava assolutamente. Ho provato in mille modi, cercando di capire perché nessuno dei “flag” relativi alla ricezione seriale o agli errori venisse settato, nonostante le impostazioni fossero corrette (e confermate dai segnali che uscivano regolarmente da TXD). Alla fine sono andato sul forum della ST e lì ho trovato altra gente col medesimo problema. Ho aggiunto la mia esperienza e le mie considerazioni (https://community.st.com/thread/46397-stm8s001j3-uart-remap e https://community.st.com/thread/47302-stm8s001the-uart-of-alternate-function-after-remapunable-to-receive), chiedendo che qualcuno degli esperti tecnici ST intervenisse per dare una risposta, ma non si è fatto vivo nessuno.  Per quanto ne so, al momento, il remapping della UART non funziona. Aggiornamento del 29/06/2018: un tecnico della ST ha risposto al quesito confermando la mia ipotesi. Il pin RX viene anch’esso rimappato, ma su un I/O non accessibile. La UART potrebbe quindi essere utilizzabile solo in modo single-wire / half-duplex. Per usare la UART sul pin “naturale”,  dovete quindi considerare che il ritardo iniziale di 5 secondi DEVE essere inserito, prima di effettuarne l’abilitazione, oppure butterete via un sacco di microcontrollori.
In fase di produzione, quando il ritardo di 5 secondi al power-on sarà inaccettabile, suggerisco di inserire nella routine di ricezione seriale, la decodifica di un comando “speciale”, per esempio +++ e Ctrl-C. Quando il programma riceverà questa sequenza, dovrà disabilitare la UART e rimanere in un loop infinito. In questa condizione sarà possibile usare di nuovo lo SWIM e riprogrammare il micro. E’ uno sporco trucco e sarebbe molto meglio se il remapping venisse risolto o se la porta PD5 venisse mappata altrove (per esempio sul pin 6), ma questo significa aspettare una nuova maschera del chip (se pure qualcuno della ST si convincesse a farla).

Il primo programma

Iniziamo con il primo programma, un classico “led blinker” che ci permette di testare alcune funzioni base, tra cui l’interrupt del Timer 4. L’intera cartella di progetto si può scaricare da questo link. Il file è zippato; per decomprimerlo usare il programma 7Z e la password: eficara.

Mettete la cartella scompattata in una directory di lavoro, per esempio documenti/progetti STM8. Al suo interno troverete questi files:


Cliccate sul file Test.stw e il sistema vi chiederà (dato che l’estensione .stw non è associata) con quale programma volete aprire il file. Allora andate sul percorso evidenziato nella figura sotto e scegliete l’IDE della ST.

A questo punto l’IDE si aprirà e subito vi verrà richiesto dove si trova il file di licenza del compilatore C Cosmic.

Cliccando next si aprirà un dialogo di navigazione dei files e voi andrete a specificare la cartella License che abbiamo visto in precedenza. Fornito il percorso verso il file di licenza, tutto è pronto all’uso. Colleghiamo quindi ad una porta USB il nostro programmatore / emulatore compatibile ST-Link V2. Verrà subito riconosciuto dal sistema e il driver verrà scaricato da internet. Associamo quindi il debugger all’IDE cliccando sul tab Debug Instrument.

Ora siamo pronti. Nella finestra a sinistra troviamo i files sorgenti in C che sono inclusi nel progetto e che sono main.c e stm8_interrupt_vector.c. Clicchiamo sull’uno e sull’altro ed entrambi verrano aperti nella finestra di edit a destra, con due differenti tabs. A questo punto lanciamo la compilazione cliccando il “compila tutto”.

Se tutto è stato impostato correttamente, non avremo errori di compilazione. Ora colleghiamo il nostro circuito di test all’emulatore ST-Link. Useremo i 3 fili GND, SWIM e 3.3V (oppure 5V). In questo modo il circuito verrà alimentato direttamente dall’emulatore. Si noti che l’emulatore ST-Link V2 “vero“, quello di serie fornito dalla casa madre, NON ha l’uscita di tensione attiva, anzi, presume che il circuito sia alimentato dall’esterno. Questo è certamente più corretto, in quanto la circuiteria interna deve “adattarsi” alla tensione di alimentazione del DUT (Device Under Test). Nel nostro caso, abbiamo speso poco e ci accontentiamo di una soluzione tecnica meno precisa…

Avviamo quindi il debug cliccando l’apposita icona.

La versione del programma appena compilata verrà trasferita nel microcontrollore. Al termine del trasferimento potremo avviare l’esecuzione del firmware cliccando l’apposito pulsante (run).

Aspettiamo i famosi 5 secondi che precedono l’esecuzione del programma vero e proprio e poi vedremo lampeggiare il LED sul circuito. Se lampeggia, è tutto a posto. Potremo d’ora in poi sviluppare le nostre applicazioni utilizzando questo bell’ambiente di lavoro completamente gratuito.

Analisi del file sorgente in C

Diamo ora una rapida scorsa al listato dei files sorgenti in C, iniziando da main.c:

Qui vediamo alcune definizioni che uso per mantenere la compatibilità con altri compilatori (per altri microcontrollori) che uso abitualmente. Per esempio, definisco _CLI() come _asm(“SIM”) perché con altri microcontrollori uso questa istruzione per disabilitare gli interrupts; così posso passare più facilmente da un listato scritto per un micro ad un altro modificando solo i define iniziali. Stesso discorso vale per BYTE e WORD al posto di unsigned char e unsigned int. Si tratta quindi di una mera “traduzione”.

Qui ho indicato il file di header da includere. Questo contiene tutte le definizioni relative allo specifico microcontrollore. Bisogna stare attenti al file che si include: se si usa il generico iostm8s.h, per esempio, i registri del timer TIM4 risultano all’indirizzo sbagliato e quindi il timer non funzionerà!

Qui ho definito una costante che userò nel caricamento di un registro del timer TIM4 per ottenere un “tick” di 1mS.

Qui ho definito una variabile bit (boolean) che corrisponde al bit 4 del registro PB_ODR, che è in pratica la PortB (out). In questo modo potrò usare il simbolo RLED per fare riferimento alla linea di I/O.

Qui ho definito due variabili BYTE che userò nel programma. La keyword “volatile” sta ad indicare che il compilatore non dovrà fare ottimizzazioni su di esse. Conviene usare sempre questa modalità per variabili che vengono usate sia in interrupt che nel main (contatori, flags, eccetera).

Ecco il “corpo” dell’interrupt relativo al timer TIM4. La funzione viene eseguita con un timing di 1mS. Notate la riga 49, dove il flag relativo all’interrupt attivo viene resettato. Se non lo si resetta, il programma continuerà ad eseguire interrupts a valanga! Nelle istruzioni successive si incrementa un contatore e quando il nuovo valore vale 0 (incremento da 255 +1), viene settato il flag “flag250ms” che verrà usato nel main successivamente. Questo è solo un esempio d’uso, la stessa funzione può essere realizzata in mille modi!

Questa è la funzione chiamata ciclicamente dal main. Il suo nome è TimedEvents perché abitualmente creo una serie di task non bloccanti, ognuno dei quali si occupa di eseguire le proprie operazioni e quindi rientra al main. In questo caso, la funzione ritorna senza fare nulla se il flag250ms non è settato. In caso contrario, il flag viene resettato e viene eseguito il compito “a tempo” (cambio di stato del Led rosso). Si notino le istruzioni _CLI() prima e _SEI() dopo il reset della variabile flag. Per ogni variabile utilizzata sia nel main che nell’interrupt è necessario fare in modo che non ci possano essere scritture in interrupt mentre il main ne sta eseguendo un’altra. Insomma, è un modo come un altro per dare un accesso esclusivo o protetto ad una variabile.

Ecco il “main” vero e proprio. Al reset del micro, si parte da qui. Notiamo una serie di istruzioni ASM che servono per effettuare il famoso ritardo di circa 5 secondi. Il codice avrebbe potuto essere scritto in C, ma ho voluto mostrare come sia possibile utilizzare il compilatore anche per scrivere piccole parti in assembler. Nello specifico, troviamo due loop annidati l’uno dentro l’altro. All’inizio, vengono salvati sullo stack i registri X (16 bits) ed A (8 bit); poi in X viene caricato il valore immediato 0xFFFF e in A il valore 50, quindi il registro A viene decrementato finché non diventa 0 e a questo punto si decrementa il registro X e si torna a settare A col valore 50, fino a quando anche X non arriva a zero. A questo punto i valori iniziali di X ed A vengono recuperati dallo stack e il ritardo è finito. Si passa quindi alla prossima istruzione del codice C.

Nelle linee subito dopo il ritardo iniziale settiamo i valori per i registri che ci interessano. Alla linea 84 forziamo il clock del sistema dall’iniziale 16MHz/8 (2MHz) al più veloce 16MHz/1. Poi impostiamo le porte; non tocchiamo la PortA, la PortC e la PortD perché sono già messe in input automaticamente al reset. Sulla PortB forziamo PB4 a diventare un output, perché ci sarà collegato un led. Attenzione, le linee che possono diventare segnali IIC bus, non sono push-pull, ma open-drain. Dopo l’I/O settiamo infine il timer TIM4 in modo da prendere come prescaler il valore 128 (2 alla 7) e lo forziamo a resettarsi al valore TOP1MS, con il risultato di avere un overflow  (e quindi un interrupt) ogni 1mS.

Infine, ecco il “magro” main loop. Nelle applicazioni “serie”, all’interno del while(1) si mette un reset del watchdog, per fare in modo che le varie funzioni (o i vari tasks) richiamati, siano sotto controllo. Infatti, se una di esse non ritorna al main nel tempo impostato per il watchdog, il micro si resetta e riprende il suo normale ciclo. Ovviamente, è utile tener traccia di questi errori salvando (magari in EEPROM) le condizioni per cui si è avuto un watchdog reset. In un programma ben strutturato, il loop principale sarà sempre una serie di chiamate alle funzioni “concorrenti”, il più semplice possibile.

Vediamo ora l’altro file sorgente che fa parte del progetto: lo stm8_interrupt_vector.c

Alla linea 1 viene definito un nuovo “tipo” di variabile, che è un puntatore alla funzione di interrupt. Poi viene definita una struttura che ci dice che un vettore di interrupt è composto da un byte (il codice ASM relativo alla call interrupt) seguito dall’indirizzo di memoria in cui la funzione è allocata. Ancora dopo, viene definita una funzione di interrupt “fittizia”, che sarà usata per riempire la tabella dei vettori di interrupt nei punti che non usiamo (tutti gli interrupts non utilizzati). Inserendo, in fase di debug, un breakpoint sul “return” (linea 10), potremo accorgerci se qualcosa di imprevisto accade. Per esempio, se abbiamo abilitato un interrupt su un pin di I/O e non abbiamo scritto la relativa procedura, avremo un breakpoint che ci avvisa dell’errore. Infine abbiamo la definizione di _stext() che è la funzione chiamata al reset e che contiene il codice di inizializzazione aggiunto dal compilatore C in base al modello di compilazione richiesto e per ultimo un riferimento alla funzione di interrupt _TIM4_OVF che abbiamo visto sul main. Dato che questa funzione è esterna al modulo stm8_interrupt_vector.c, viene appunto definita come “extern” e sarà il linker ad assegnare l’indirizzo giusto, al termine della compilazione.

E finalmente ecco i “vettori” di interrupt. In pratica, dall’indirizzo 0x8000 (il vettore di reset) a 0x807C (riservato ad irq29) abbiamo una serie di [0x82 0xnn 0xnn 0xnn] dove 0x82 è l’istruzione di call interrupt e i restanti 3 bytes sono l’indirizzo in cui la routine è stata allocata. Un interrupt da timer TIM4, per esempio, provocherà un cambio del program counter dalla locazione corrente verso l’indirizzo 0x8064 con conseguente chiamata della funzione di interrupt. Chiaramente, la chiamata di un interrupt ha come differenza da una chiamata di subroutine qualsiasi, il salvataggio automatico di alcuni registri essenziali, che vengono poi rimessi a posto al termine della routine stessa (anche il return di un interrupt è diverso dal return di una subroutine).

Conclusioni

Faccio presente che tutto ciò che ho scritto (a parte i problemi di blocco del microcontrollore) vale anche per gli altri modelli di STM8, basta cambiare le impostazioni del compilatore (scegliendo il tipo adatto nell’IDE) e il file di header selezionato nel sorgente main.c. In futuro, nel mio tempo libero, aggiungerò altri esempi (un po’ più elaborati) di firmware scritto in C per la serie STM8.

A presto…