Quello che avete appena letto non è il solito titolo: la puntata di oggi del nostro corso di programmazione su ARM sarà diversa dalle precedenti. Più pratica, più tecnica, essenziale ma efficace. Oggi riusciremo a fare qualcosa di davvero interessante perché parleremo con la seriale e lavoreremo su alcuni esempi. Alla fine di questa puntata, più delle altre volte, saprete fare qualcosa. Siete pronti?
Questa puntata del nostro
corso sarà diversa da quella
passata: sebbene alcune vostre osservazioni, alcuni vostri dubbi, siano stati molto seriamente presi in considerazione, adesso è arrivato il momento di addentrarsi ancora di più nel dettaglio. Vi ricordiamo che i commenti sono una vera e propria risorsa di questo blog: se avete dubbi, domande, perplessità e tutte le altre cose che di solito scrivo in questo elenco, cerchiamo di approfondirli insieme utilizzando degli articoli dedicati su richiesta vostra ma soprattutto usando i commenti. Lo dico in apertura di puntata perché i commenti sono davvero il luogo in cui tutti noi possiamo confrontarci per cui è importante che per ogni dubbio ci sia almeno un commento. Ritengo sia molto importante che tutti noi troviamo il tempo e la voglia di scrivere ogni dubbio che ci venga in mente.
Ogni dubbio è legittimo, ogni difficoltà superabile! Questo è il bello di essere ingegneri.
Detto questo, quello che voglio farvi vedere quest'oggi è come arrivare al classico progetto "Hello World!". Il più classico degli esempi, in realtà, perché questa volta il soggetto non sarà più il LED che lampeggia bensì una stringa di testo che vedremo lavorare su seriale.
Potrà sembrare poco ma non lo è affatto perché c'è una discreta complessità nell'elaborare questo genere di progetto per cui andiamo con ordine e con metodo.
Naturalmente anche per questa puntata, così come per le precedenti, non possiamo assolutamente fare a meno dei nostri più fedeli alleati ovvero
CodeWarrior e
Processor Expert. Questa volta, però, l'elenco non è completo: manca un componente. Anzi, in verità, per essere precisi, due: Serial_LDD, che troverete in CodeWarrior, e RingBufferUint8, che trovate in link alla fine di questo articolo.
Importare componenti
Dal momento che ci siamo trovati di fronte ad una nuova necessità, cioè quella di importare un componente all'interno del sistema, apriamo una breve parentesi su come fare questa semplice operazione.
Una volta che avrete avviato CodeWarrior dovrete semplicemente localizzare sulla barra in alto la voce "Processor Expert". Si tratta di un menù a tendina all'interno del quale troverete "Import Package". Se volete essere sicuri che il file in oggetto sia compatibile dovrete semplicemente controllarne l'estensione: in questo caso stiamo parlando di *.PEupd. Se avete più file da importare, questa operazione sarà eseguibile anche contemporaneamente su un numero elevato di contributi.
Una volta che avrete importato tutto ciò che vi occorre per completare il vostro progetto, tornate all'interno di quel menu tendina selezionate "Show Views".
Questo genere di opzioni è disponibile per ogni tool col quale lavorare all'interno di CodeWarrior e vi garantisce di avere sottomano tutti gli strumenti utili per completare il vostro progetto guardando solo quello che vi serve davvero.
Ecco cosa vedrete se tutto, nell'importazione, sarà andato a buon fine:
Naturalmente, una volta che avrete importato il componente, lo troverete nella libreria. Se non siete sicuri di quale sia la categoria giusta, effettuate una ricerca in ordine alfabetico: il vostro componente sarà sicuramente disponibile.
Naturalmente, neanche a dirlo, più componenti importerete, maggiormente popolata sarà la vostra "Component Library":
Possiamo iniziare
Ogni volta che sarete sicuri di aver installato tutti i componenti che vi servono per portare avanti il vostro progetto, potrete finalmente cominciare. La procedura è la stessa della volta passata per cui selezionate File -> New -> "Bareboard Project".
Date al progetto il nome che preferite ma la cosa fondamentale è che voi non sbagliate la scheda da selezionare.
Vi ricordiamo che la
FRDM-KL46Z si trova all'interno del menù "Kinetis L series" e non dimenticate di selezionare il Processor Expert quando vi chiederà l'ambiente di sviluppo che vorrete utilizzare.
La scheda, come voi stessi avrete certamente notato, non dispone di un connettore fisico per la UART e pertanto verrà utilizzata la P&E OpenSDA virtual USB CDC su UART0. Questo vuol dire che il dispositivo utilizzerà una UART fisicamente connessa al dispositivo di debug OpenSDA che implementa la USB CDC (Communication Device Class) sull'host, in qualità di porta virtuale.
Selezionate, allora, il componente Serial_LDD e riempite i campi come segue:
- Device -> UART0;
- Interrupt service/event -> Enabled;
- Settings -> Baud Rate = 38400;
- Receiver -> pin TPM2_CH0;
- Transmitter -> pin TPM2_CH1.
In questo modo potete iniziare la comunicazione.
Adesso quello che vi serve è creare un buffer per gestire al meglio i caratteri in ingresso; per questo motivo utilizzeremo quel componente di cui abbiamo parlato ovvero il buffer ad anello. La dimensione, il parametro più importante, deve essere impostata a 64.
Le opzioni da selezionare, per questo componente, sono molto poche per cui non potete sbagliarvi.
A questo punto abbiamo finito, possiamo iniziare a lavorare sul codice e pertanto selezionate "Generate Processor Expert Code".
Una volta terminata la procedura, avrete a disposizione nella cartella "Sources" del progetto due file, "Application", un header ed un file di codice. Come sempre, l'Header file serve per la definizione dell'interfaccia mentre Application.c viene utilizzato per l'implementazione.
All'interno dell'header file troverete una funzione che si chiama void APP_Run(void). Come la sua definizione suggerisce, si tratta di una funzione che non restituisce niente e non richiede parametri in ingresso. Di che cosa si tratta, allora?
APP-Run() è la routine principale che viene richiamata dal main().
Con UART_Desc viene dichiarato una struttura che verrà utilizzata per l'accesso alla UART.
È costituita da un device handle, un flag (che funziona da reset nell'interrupt della routine di invio), un buffer a singolo carattere per quelli che vengono ricevuti ed un puntatore ad una funzione di callback che immagazzina i caratteri in ingresso all'interno di quel buffer ad anello che abbiamo introdotto.
Dentro Application.c, invece, troverete questo codice:
#include "Application.h"
#include "RxBuf.h"
#include "AS1.h"
#include <stdio.h>
static UART_Desc deviceData;
static void SendChar(unsigned char ch, UART_Desc *desc) {
desc->isSent = FALSE; /* this will be set to 1 once the block has been sent */
while(AS1_SendBlock(desc->handle, (LDD_TData*)&ch, 1)!=ERR_OK) {} /* Send char */
while(!desc->isSent) {} /* wait until we get the green flag from the TX interrupt */
}
static void SendString(const unsigned char *str, UART_Desc *desc) {
while(*str!='\0') {
SendChar(*str++, desc);
}
}
static void Init(void) {
/* initialize struct fields */
deviceData.handle = AS1_Init(&deviceData);
deviceData.isSent = FALSE;
deviceData.rxChar = '\0';
deviceData.rxPutFct = RxBuf_Put;
/* set up to receive RX into input buffer */
RxBuf_Init(); /* initialize RX buffer */
/* Set up ReceiveBlock() with a single byte buffer. We will be called in
OnBlockReceived() event. */
while(AS1_ReceiveBlock(deviceData.handle, (LDD_TData *)&deviceData.rxChar,
sizeof(deviceData.rxChar))!=ERR_OK) {} /* initial kick off for receiving data */
}
void APP_Run(void) {
Init();
SendString((unsigned char*)"Hello World\r\n", &deviceData);
for(;;) {
if (RxBuf_NofElements()!=0) {
SendString((unsigned char*)"echo: ", &deviceData);
while (RxBuf_NofElements()!=0) {
unsigned char ch;
(void)RxBuf_Get(&ch);
SendChar(ch, &deviceData);
}
SendString((unsigned char*)"\r\n", &deviceData);
}
}
}
come potete vedere voi stessi, esistono e vengono utilizzate diverse funzioni, SendChar(), SendStrong() e così via dicendo, le quali hanno la funzione di gestire l'invio dei dati.
La Init() routine è quella al cui interno la struttura del dispositivo viene inizializzata insieme al buffer ad anello.
App_Run() prima di tutto inizializza e poi inserisce il messaggio "Hello World!" nella UART per poi entrare all'interno di un loop infinito in cui viene controllato se per caso arrivano caratteri nuovi altrimenti viene mandato sempre lo stesso messaggio.
La scheda parla
Dal momento che per questo esperimento stiamo utilizzando gli interrupt, è necessario entrare anche nel file Events.c per gestire alcune funzioni, OnBlockReceiver() e OnBlockSent().
Dal momento che stiamo utilizzando l'UART descriptor, è necessario includere l'Header file con la sua dichiarazione e pertanto inseriamo
#include "Application.h"
all'inizio del file. All'interno dell'interrupt della routine di callback del ricevitore vengono memorizzati i caratteri ricevuti, sempre all'interno del buffer ad anello. Qui avviene anche l'inizializzazione e la preparazione per l'eventuale nuovo caratteri in arrivo.
Nella routine del mittente, invece, viene effettuato il reset del flag per indicare che il blocco è stato inviato senza problemi.
void AS1_OnBlockReceived(LDD_TUserData *UserDataPtr)
{
UART_Desc *ptr = (UART_Desc*)UserDataPtr;
(void)ptr->rxPutFct(ptr->rxChar); /* but received character into buffer */
(void)AS1_ReceiveBlock(ptr->handle, (LDD_TData *)&ptr->rxChar, sizeof(ptr->rxChar));
}
Naturalmente non abbiamo ancora finito perché manca l'ultimo passo: bisogna richiamare la routine all'interno del file main ProcessorExpert.c.
Per fare questo basta includere anche qui l'Header Application.h ed effettuare la chiamata alla funzione AppRun() all'interno del codice stesso.
A questo punto non vi resta che compilare il codice ed eseguirlo.
Come vi aspettate, il messaggio di "Hello World!" sarà semplicemente ripetuto periodicamente dal momento che il progetto non prevede l'acquisizione o il transito effettivo di altre stringhe.
Il codice di esempio
/* ###################################################################
** Filename : ProcessorExpert.c
** Project : ProcessorExpert
** Processor : MKL46Z256VMC4
** Version : Driver 01.01
** Compiler : GNU C Compiler
** Date/Time : 2013-08-03, 19:01, # CodeGen: 0
** Abstract :
** Main module.
** This module contains user's application code.
** Settings :
** Contents :
** No public methods
**
** ###################################################################*/
/*!
** @file ProcessorExpert.c
** @version 01.01
** @brief
** Main module.
** This module contains user's application code.
*/
/*!
** @addtogroup ProcessorExpert_module ProcessorExpert module documentation
** @{
*/
/* MODULE ProcessorExpert */
/* Including needed modules to compile this module/procedure */
#include "Cpu.h"
#include "Events.h"
#include "UTIL1.h"
#include "LED1.h"
#include "LEDpin1.h"
#include "BitIoLdd1.h"
#include "LED2.h"
#include "LEDpin2.h"
#include "BitIoLdd2.h"
#include "FRTOS1.h"
#include "RTOSCNTRLDD1.h"
#include "CLS1.h"
#include "AS1.h"
#include "ASerialLdd1.h"
#include "MMA1.h"
#include "GI2C1.h"
#include "I2C0.h"
#include "I2CSPY1.h"
#include "SegLCD1.h"
#include "SW1.h"
#include "BitIoLdd3.h"
#include "SW3.h"
#include "BitIoLdd4.h"
#include "PTC.h"
#include "AD1.h"
#include "AdcLdd1.h"
#include "TMOUT1.h"
#include "MAG1.h"
#include "MINT1.h"
#include "ExtIntLdd1.h"
/* Including shared modules, which are used for whole project */
#include "PE_Types.h"
#include "PE_Error.h"
#include "PE_Const.h"
#include "IO_Map.h"
/* User includes (#include below this line is not maintained by Processor Expert) */
#include "Application.h"
/*lint -save -e970 Disable MISRA rule (6.3) checking. */
int main(void)
/*lint -restore Enable MISRA rule (6.3) checking. */
{
/* Write your local variable definition here */
/*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
PE_low_level_init();
/*** End of Processor Expert internal initialization. ***/
APP_Run();
/*** Don't write any code pass this line, or it will be deleted during code
generation. ***/
/*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS
component. DON'T MODIFY THIS CODE!!! ***/
#ifdef PEX_RTOS_START
PEX_RTOS_START();
/* Startup of the selected RTOS. Macro is defined by the RTOS component. */
#endif
/*** End of RTOS startup code. ***/
/*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
for(;;){}
/*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
/* END ProcessorExpert */
Trovate diversi codici relativi a moltissimi esempi già previsti da Freescale raggruppati in
questo pacchetto disponibile direttamente sul sito Freescale. Naturalmente non sono tutti specificatamente sviluppati per la piattaforma Freedom quindi dovrete selezionare soltanto quelli opportuni. Se includerete dei progetti per i sistemi Tower, per esempio, così come vi abbiamo detto l'altra volta, essi non funzioneranno.
Una volta che sarete riusciti ad utilizzare la seriale per indirizzare il vostro bitstream, vi consigliamo caldamente l'utilizzo di un programma come
Terminal v1.91 per monitorare il traffico su questa interfaccia.
Conclusioni
E con questo abbiamo concluso anche questa puntata del nostro corso. Nelle prossime continueremo ad entrare sempre più nello specifico trattando i sensori ma anche le interfacce di comunicazione per studiare il dettaglio di come questa scheda e l'intero ambiente di sviluppo e controllo dell'applicazione e come riesca ad interfacciarsi con l'utente in maniera sempre più affidabile è completa.
Faccio seguito ad alcune richieste che sono arrivate sia in privato sia qui sul blog: per qualsiasi dubbio, specifico o meno, utilizzate i commenti! Siamo a disposizione per chiarire ogni aspetto collegato a questi articoli e se per caso c'è qualcosa che necessita di ulteriore approfondimento, dedicheremo articoli interi per spiegare ancora meglio tutti gli aspetti fondamentali di ciò che non è stato chiaro. Approfittatene!
E, come sempre, alla prossima.
Ringraziamenti & Credits:
Il componente RingBufferUInt8 proviene da
questo riferimento ed è rilasciato sotto licenza LGPL, Copiright Erich Styger, 2012. Dello stesso autore sono stati anche riproposti alcuni codici.
Per ulteriori informazioni potete scrivere a questa email: [email protected]
Ti potrebbe interessare anche:
Ottima puntata nella quale si è passati (quasi) direttamente alla pratica con il (quasi) classico HELLO WORLD.
Mi piace parecchio il fatto che il dispositivo in questo esperimento utilizza una UART fisicamente connessa al dispositivo di debug OpenSDA che implementa la USB CDC sull’host in qualità di porta virtuale, così facendo è possibile anche collegare qualsiasi dispositivo USB, giusto?
—– Un altra domanda:
Questo corso è “compatibile” con Arm cortex-m3 stm32f103c8t6?
—– Un altra domanda ancora:
Pensi che sia possibile studiare l’implementazione del kernel linux su tecnologia ARM? (Intendo se pensi se potrebbe essere realizzato qualche articolo a riguardo)
Riguardo a quest’ultima domanda devo dire che ricordo ancora questo articolo nel quale viene spiegato appunto come creare da zero un un sistema GNU/Linux perfettamente funzionante applicabile ad esempio in scenari di tipo embedded.
Perciò la mia vera domanda è: Come implementare Linux sulla Tecnologia ARM? Quali requisiti devo possedere un ARM per supportare Linux? Quali limiti? Pregi? Difetti? etc… 😀
Grazie per la pazienza,
Ivan
Si, giusto.
In effetti si trata di una funzionalità davvero comoda e molto professionale 🙂
che intendi per “compatibile”? Noi stiamo lavorando in un ambiente di sviluppo e le peculiarità del cortex-m0+ lo rendono un prodotto “autonomo” quindi… bisogna capire esattamente che cosa vuoi sapere se è equivalente o meno…
Infatti… C’è proprio l’autore di quell’articolo a cui puoi chiedere, lo saprà certamente meglio di me 😀
arighi, ci sei? 😉
Per compatibile intendo dire se seguendo questo mini corso posso applicarlo anche ad una board che monta un Arm cortex-m3.
Avrei pensato anche io ad arighi in effetti…
Arrivi sempre prima di me…!!!!! 😉
Per fare porting di Linux su un’architettura “nuova” (immagino si riferisca a questo la domanda) e` necessario scrivere un layer di compatibilita` che andra` poi collocato sotto arch/arm/. Se hai i sorgenti di un kernel recente alla mano puoi provare a listare la directory arch/arm per avere un’idea delle varie architetture ARM correntemente suportate:
$ ls arch/arm/
boot mach-ebsa110 mach-mv78xx0 mach-s5pc100 Makefile
common mach-ep93xx mach-mvebu mach-s5pv210 mm
configs mach-exynos mach-mxs mach-sa1100 net
crypto mach-footbridge mach-netx mach-shark nwfpe
include mach-gemini mach-nomadik mach-shmobile oprofile
Kconfig mach-highbank mach-nspire mach-socfpga plat-iop
Kconfig.debug mach-imx mach-omap1 mach-spear plat-omap
Kconfig-nommu mach-integrator mach-omap2 mach-sti plat-orion
kernel mach-iop13xx mach-orion5x mach-sunxi plat-pxa
kvm mach-iop32x mach-picoxcell mach-tegra plat-samsung
lib mach-iop33x mach-prima2 mach-u300 plat-versatile
mach-at91 mach-ixp4xx mach-pxa mach-ux500 tools
mach-bcm mach-keystone mach-realview mach-versatile vfp
mach-bcm2835 mach-kirkwood mach-rockchip mach-vexpress xen
mach-clps711x mach-ks8695 mach-rpc mach-virt
mach-cns3xxx mach-lpc32xx mach-s3c24xx mach-vt8500
mach-davinci mach-mmp mach-s3c64xx mach-w90x900
mach-dove mach-msm mach-s5p64x0 mach-zynq
In alcuni casi e` sufficiente partire da un’architettura gia` presente e aggiustare solo un po’ di valori (cambiare l’indirizzo a cui sono mappati certi registri, o aggiungere/togliere alcuni device registrati al boot, etc.).
In altri casi invece e` necessario scriversi da zero il layer di compatibilita` di basso livello, con datasheet alla mano, utilizzando le primitive e i sottosistemi che il kernel mette a disposizione. In questo modo una volta creato il layer di astrazione sottostante (in genere dopo aver sbattuto la testa varie volte su problematiche hardware :)) ci troviamo di colpo un kernel Linux funzionante esattamente come su qualsiasi altra architettura. Chi ha provato questa esperienza almeno una volta avra` sperimentato la sensazione di “e luce fu!”, quando finalmente parte il kernel su una board nuova su cui e` stato fatto il porting.
Una volta fatto il porting delle funzionalita` base necessarie a fare boot l’ultimo passo consiste nell’abilitare i driver presenti sulla board. Per questa fase in genere e` sufficiente mettere mano solo al “make menuconfig” del kernel (Linux supporta gia` una vasta gamma di device, bus i2c, SPI, uart, device RTC, etc. etc.) quindi in genere per questa fase non c’e` quasi mai da scrivere codice. Al limite anche qua si tratta di aggiustare qualche indirizzo su cui vengono mappati certi registri del device, la linea IRQ o simili.
In tutto cio` il caso piu` semplice ovviamente e` quando Linux supporta gia` la board che decidiamo di usare. In questa condizione per fare il porting e` sufficiente scaricarsi una toolchain di cross-compilazione, scaricarsi i sorgenti del kernel, lanciare un “make menuconfig” e selezionare i driver dei dispositivi che vogliamo che il kernel “veda” (spesso per board note si trovano gia` dei .config gia` pronti all’uso e dell’ottima documentazione su come ricompilare il kernel, vedi RaspberryPI, BeagleBoard e simili).
Spero di aver risposto (almeno in parte) alla domanda.
Ottimo, veramente esauriente, Grazie mille.
Mi piacerebbe studiare l’architettura ARM e creare appunto una board su cui può girare Linux.
Visto che è più semplice partire da un architettura già presente, quale mi consiglieresti di utilizzare?
Ad esempio, in questo link è possibile vedere un esperimento, nulla di più.
Potresti gentilmente darmi qualche informazione in più su “come si fa in pratica?”?
Ad esempio, scelgo un microcontrollore ARM, e per utilizzarlo, ho bisogno di creare una board di prototipazione minimale, non perchè in commercio non ce ne siano, ma semplicemente per imparare qualcosa in più. Oltre a tener conto delle prestazioni, in base a quali fattori devo scegliere il micro?
Dopo… che tipo di programmatore dovrei utilizzare? Basta un convertitore USB-SERIALE TTL, giusto?
Ad esempio, ho trovato Questa board a basso costo basata su Arm cortex-m3 stm32f103c8t6 stm32 core.
Grazie,
Ivan
Ciao Ivan, scusami per la risposta in ritardo.
Allora, se ti interessa capire come costruire una board che supporti Linux mi viene difficile darti consigli, perche` non ho esperienza in merito. Io ho sempre lavorato su hardware gia` fatto, sono un softwarista / kernel-ista puro. Al limite mi limito a dire “questo hardware non va, fa schifo, rifatelo”. 🙂
Per il discorso porting Linux, qualora ti interessase imparare come fare, io a scopo didattico ti consiglieri di partire da una board esistente che ha gia` un supporto Linux completo, es. una RaspberryPi o una BeagleBoard.
Partendo da un’architettura del genere proverei ad esempio a riscrivere un driver di un dispositivo semplice, un RTC, un driver per controllare dei semplici pin di GPIO, anche accendere e spengere un LED via procfs o sysfs, roba del genere insomma.
Se qualcosa non funziona o non sai come fare puoi consultare il driver originale se c’e`, oppure puoi consultare un driver che fa qualcosa di simile, se ti servono delle interfacce (es. come registrare il dispositivo e farlo vedere via procfs o sysfs) sei costretto a vedere e capire come fanno gli altri driver. Tutte informazioni che se le scopri direttamente “esplorando” in questo modo ti rimangono in testa, meglio di qualsiasi corso tu possa frequentare o libro che tu possa leggere.
A mio avviso questo approccio e` il migliore per imparare a fare porting (o almeno, per me e` stato utile). Partire da piccoli problemi o semplici driver, provare a riscriverli per capire e imparare come funzionano. Se sei fortunato magari trovi anche dei bug nella “mainline” e allora puoi provare a correggerli o segnalarali sulla Linux kernel mailing list, ricevere feedback (spesso anche insulti :)) dai maggiori esperti del settore, etc. ad ogni modo tutte informazioni preziose per imparare questo tipo di programmazione.
Ora mi rendo conto che non ti ho detto niente di pratico riguardo a cio` che hai chiesto. 🙂 Pero` magari per costruire una board nuova puo` funzionare un approccio simile. Parti da una board completa su cui gira Linux (le solite BeagleBoard, Raspberry, etc.), provi ad aggiungere o togliere pochi device e magari in questo modo puoi anche verificare veramente se e come ci gira Linux. Basta ricompilare il kernel disabilitando o abilitando i pochi device che hai cambiato. Verificare sul campo il risultato del lavoro che fai secondo me da` un valore aggiunto all’apprendimento, cosa che non sempre e` possibile fare se si parte con qualcosa disegnata completamente da zero.