Arduino UNO R4 - Driver Seriale

Chi ha lavorato con Arduino UNO R3, Arduino MEGA, Arduino Leonardo, ecc. (tutte le classiche AVR) ber ricorda che la seriale, per ottimizzare i tempi era gestita ad interrupt così da NON bloccare il programma, ad esempio durante una tramissione di parecchi caratteri magari con un baud rate basso.

Per le prove che seguono, dato il il blocco di caratteri trasmessi è, volutamente, grande (180 caratteri) e che di base, il buffer di trasmissione per la seriale, sulle MCU classiche AVR è impostato al massimo a 64, occorre temporaneamente modificare UNA riga nel "core AVR" per rendere equiparabile il test che poi seguirà con Arduino UNO R4.

Quindi, trovato il core della scheda (io ho usato una Leonardo per avere sia la seriale USB nativa che la seconda seriale UART sui pin 0 e 1, come si ha su Arduino UNO R4 MINIMA) occore modificare UNA riga nel file "HardwareSerial.h", riga 53 (non ho fatto prove con MCU che hanno meno di 1024 bytes di SRAM) in modo che si abbia:

#define SERIAL_TX_BUFFER_SIZE 256

che è sufficiente per la nostra prova con 180 caratteri.

Dopo le prove su AVR, salvo non vi serva veramente così grande, consiglio di riportare il valore a quello originale, ovvero 64 per non sprecare preziosa memoria.

Passiamo al semplice programma di test che utilizzeremo sia per la Leonardo che per la R4 MINIMA (ed una prova anche sulla WiFi) ...

void setup() {
   char msg[] = "The quick brown fox jump over the lazy dogs\r\n"
                "The quick brown fox jump over the lazy dogs\r\n"
                "The quick brown fox jump over the lazy dogs\r\n"
                "The quick brown fox jump over the lazy dogs\r\n";
   unsigned long t_start, t_start_1, t_start_g;
   unsigned long t_end, t_end_1, t_end_g;
   //
   delay ( 500 );
   pinMode ( LED_BUILTIN, OUTPUT );
   //
   Serial.begin ( 4800 );
   while ( !Serial ) delay ( 50 );
   Serial1.begin ( 4800 );
   //
   t_start_1 = micros();
   t_start_g = t_start_1;
   Serial1.println ( msg );
   t_end_1 = micros();
   t_start = micros();
   Serial.println ( msg );
   t_end = micros();
   t_end_g = t_end;
   //
   Serial.print ( "Number of printed chars: " );
   Serial.println ( strlen ( msg ) );
   Serial.print ( "Time to print to Serial microseconds: " );
   Serial.println ( t_end - t_start );
   Serial.print ( "Time to print to Serial1 microseconds: " );
   Serial.println ( t_end_1 - t_start_1 );
   Serial.print ( "Time to print total microseconds: " );
   Serial.println ( t_end_g - t_start_g );
   //
   Serial.println ();
}

void loop() {
   static byte ledStatus = LOW;
   //
   delay ( 750 );
   ledStatus = ~ledStatus;
   digitalWrite ( LED_BUILTIN, ledStatus );
}

Programmino molto semplice che crea una stringa classica del C di 180 caratteri e la invia in trasmissione sia su Serial1 che su Serial, misurando i microsecondi che passano prima che il controllo torni all'applicazione dopo aver chiamato la Serial.println().

Ho scelto di lavorare a 4800 bps proprio per evidenziare bene il problema ...

Guglielmo

Se si esegue il programma sulla "Leonardo", che monta una MCU AVR ATmega32U4 a 16 MHz, si ottiene il seguente risultato:

The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs

Number of printed chars: 180
Time to print to Serial microseconds: 1196
Time to print to Serial1 microseconds: 1128
Time to print total microseconds: 2328

Chiariamo che 180 caratteri a 4800 bps 8 bit, 1 start, 1 stop (quindi 10 bit) impiegano, teoricamente, circa 375000 µsec ... quando si fa Serial1.println() (UART della Leonardo), la stringa viene copiata nel buffer e poi trasmessa ...

... sui classici AVR, attraverso una ISR per cui, la Serial1.println() ritorna il controllo all'applicazione dopo circa 1128 µsec, dopo di che ci pensa la ISR a trasmettere il tutto carattere a carattere. Un bel vantaggio rispetto ai teorici 375000 µsec :slight_smile:

Guglielmo

Eseguiamo ora lo stesso programma su Arduino UNO R4 MINIMA, che monta una MCU RA4M1, ARM Cortex M4 a 48 MHz, ed otteniamo:

The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs

Number of printed chars: 180
Time to print to Serial microseconds: 468
Time to print to Serial1 microseconds: 379201
Time to print total microseconds: 379671

:open_mouth: :open_mouth: :open_mouth: mentre sulla USB nativa le cose funzionano perfettamente, sulla UART abbiamo 379201 µsec ovvero, considerando le varie operazioni che fa la Serial1.println(), un po' più del tempo teorico che avevamo calcolato, tempo in cui il programma applicativo resta completamente bloccato sulla Serial1.println().

