Corso su ARM: “Hello World” su UART

serial

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: ggalletti@arroweurope.com

7 Comments

  1. Andrea Righi 6 dicembre 2013
  2. Ivan Scordato 22 novembre 2013
  3. Piero Boccadoro 23 novembre 2013
  4. Ivan Scordato 23 novembre 2013
  5. Giorgio B. 23 novembre 2013
  6. Andrea Righi 23 novembre 2013
  7. Ivan Scordato 29 novembre 2013

Leave a Reply