Arduino UNO R4 - manipolazione diretta dei pin di I/O

Molti di noi, in passato, con Arduino UNO R3 (ATmega328P, AVR) abbiamo utilizzato, per esigenze di velocità, l'accesso diretto ai pin di I/O con le istruzioni utilizzanti PIN e PORT ... anche con Arduino UNO R4 (Renesas RA4M1, ARM Cortex M4) la cosa è possibile, ma, data l'architettura differente, occorre seguire tutt'altra strada.

Per correttezza rammento che, l'utilizzo dell'accesso diretto ai pin di I/O, senza utilizzare il framework Arduino (quindi, senza utilizzare digitalRead() e digitalWrite()), rende il codice intrasportabile su altre piattaforme senza un discreto lavoro di adattamento e che quindi, salvo particolari esigenze, e del tutto da sconsigliare.

Comunque, studiando gli ottimi lavori di Susan Parker (TriodeGirl (Susan Parker) · GitHub) e i vari testi legati alla cosa ho messo a punto un piccolo modulo .h, che si può includere in un .ino mettendolo direttamente nella stessa cartella del progetto, e che rende banale l'accesso diretto in I/O sul singolo pin di Arduino UNO R4.

Per chi volesse approfondire c'è da studiarsi:

... che contengono tutte le informazioni necessarie.

In pratica, sul RA4M1, ogni singolo pin di I/O (e non solo) è mappato in memoria, per cui è possibile agire su di esso semplicemente leggendo o scrivendo una certa locazione.

Per semplificare la cosa ho fatto, come detto, un semplice .h che allego: portIO.h (4.3 KB) ... esso una volta incluso nel .ino permette banalmente di leggere/scrivere sul singolo pin di I/O di Arduino UNO R4.

Un paio di esempi per chiarire quanto la cosa sia semplice ...

  • Il classico blink del LED che, anche su Arduino UNO R4, si trova sul pin 13:
#include "portIO.h"

bool ledStatus = false;

void setup() {
   delay ( 500 );
   Serial.begin ( 115200 );
   while ( !Serial ) delay ( 100 );
   Serial.println ( "Direct I/O LED blinking ... " );
}

void loop() {
   if ( ledStatus ) {
      IO_D13 = IO_OUT_LOW;
   } else {
      IO_D13 = IO_OUT_HIGH;
   }
   ledStatus = !ledStatus;
   delay ( 1000 );
}
  • la lettura di un pin in input (pull-up) (pin 7) e la sua visualizzazione sul LED (pin 13):
#include "portIO.h"

uint8_t pinStatus;

void setup() {
   delay ( 500 );
   Serial.begin ( 115200 );
   while ( !Serial ) delay ( 100 );
   Serial.println ( "Direct I/O test ... " );

   IO_D7 = IO_INP | IO_INP_PULLUP; /* Enable INPUT PULL-UP) */
}

void loop() {
   pinStatus = IO_D7 & IO_INP_MASK;
   if ( pinStatus ) {
      IO_D13 = IO_OUT_HIGH;
   } else {
      IO_D13 = IO_OUT_LOW;
   }
}

Per semplificare la vita ho definito, nel .h, alcuni valori che si possono usare:

#define IO_OUT_LOW     0x04
#define IO_OUT_HIGH    0x05
#define IO_INP         0x00
#define IO_INP_MASK    0x02
#define IO_INP_PULLUP  0x10

... in pratica permettono di scrivere il valore LOW, il valore HIGH, di definire un pin come INPUT e, se occorre, attivare la pull-up interna ed infine di mascherare il uint8_t che si legge in input al fine di avere solo il valore logico del pin senza altri bit inutili.

Spero possa essere utile a chi avesse veramente la necessità di un accesso diretto senza l'utilizzo del framework Arduino (cosa che, ribadisco, senza una vera esigenza, è cosa da evitare).

Fatemi sapere ... :grin:

Guglielmo

P.S.: Solo una cosa, sul RA4M1, per questo tipo di accessi, occorre prestare molta attenzione all'allineamento di ciò che si va a leggere/scrivere ... ci sono indirizzi, come quelli usati da me nel .h, che sono per accesso a uint8_t, ce ne sono altri per accesso con uint16_t ed altri per accesso con uint32_t ... occhio quindi se toccate qualche cosa a studiarvi bene i documenti che ho indicato sopra :wink:

3 Likes

Ah, dimenticavo, se date un occhiata dentro al .h, vedete che è già pronto sia per la UNO R4 MINIMA che per la UNO R4 WIFI ... già perché chi ha disegnato le due schede, pur con la stessa identica MCU e gli stessi I/O ... ha pensato bene di usare dei pin di I/O diversi per i vari pin di Arduino e quindi ... sono necessarie due mappature differenti. la selezione avviene in automatico grazie al pre-processore del C ed alle variabili d'ambiente :wink:

Guglielmo

Scusa, Guglielmo...
Perché HIGH e LOW devono assumere questi valori?

