Read Usart una modifica al core di Arduino

Stampa
( 0 Votes ) 
Valutazione attuale:  / 0
ScarsoOttimo 
Categoria principale: Elettronica Categoria: Microcontrollori
Data pubblicazione
Scritto da MGuruDC Visite: 2214

"readUsart", una modifica al core di Arduino

Un metodo diverso per leggere l'USART su Arduino da MGuruDC

Premessa

Alcuni spesso mi chiedono... perchè Arduino?
Beh, penso che negli altri due articoli che ho scritto sull'Arduino ho già  spiegato parecchi vantaggi, ma questa volta voglio mettere in evidenza un aspetto veramente importante: Arduino è Open Source... e non  è poco!

Se lavoriamo in un ambiente di programmazione o un SDK (software developement kit) ci troviamo ad usare delle funzioni, API, metodi che ne fanno parte. Naturalmente chi crea questi  ambienti di programmazione è molto competente e cerca di soddisfare tutte le esigenze che possono venire fuori, o come minimo di mettere a portata di mano tutte le funzioni hardware.

Normalmente queste soluzioni bastano e avanzano, ma casomai avessimo bisogno di API diverse o di funzionalità estese? In un ambiente chiuso non c'è molto da fare... le api sono definite in dei file oggetto assolutamente intoccabili (e che magari sono costati pure parecchi quattrini) e i proprietari del codice molto difficilmente ci vengono incontro. Spesso in questi casi si desiste, o addirittura si cambia ambiente con la conseguente enorme perdita di tempo che comporta l'apprendimento di un nuovo linguaggio o ambiente di programmazione.
Quando invece abbiamo a disposizione un ambiente aperto non resta che esplorare il nostro SDK, scoprire come funziona e applicare tutte le modifiche necessarie, dato che il nostro operato... praticamente non ha limiti!

La gestione classica della USART

L'ambiente di programmazione di Arduino ci mette a disposizione una serie di funzioni che si occupano della comunicazione con la porta seriale a cui è collegato l'adattatore USB. Queste funzioni sono molto comode e permettono realmente di scambiare dati con un computer(alcuni esempi dei miei primi due articoli usano queste funzioni).
Riepiloghiamo quali sono:
(fanno tutte parte della classe "Serial" quindi vanno invocate con "Serial." davanti)

    begin()
Inizializza la porta e imposta il baudRate della comunicazione

    end()
Chiude la comunicazione e libera i pin impiegati dalla porta

    available()
Permette di sapere se e quanti byte sono stati ricevuti

    read()
Restituisce l'ultimo byte ricevuto

    flush()
Pulisce il buffer, da usare in caso di errore

    print(), println(), write()
Scrivono sulla porta, rispettivamente il primo stampa caratteri, il secondo stampa caratteri seguiti da un '\n', il terzo scrive valori numerici(byte insomma...)

Sulla versione 0021 dell'SDK è stata implementata ma non resa pubblica(c'è ma non si vede) anche la funzione peek che restituisce il primo carattere del buffer usato dalla seriale... pressochè inutile.

Mentre nell'invio di dati al computer non ci sono problemi, la lettura del dati che poi sono già  sull'Arduino si rivela un po' problematica.
Finchè non chiediamo di leggere più di un byte alla volta la funzione read va alla grande, ma se ad esempio vogliamo delle stringhe dobbiamo ricorrere a stratagemmi tipo questo(usato negli articoli precedenti):

  for(i = 0; i < MAX_BUFF; i ++) {  //pulisce il buffer
    buf[i] = 0;
  }
  for(i = 0; Serial.available() > 0; i ++) {  //in caso di errore segnala e esce dalla funzione
    if (i > MAX_BUFF) {
      Serial.println("Errore");
      Serial.flush();
      return;
    }
    buf[i] = Serial.read();  //memorizza nel buffer i dati in arrivo
    delay(BUFF_ACK_DELAY);
  }

Ovvero, andiamo a creare un nuovo buffer(con conseguente spreco di memoria a rischio di errori) e lo riempiamo "in streaming" con i dati in arrivo, col conseguente rischio di perdere pezzi e mandare a rotoli la tempistica se parte un interrupt interno.

La modifica

Avendo necessità di creare una specie di consolle questo meccanismo non mi andava affatto bene... affidare la lettura di dati e comandi piuttosto lunghi quasi al caso non è l'ideale.
Per cercare un buona soluzione ho cominciato a esplorare il cuore di Arduino risalendo subito ai sorgenti delle funzioni prima elencate.
La classe "Serial" è infatti automaticamente inclusa in tutti gli sketch in fase di compilazione  grazie alla direttiva
#include "HardwareSerial.h"
presente nel file "WProgram.h". L'header contiene la dichiarazione della classe che rimanda al file HardwareSerial.cpp, il quale contiene realmente le funzioni della seriale.
Da queste capiamo che la gestione della seriale avviene attraverso una struttura così dichiarata

 
struct ring_buffer {
  unsigned char buffer[RX_BUFFER_SIZE];
  int head;
  int tail;
};