La cosa è, purtroppo, facilmente spiegabile esaminando il codice:

-------------------------------------------------------------------------- */
size_t UART::write(uint8_t c) {
/* -------------------------------------------------------------------------- */
if(init_ok) {
    tx_done = false;
    R_SCI_UART_Write(&uart_ctrl, &c, 1);
    while (!tx_done) {}
    return 1;
  }
  else {
    return 0;
  }
}

il codice è completamente bloccante e rimane sulla while() sino a quando la trasmissione NON è completata :cry:

Peggio, troviamo anche:

class UART : public arduino::HardwareSerial {
  ...
  private:
  ...
    arduino::SafeRingBufferN<SERIAL_BUFFER_SIZE> txBuffer;
  ...
};

... ovvero viene riservata memoria per un txBuffer che NON viene usato sprecando solo SRAM!!! :angry:

Guglielmo

Quanto suddetto è ancora più valido su Arduino UNO R4 WiFi che NON utilizza una USB nativa, ma attraverso una UART (con i problemi su evidenziati) colloquia con un ESP32, il quale, a sua volta, colloquia con la USB; quindi, mentre sulla MINIMA il problema si ha solo con la Serial1, sulla WiFi il problema è identico anche con la USB che NON è la USB del RA4M1, come spiegato, ma la ritrasmissione da parte del ESP32 di quanto ricevuto via UART dal RA4M1 ... :confused:

Quindi ... mentre il driver seriale degli AVR era stato sviluppato come si deve, quello del "core" della UNO R4 purtroppo NO ... magari sarà stato utilizzato uno degli esempi di BASE che fornisce Renesas, senza approfondire più di tanto :sob:

Fortunatamente alcuni sviluppatori indipendenti, hanno affrontato il problema ed hanno proposto alcune soluzioni, di cui una, a mio parere, molto valida ... che vedremo nei post successivi :wink:

Guglielmo

1 Like

Il problema è stato affrontato già parecchio tempo fa (giugno - luglio) da un utente della sezione di lingua Inglese, Delta_G, che, in vari suoi thread ha esaminato la cosa e proposto delle possibili alternative, una modificando il codice per utilizzare l'interrupt ed un'altra modificando il codice per usare, addirittura, il DMA ... entrambe le soluzioni sono molto efficienti, ma, purtroppo, io ho riscontrato alcuni problemi di perdita di caratteri, utilizzando le sue versioni dei driver sulla Serial della UNO R4 WiFi ... probabilmente qualche cosa che non va con il colloquio con ESP32 ... :roll_eyes:

Successivamente, un altro utente, chuygen, ha segnalato di aver esaminato e modificato il driver, ma, purtroppo, i suoi multipli interventi su vari thread, sono stati considerati quasi più come SPAM che come un aiuto ... peccato, perché, invece, ad oggi è quello che propone la soluzione alternativa migliore basata sul uso del buffer e del interrupt.

Su Hackster.io ha scritto un bel articolo in cui ha spiegato tutti i problemi che ha incontrato, l'analisi che ha fatto ed il codice che propone e che può essere scaricato.

Io l'ho provato e, ad oggi, NON ho riscontrato problemi, anzi, ho ottenuto degli ottimi risultati. Anche qui, dato il blocco da 180 charatteri che uso per la prova, ho fatto una modifica al suo file Serial.h alla linea 60, portando il buffer allo stesso valore di default che ha per il "core" della R4, ovvero di 512 bytes (a cui ne viene aggiunto uno per i motivi spiegati nell'articolo):

#define SERIAL_BUFFER_SIZE 512+1

Nello .zip che allego, ci sono tutti i files da sostituire nel "core" di Arduino UNO R4 (Reneasa), già modificati, per provare: Zeven_Serial.zip (13.9 KB)

:warning: ATTENZIONE: Vi consiglio di rinominare tutti i files originali che andate a sostituire per poter, facilmente, ripristinare la situazione originale nel caso incontraste problemi.

E vediamo i risultati ottenuti con questi files su Arduino UNO R4 MINIMA:

The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs

Number of printed chars: 180
Time to print to Serial microseconds: 533
Time to print to Serial1 microseconds: 317
Time to print total microseconds: 852

... il tempo di attesa passa dai 379201µsec. del "core" originale a soli ... 317 µsec!!!

Proviamo a far girare lo stesso programma anche su Arduino UNO R4 WiFi, in cui, come detto, le stampe su Serial NON sono sulla USB nativa, ma escono dalla USRT e finiscono nel ESP32 che, a sua volta, le manda sulla USB:

The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs
The quick brown fox jump over the lazy dogs

Number of printed chars: 180
Time to print to Serial microseconds: 317
Time to print to Serial1 microseconds: 317
Time to print total microseconds: 636