...e la maschera di lettura 0x02 è sempre la stessa? Perché è necessaria?

Perché così vuole i bit quel registro relativo al singolo pin ...

... ho eliminato i bit che non ci interessano (in tutto sono 32), noi accediamo come uint8_t alla parte bassa.

In pratica 0x04 indica che il pin è in OUTPUT e il primo bit ( b0) indica se il pin è LOW o HIGH :wink:

Guglielmo

Perché tu leggi tutti gli 8 bit, ma a te interessa solo b1, lo stato del pin in input. Quindi, con un bel AND, si eliminano tutti gli altri :slight_smile:

Guglielmo

Ah! Chiaro. Nel registro non è scritto solo se lo stato dell'ingresso o dell'uscita è alto o basso, ma tutta la configurazione.

Si, ogni singolo pin ha tutta una serie di bit ... tocca selezionare solo quelli che servono :wink:

Guglielmo

Non è sufficiente IO_D7 = IO_INP_PULLUP;? Mi sembra la stessa cosa, perché IO_INP forza tutto a zero e IO_INP_PULLUP mette a 1 il quinto bit.

VERO ... è la stessa cosa, tanto IO_INP vale 0x00 e quindi ... poco influente in caso di OR :grin:

Guglielmo

P.S.: ... però, così, è più leggibile, tanto, alla fine, il compilatore mette direttamente il valore del OR, mica il calcolo :wink:

Piuttosto, visto che qui si parla di GPIO, rammento a tutti che Arduino UNO R4 è in grado di dare sui pin digitali molta meno corrente che Arduino UNO R3:

The GPIOs on the R7FA4M1AB3CFM#AA0 microcontroller can handle up to 8 mA. Never connect devices that drawhigher current directly to a GPIO.

Di base, il massimo teorico è 8mA (mente su Arduino AVR era 40 mA) ... quindi ... meglio NON superare i 5mA. Praticamente, con Arduino UNO R4, è sempre bene usare un driver per pilotare oggetti esterni.

Guglielmo

Quindi anche per un semplice led. Mi pare che il calcolo della R per un led era per far scorrere al massimo 15/20 mA per led, ben al di sopra del limite del GPIO della R4. Corretto ?

Corretto ... purtroppo ... :confused:
... qualcuno, nella sezione dedicata, suggerisce l'uso di 74ACT240N ... :roll_eyes:

Guglielmo

Dipende anche dal tipo e epoca geologica del LED. Ho leddini verdi che si vedono benissimo anche con soli 500 µA.

1 Like

Almeno il 245, che ha tutti gli ingressi da un lato e le uscite dall'altro...

:joy: :joy: :joy: ... si, vero, ci sono minuscoli LED che si accendono con pochissima corrente, ma se la gente continua a usare quelli belli grossi rossi ... dovranno per forza "aiutarli" :roll_eyes:

Guglielmo

... ma un bel ULN2003A non andrebbe bene?
E almeno ci piloti anche qualche cosa di un po' più robusto :roll_eyes:

Guglielmo

domanda sciocca, sporco il thread, poi cancelliamo... ho visto c'e' 74act245 ma anche 74hct245
(anche per il 240) cosa cambia tra Act e Hct ?!? dalle info generiche e dai ds (ma sono gnurant) non vedo molte differenze.

ACT è descritto come "The AC/ACT240 is an octal buffer and line driver designed to be ...", l'altro, HCT, è descritto come "is a high-speed Si-gate CMOS device and is pin compatible with Low-Power Schottky TTL" ... direi che è solo una questione di tecnologia e velocità di commutazione :roll_eyes:

Guglielmo

1 Like

Sì: ricordo che una volta tenevo la corrente sempre alta, intorno ai 15mA, e talvolta avrei desiderato più luce. Ora, invece, i LED cominciano a fare luce già con i 5V tra le dita di una mano e l'anodo del LED tra le dita dell'altra! Difficilmente vado oltre i 5mA.

L'ATmega328p ha anche le uscite ben protette, tanto che resiste bene anche agli inesperti che collegano LED senza una resistenza! L'impronunciabile :smile: dell'R4 non so se è altrettanto ben protetto... Mi viene in mente il vecchissimo NE555, in grado di dare in uscita anche 200 o 300mA (non so per quanto tempo, in realtà!...), ma facendo un cortocircuito in uscita si bruciava in un microsecondo!!! Era anche noto per sporcare l'alimentazione, proprio perché tirava fuori tanta corrente (anche impulsiva, sulla capacità delle piste o dei fili sull'uscita) ed era anche veloce, producendo istantanei abbassamenti della tensione di alimentazione, cosa che potrebbe anche resettare un microcontrollore nelle vicinanze.

In realtà lo si può sintetizzare con il più semplice "RA4M1" (Renesas stessa lo identifica così) che poi ha varie versioni :wink:

Renesas RA4M1 Group.pdf (1.7 MB)

Guglielmo

1 Like