ovvero un buffer e due interi... un tipico buffer circolare.
Le funzioni presenti nella classe memorizzano i singoli byte nel buffer e li indirizzano tramite i due interi in modo da scongiurare il "buffer overflow", errore molto grave.
E' proprio questo il meccanismo  che ci costringe a leggere un solo carattere alla volta.
Bene, a me interessava avere a disposizione tutti i dati presenti nel buffer.
Il modo migliore per avere questi dati senza stravolgere la classe, è creare una nuova funzione che ci restituisca semplicemente un puntatore al buffer(che il C sia lodato!), quindi ho inserito una nuova funzione nella classe:


unsigned char* HardwareSerial::buff() {
	return _rx_buffer->buffer;
	}

Niente di difficile, ho preso il buffer, l'ho puntato e restituito conservandone il tipo.
Naturalmente, affinchè la funzione possa essere invocata al di fuori della classe bisogna pubblicarla insieme alle altre nel file header, cioè aggiungere la riga

virtual unsigned char* buff();

dove si trovano tutte le altre dichiarazioni public.
Bene, da adesso in poi, negli sketch per Arduino posso conoscere la posizione in memoria del buffer, e volendo posso anche leggerlo tramite un secondo puntatore.
Per fare le cose come si deve ho implementato una funzione:


char* readUsart() {
  noInterrupts();						//ferma tutti le interruzioni per 
//evitare che il buffer venga modificato mentre viene letto
  char *np = (char*)malloc(strlen((char*)Serial.buff() + 1));	//alloca la memoria per creare una copia del buffer
  strcpy(np, (char*)Serial.buff()); 				//lo copia
  unsigned char *p = Serial.buff();				//punta al buffer con un secondo puntatore
  for(int i = 0; i < 128; i ++, p ++) {				//e lo pulisce
    *p = 0;
  }
  Serial.flush();						//azzera gli indirizzi del buffer circolare
  interrupts();							//riabilita le interruzioni
  return np;							//restituisce il puntatore alla copia del buffer
}

Usando questa nuova funzione per leggere i dati dalla seriale posso leggere direttamente delle stringhe, sono sicuro che il buffer venga pulito dopo la lettura e che i successivi dati in ingresso vengano scritti sempre nella stessa posizione, inoltre la copia del buffer è allocata in maniera dinamica quindi riusciamo a risparmiare un po' di RAM.
Con questa funzione risulta molto più facile lavorare con le stringhe e tenere sotto controllo il flusso dei dati, senza contare che se conosciamo a priori il tipo di stringhe che avremo in ingresso possiamo adattare la dimensione del buffer circolare.

Riepiloghiamo le modifiche da fare nel caso un cui volessimo usare la funzione "readUsart":

  • Individuare il percorso base della nostra istallazione di Arduino:

Se abbiamo scaricato il file zip dal sito ufficiale, il percorso base sarà  la cartella in cui abbiamo scompattato il file zip.
Se abbiamo scaricato il pacchetto dalle repository della nostra distribuzione, il percorso sarà

    /usr/share/arduino

Nella cartella
    ./hardware/arduino/cores/arduino
apriamo il file "HardwareSerial.cpp" e aggiungiamo nella classe la seguente funzione:


unsigned char* HardwareSerial::buff() {
	return _rx_buffer->buffer;
	}

Naturalmente salvare il file.

  • Aprire il file "HardwareSerial.h"

e inserire nelle dichiarazioni public il prototipo della funzione che abbiamo appena messo nel file .cpp
    virtual unsigned char* buff();
 

  • A questo punto abbiamo a disposizione la posizione in memoria del buffer,

Se vogliamo usare la funzione readUsart possiamo definirla nel nostro sketch, Se invece vogliamo averla sempre a disposizione possiamo inserire la routine
 


char* readUsart() {
  noInterrupts();
  char *np = (char*)malloc(strlen((char*)Serial.buff() + 1));
  strcpy(np, (char*)Serial.buff());
  unsigned char *p = Serial.buff();
  for(int i = 0; i < 128; i ++, p ++) {
    *p = 0;
  }
  Serial.flush();
  interrupts();
  return np;
}

alla fine del file "main.cpp". Penso sia la cosa migliore, dato che il compilatore sorvolerà tranquillamente la funzione se non la invocheremo nel nostro sketch.

Penso sia tutto, spero sia utile a qualcuno.
CiaoGuru

Joomla 1.7 Templates designed by College Jacke