... abbiamo esattamente lo stesso miglioramento :slight_smile:

I files di chuygen contengono anche delle altre modifiche che ottimizzano il funzionamento del tutto, una sulla priorità del interrupt della USB nativa ed una riguardante la possibilità di lavorare in modalità 9N1 (9 bit dati, no parity, 1 stop) ... modalità che però NON ho provato e non so se funziona :roll_eyes:

Sulle modalità della seriale di Arduino UNO R4 aprirò un'altra discussione perché ... anche li ... ce ne sono delle belle :smile:

Fate le vostre prove e fatemi sapere ... io sono in contatto direttamente con chuygen e quindi posso facilmente segnalare anomalie che verranno sicuramente sistemate in tempi brevi :wink:

Guglielmo

P.S.: chuygen mi ha mandato anche il link ad un filmato in cui controlla delle schede relè che funzionano a 9N1, facendo vedere che non ci dovrebbero essere problemi ... il filmato si trova QUI. Il sito è lento ... fate caricare il filmato prima di fare PLAY così da vederlo senza interruzioni.

Mah... sto usando bcompare per vedere differenze tra core e quei file.

File serial.cpp ... nella "UART::WrapperCallback" nello switch() nel case
case UART_EVENT_TX_COMPLETE:
in pratica hanno dimenticato nel core ufficiale un break; !?!?!
Ma è fatto un pò a cacchio sto core ?!?

Assurdo. Ovvio che ha modificato molti punti in quei 4 file. Ma bisognerebbe ringraziarlo !!

Mmm ... mi sa che "bcompare" funziona male perché le modifiche sono ben di più ... c'è tutta la gestione del buffer circolare e degli interrupt ...
... e le modifiche sono sia nel .h che nel .cpp :roll_eyes:

Guglielmo

Ecco, ti allego uno .zip che contiene due .html che puoi aprire con un browser per vedere le differenze tra i due file ... Compare.zip (12.3 KB)

Buona lettura :grin:

Guglielmo

Lo so che son molto "border line" come limite all'OT, ma questa cosa vale solo per il core Renesas?
Oppure affligge anche altri core di schede più attuali?

Non ne ho idea, basta provare con l'esempio messo all'inizio ... hai tutte le tempistiche :wink:

Guglielmo

1 Like

Il problema è acquistare le schede per fare le prove!

Se non hai le schede ... non hai il problema ... :smiling_imp:

Guglielmo

1 Like

Potrei provare con le Pico Raspberry. Forse.

Dubito che sia stata fatta una bojata come quella vista sopra ... :roll_eyes:

Guglielmo

Mi sono espresso male. Ho visto le molte modifiche. Quella che mi è saltata all'occhio è quella che ho indicato, perché in un core ufficiale dimenticare un break in uno switch mi sembra grave. Mi pare indichi poche prove fatte dal team ufficiale.

Prove??? ... probabilmente nessuna ... un copia/incolla (forse mal riuscito) da qualche esempio base Renesas e via ... :confused:

Guglielmo

Con riferimento a QUESTA discussione metto qui il file serial.cpp versione chuygen con aggiunto il supporto per i 7 bit ... serial.cpp (14.6 KB).

Guglielmo

EDIT: aggiornato con la versione ufficiale inviatami da chuygen

Ho contattato chuygen per avere maggiori dettagli sul 9N1 che ha implementato ...
... in realtà NON ha implementato i 9 bit (che richiedono modifica sostanziale al driver ed ai buffers), ma, traducendo le sue parole:

L'ho implementato solo come master in modalità indirizzo a 9 bit, lascia che ti spieghi;

La modalità indirizzo a 9 bit utilizza ancora buffer ad anello Tx e Rx a 8 bit, quindi la stessa dimensione di memoria di 8 bit. I veri dati a 9 bit richiedono buffer a 16 bit, ma se stai utilizzando solo 9 bit per la modalità indirizzo puoi impostare manualmente il 9° bit di indirizzo quando invii un indirizzo, ma continua a utilizzare buffer a 8 bit per i dati. Ciò significa ovviamente che Arduino può essere utilizzato solo come master in modalità indirizzo a 9 bit. Non può essere lo slave perché non riceve veri 9 bit. Sull'IO Expander ho implementato la modalità indirizzo hardware a 9 bit, dove finché l'IO Expander non riceve l'indirizzo corretto a 9 bit non ci sarà alcuna generazione di interrupt del carattere ricevuto per i dati inviati ad altri indirizzi sullo stesso cavo (multi-drop come RS-485). Questo è un vero hardware multi-drop a 9 bit. Il 9° bit viene utilizzato solo per identificare un byte di indirizzo.

... insomma, "un qualche cosa" che gli è servito per pilotare degli expander che montano una MCU Microchip PIC18F27K42 (vd. pag. 481, sezione 31.3 punto 31.3.2).

Quindi, al momento, nessun supporto reale per i 9 bit.

Guglielmo

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.