[RISOLTO] - controllo in contemporanea di più pin di arduino (uno, mega,...)

Progetti Arduino
Leonardo220
Messaggi: 58
Iscritto il: gio 4 mar 2021, 21:32

[RISOLTO] - controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da Leonardo220 »

Salve, è da un po' di tempo che mi affligge questo dilemma: c'è un metodo più veloce rispetto all'utilizzo di una serie di cicli e condizioni per controllare un gruppo di pin in modo da poterli controllare in contemporanea utilizzando come valore per gestirli un numero espresso in binario o in altro formato? In pratica chiedo se esiste un modo per fare si che, se ho abilitati ad esempio i pin 23, 24, 25, 26, posso usare una funzione o usare un'altra tecnica che mi raggruppa quei 4 pin in modo che ognuno rappresenti una cifra del numero con cui li controllerò espressa in binario, per poi usare un'altra funzione che va ad assegnare ai pin in questione un valore che gli assegno? Un esempio di pseudo-codice:

A=assegna(23, 24, 25, 26);
output(A, 0110); //i pin assumono valore: pin23==0, pin24==1, pin25==1, pin26==0

o al più qualcosa che dando in pasto i pin e il numero da assegnare faccia la stessa cosa di quella ipotetica funzione output che ho scritto sopra:

output2(23,24,25,26,0110); //stesso output di prima

il tutto cambiando lo stato di tutti i pin in questione contemporaneamente? So che scrivendo il codice in assembly è possibile gestire qualcosa di simile, ma è piuttosto complicato... grazie in anticipo e scusatemi per il pippone :lol:
Ultima modifica di Leonardo220 il ven 21 mag 2021, 1:52, modificato 1 volta in totale.
apollokid
Messaggi: 74
Iscritto il: lun 27 lug 2020, 14:36

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da apollokid »

Domanda stupida: hai davvero bisogno di tempistiche così stringenti per cui perdere qualche ciclo di clock tra il primo bit e l'ultimo rappresenta un problema?
Per dire, io sto leggendo tramite interrupt un segnale ad onda quadra perchè devo misurare con precisione il tempo di Ton e senza particolari accorgimenti riesco a fare misurazioni al centesimo di millisecondo (ero sceso anche a precisioni maggiori ma nel mio caso era inutile perchè il segnale passava per un optoisolatore ed i fronti di salita/discesa erano proprio in quell'ordine di tempi)
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da pgv »

Se il problema e' la sincronicita' dei cambiamenti dei vari bit, rischia che la presenza di componenti esterne sulla scheda di Arduino possa creare dei ritardi diversi a seconda del pin, ma se la temporizzazione non deve essere al picosecondo, per citare Frankenstein Junior "Si... puo'... fare!".
Non con qualsiasi combinazione di pin, ma esistono delle opzioni. Bisogna pero' prendere in conto non piu' la numerazione dei pin "secondo Arduino" ma quella interna al microcontrollore. Prendiamo per esempio un Arduino Nano Nella figura seguente (per esempio):
https://www.electronicshub.org/wp-conte ... Pinout.jpg
vediamo come i pin da 8 a 13 (o da D8 a D13) in terminologia arduinesca si chiamano anche PB0, PB1,... PB5. Il motivo e' che, internamente all'ATMEGA328P che e' il cervello della scheda essi appartengono alla cosiddetta "PORTA B". Similmente, i pin da A0 a A5 (in Arduino) si chiamano anche PC0, PC1, ... PC5 perche' appartengono alla "PORTA C" (ma non A6 e A7!). Infine, abbiamo che i pin da 0 a 7 (o da D0 a D7) si chiamano anche PD0, PD1,... PD7 in quanto formano la "PORTA D". Queste porte corrispondono ad un certo numero di registri interni del microcontrollore. Registri che, nell'ATMEGA328P, hanno le dimensioni di un byte. E' quindi possibile leggere o scrivere otto bit alla volta (purche' appartengano tutti alla stessa PORTA ovviamente). Prendiamo la Porta C perche' (a parte l'ADC) non ha funzioni speciali (come TX e RX). Per leggere:

Codice: Seleziona tutto

uint8_t valorePortaC;
const uint8_t mascheraPortaC = 0x3F;	// La Porta C dell'Arduino Nano ha solo i 6 bit meno significativi
// altro codice che non ci interessa per ora
valorePortaC = PORTC & mascheraPortaC;	// Eliminiamo i bit 6 e 7 che chissa' che cosa ci combinano
e per scrivere una possibile sequenza di istruzioni e':

Codice: Seleziona tutto

uint8_t valorePortaC, vecchiaPortaC;
const uint8_t mascheraPortaC = 0b00111111;	// Binario
// altro codice irrilevante, ma nel quale valorePortaC assume il valore necessario. Per esempio
valorePortaC = 0b00110101;	// Anche questo e' binario, per seguire i bit individualmente
// Altro codice intermedio e irrilevante...
valorePortaC &= mascheraPortaC;	// Azzeriamo i due bit "proibiti" 6 e 7, per sicurezza
vecchiaPortaC = PORTC;	// Leggiamo il valore attuale
vecchiaPortaC &= !mascheraPortaC;	// azzeriamo tutti i bit tranne i due bit "proibiti" 6 e 7 che non abbiamo diritto di utilizzare sul Nano
                                                        // a questo punto, vecchiaPortaC contiene solo zeri a parte i bit 6 e 7 che mantengono il valore precedente
valorePortaC |= vecchiaPortaC ;	// Con l'istruzione | (OR bit a bit) abbiamo combinato i bit da 0 a 5 del nostro nuovo valore con i bit 6 e 7
                                                        // gia' presenti sulla Porta C e che non abbiamo il diritto di cambiare                                              
PORTC = valorePortaC;			// A questo punto rimpiazziamo il vecchio valore con quello appena calcolato
Ci sono modi piu' stringati di scrivere il codice, ma cosi' seguiamo passo passo le manipolazioni necessarie per evitare di "perdere" un bit che non abbiamo diritto di modificare. Ovviamente, se uno desidera utilizzare un sottoinsieme ancora piu' ridotto (che so io, 4 bit?) basta cambiare il valore assegnato a mascheraPortaC mettendo un bit a "1" solo in corrispondenza ai bit che si vogliono manipolare. Per esempio,

Codice: Seleziona tutto

mascheraPortaC = 0b00111100;
utilizza solamente i bit 2, 3, 4, e 5 della Porta C (A2, A3, A4 e A5).

Buona serata e buon divertimento!
Leonardo220
Messaggi: 58
Iscritto il: gio 4 mar 2021, 21:32

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da Leonardo220 »

apollokid ha scritto: sab 10 apr 2021, 17:28 Domanda stupida: hai davvero bisogno di tempistiche così stringenti per cui perdere qualche ciclo di clock tra il primo bit e l'ultimo rappresenta un problema?
Per dire, io sto leggendo tramite interrupt un segnale ad onda quadra perchè devo misurare con precisione il tempo di Ton e senza particolari accorgimenti riesco a fare misurazioni al centesimo di millisecondo (ero sceso anche a precisioni maggiori ma nel mio caso era inutile perchè il segnale passava per un optoisolatore ed i fronti di salita/discesa erano proprio in quell'ordine di tempi)
in effetti stavo cercando un modo per gestire più velocemente uscite e ingressi di informazioni in formato parallelo, per avere una gestione più rapida sia dal punto di vista del codice che della velocità di esecuzione: una volta mi ero imbattuto nel problema di dover gestire un integrato che aveva input e output in formato parallelo, quindi per riuscire ad eseguire in tempi decenti e con sicurezza il tutto mi serviva quel tipo di gestione dei pin... però se non ricordo male l'unica soluzione che trovai fu quella di usare l'assembly per gestire quella parte del codice, escludendo la soluzione ovvia del cambio dei valori pin a pin, che però rende un po' più difficile variare i valori in modo da rappresentare un numero binario da rappresentare.
apollokid
Messaggi: 74
Iscritto il: lun 27 lug 2020, 14:36

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da apollokid »

Non mi sono mai interessato, ma penso esistano in commercio dispositivi che ti permettano di convertire da seriale a parallelo e viceversa.
Questo per esempio sembrerebbe a prima vista essere un modo: https://www.arduino.cc/en/Tutorial/Foundations/ShiftOut

Usare le porte di I/O del processore come ti diceva pgv potrebbe essere un'altra soluzione.
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da pgv »

Io in passato per convertire stringhe di bit da seriale a parallelo mi sono trovato bene con i chip TTL 74xx595 (xx puo' essere LS, HC, AHC...) che sono registri a scorrimento a 8 bit utilizzati per la conversione da seriale a parallelo. Il buono e' che hanno una "memoria" addizionale, per cui mentre scorrono i bit le uscite non cambiano, e ci vuole un colpo di clock in un ingresso addizionale per trasferire i nuovi valori alle uscite. La differenza tra la comparsa dei vari bit sulle uscite dipende dalla famiglia (AHC meglio di LS, per esempio). I 74AHC595 possono essere alimentati anche a 3.3 V e a 5 V vanno molto veloci (a 3.3 V un pochino meno). Se si tratta di un numero consistente di bit da manipolare insieme, i registri a scorrimento probabilmente sono preferibili, mentre se il numero di bit e' limitato (meno di 8) io preferisco manipolare le Porte del micro.
Leonardo220
Messaggi: 58
Iscritto il: gio 4 mar 2021, 21:32

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da Leonardo220 »

pgv ha scritto: sab 10 apr 2021, 21:19 Se il problema e' la sincronicita' dei cambiamenti dei vari bit, rischia che la presenza di componenti esterne sulla scheda di Arduino possa creare dei ritardi diversi a seconda del pin, ma se la temporizzazione non deve essere al picosecondo, per citare Frankenstein Junior "Si... puo'... fare!".

per quello no problem, in caso di problemi posso sistemare con dei buffer o con dei condensatori in per rendere più simili le capacità di carico o comunque qualche soluzione si trova... comunque non credo servirà mai la precisione al picosecondo...
pgv ha scritto: sab 10 apr 2021, 21:19 Non con qualsiasi combinazione di pin, ma esistono delle opzioni. Bisogna pero' prendere in conto non piu' la numerazione dei pin "secondo Arduino" ma quella interna al microcontrollore. Prendiamo per esempio un Arduino Nano Nella figura seguente (per esempio):
https://www.electronicshub.org/wp-conte ... Pinout.jpg
vediamo come i pin da 8 a 13 (o da D8 a D13) in terminologia arduinesca si chiamano anche PB0, PB1,... PB5. Il motivo e' che, internamente all'ATMEGA328P che e' il cervello della scheda essi appartengono alla cosiddetta "PORTA B". Similmente, i pin da A0 a A5 (in Arduino) si chiamano anche PC0, PC1, ... PC5 perche' appartengono alla "PORTA C" (ma non A6 e A7!). Infine, abbiamo che i pin da 0 a 7 (o da D0 a D7) si chiamano anche PD0, PD1,... PD7 in quanto formano la "PORTA D". Queste porte corrispondono ad un certo numero di registri interni del microcontrollore. Registri che, nell'ATMEGA328P, hanno le dimensioni di un byte. E' quindi possibile leggere o scrivere otto bit alla volta (purche' appartengano tutti alla stessa PORTA ovviamente).

In pratica non ho 3 porte da 8 bit cadauna, ma una da 8 e 3 porte da 6 bit, giusto?
Inoltre, i commenti che ho aggiunto al codice che hai postato, descrivono bene ciò che succede?

Codice: Seleziona tutto

uint8_t valorePortaC; //dichiaro una variabile intera senza segno di 8 bit (un byte), tipo definito dalla libreria stdint.h
const uint8_t mascheraPortaC = 0x3F;	// La Porta C dell'Arduino Nano ha solo i 6 bit meno significativi, è espressa in esadecimale. Volessi 					 
     								// esprimerla in binario sarebbe 0b00111111, giusto? Oppure 63 in decimale, ho indovinato? Sono 																											 
  								// descrizioni equivalenti? Serve per selezionare i bit da leggere
// altro codice che non ci interessa per ora
valorePortaC = PORTC & mascheraPortaC;	// Eliminiamo i bit 6 e 7 che chissa' che cosa ci combinano, è usata mascheraPortaC per la selezione dei 																	 
 								//bit utili nel nostro caso
Anche qui metto dei commenti, sono corretti?

Codice: Seleziona tutto

uint8_t valorePortaC, vecchiaPortaC;
const uint8_t mascheraPortaC = 0b00111111;	// Stesso discorso per la "stessa variabile" nel caso di lettura, vedere codice di sopra
// altro codice irrilevante, ma nel quale valorePortaC assume il valore necessario. Per esempio
valorePortaC = 0b10110101;	// Posso anche esprimerlo in esadecimale o decimale? O è meglio usare solo il binario?
// Altro codice intermedio e irrilevante...
valorePortaC &= mascheraPortaC;	// Azzeriamo i due bit "proibiti" 6 e 7, per sicurezza, in questo caso viene: valorePortaC==0b00110101
vecchiaPortaC = PORTC;	// Leggiamo il valore attuale
vecchiaPortaC &= !mascheraPortaC;	// azzeriamo tutti i bit tranne i due bit "proibiti" 6 e 7 che non abbiamo diritto di utilizzare sul Nano
                                                        // a questo punto, vecchiaPortaC contiene solo zeri a parte i bit 6 e 7 che mantengono il valore precedente
                                                        // nel nostro esempio risulta: vecchiaPortaC==0bXX000000
valorePortaC |= vecchiaPortaC ;	// Con l'istruzione | (OR bit a bit) abbiamo combinato i bit da 0 a 5 del nostro nuovo valore con i bit 6 e 7
                                                        // gia' presenti sulla Porta C e che non abbiamo il diritto di cambiare  
                                                        // risulterà: valorePortaC==0bXX110101                                           
PORTC = valorePortaC;			// A questo punto rimpiazziamo il vecchio valore con quello appena calcolato
pgv ha scritto: sab 10 apr 2021, 21:19 Ci sono modi piu' stringati di scrivere il codice, ma cosi' seguiamo passo passo le manipolazioni necessarie per evitare di "perdere" un bit che non abbiamo diritto di modificare. Ovviamente, se uno desidera utilizzare un sottoinsieme ancora piu' ridotto (che so io, 4 bit?) basta cambiare il valore assegnato a mascheraPortaC mettendo un bit a "1" solo in corrispondenza ai bit che si vogliono manipolare. Per esempio,

Codice: Seleziona tutto

mascheraPortaC = 0b00111100;
utilizza solamente i bit 2, 3, 4, e 5 della Porta C (A2, A3, A4 e A5).
Questa parte per fortuna l'ho capita subito :) . Comunque dove posso trovare anche i metodi più stringati per fare questo procedimento? Adesso sono molto curioso :)
Comunque perdonami se ti ho risposto piuttosto in ritardo, ci ho messo un po' a decifrare e a capire il tutto, Grazie mille per la delucidazione!
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da pgv »

Wow, che messaggio lungo! Vediamo di non perdere nulla.

0. Mi sono accorto di avere scritto una sciocchezza. Per invertire bit a bit un numero, l'operatore NON E' (come purtroppo ho scritto) ! (punto esclamativo) che e' l'operatore di negazione LOGICA, ma bensi' ~ (tilde)!

1. 0x3F (esadecimale) == 0b00111111 (binario) == 63 (decimale) = 077 (ottale, per chi se lo ricorda...). Esatto. Quanto all'esistenza dei bit "mancanti", si spiega con il fatto che, pur avendo 32 pin (28 nella versione DIL) l'ATMEGA328P permette di servirsi dei pin di I/O in molti modi diversi (li chiamano "alternate function"). In ispecie, i bit 7 e 6 della Porta B servono anche da ingressi per un quarzo esterno ( e ovviamente se vengono utilizzati a questo scopo non possiamo servircene per fare I/O), e il bit 6 della Porta C serve anche da RESET esterno (e in realta' potremmo utilizzarlo come I/O ma con estreme precauzioni e solo in caso di necessita' estrema senno' rischiamo di autoresettarci programmaticamente l'Arduino...).

2. la scelta tra binario, esadecimale, decimale e ottale e' una questione di gusto personale (evitiamo una Jihad sull'argomento). Io personalmente trovo piu' facile lavorare in esadecimale (e ricordarmi per piu' di sette secondi un valore) che in binario, ma qualche volta scrivo in binario, per esempio se voglio evidenziare bit a bit quel che faccio. Il decimale per accedere i registri di un micro e' meglio scordarselo, e l'ottale ce lo siamo gia' scordato tutti quanti (ah, i registri del PDP-11 che avevano quei convenienti campi di tre bit per indicare origine e destinazione delle istruzioni in LM...). Esadecimale: piu' compatto, piu' simile ad un "numero"; binario: piu' simile ad una sequenza (ordinata) di bit.

3. Un modo conveniente di "giocare" un pochino sarebbe di appendere sei LED ai sei pin A0..A5 (che a parte l'ADC non hanno granche' di funzioni secondarie e quindi non rischiano di compromettere altre funzioni tipo I2C, SPI, UART/USB...) e scriverci delle parole a sei bit. Magari in maniera interattiva, leggendo un numero con Serial.parseInt() dal Serial Monitor.

4. Per scrivere operazioni sulle Porte di I/O in maniera piu' stringata, prendiamo il codice seguente, "strippato" dei commenti:

Codice: Seleziona tutto

valorePortaC &= mascheraPortaC;
vecchiaPortaC = PORTC;
vecchiaPortaC &= ~mascheraPortaC;	// e NON !mascheraPortaC come erroneamente indicato
valorePortaC |= vecchiaPortaC ;                          
PORTC = valorePortaC;
e rimpiazziamolo con:

Codice: Seleziona tutto

PORTC = (PORTC & ~mascheraPortaC) | (valorePortaC & mascheraPortaC);
che fa esattamente le stesse operazioni ma su una riga sola di codice (e quindi si capisce meno immediatamente).
Un'altra macro utile per costruire maschere e riuscire a capire che cosa si stava facendo anche sei mesi dopo e' _BV(bit), che fondamentalmente produce un valore intero contenente tutti zeri tranne il bit di ordine bit, per cui se per esempio volessimo un byte con i soli bit 6 e 7 a 1 e gli altri a 0 possiamo scrivere

Codice: Seleziona tutto

uint8_t valore, inverso;

valore = _BV(7) | _BV(6);	// 0b11000000
// oppure, se vogliamo "flippare" i bit e avere a 0 solo i bit 6 e 7
inverso = ~(_BV(7) | _BV(6));	// 0b00111111
Questo riesce particolarmente utile quando si va a stuzzicare registri che non rappresentano otto pin equivalenti ma collezioni di flag ciascuni dei quali puo' fare cose molto diverse, per esempio quelli che controllano il modo di operazione dei Timer o gli Interrupt. Raccomando la lettura del "manuale" del microcontrollore https://ww1.microchip.com/downloads/en/ ... 02061B.pdf a quanti desiderassero addentrarsi in questo campo minato...

Ma chiariamoci, non e' da leggere la sera a letto, solo da consultare in caso di bisogno!
Avatar utente
pgv
Messaggi: 484
Iscritto il: gio 17 set 2020, 13:16
Località: Ginevra

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da pgv »

E bisogna che io smetta di rispondere quando sono stanco... In ATMEL, ci sono (piu' di, ma soprattuto) tre registri legati ad una Porta di Ingresso Uscita: DDRx (x = A, B, C,...) i cui bit determinano la direzione di ciascun pin "fisico" (un bit a "1" seleziona il pin come uscita), PORTx scrivendo nel quale si modificano i livelli in uscita per i pin dichiarati come uscite (non solo, ma quasi... se scriviamo un "1" in un bit di PORTx che e' stato dichiarato come INGRESSO nel corrispondente bit di DDRx, in realta' attiviamo il pull-up interno del pin! E per disattivare il pull-up occorre scrivere uno "0" PRIMA di dichiarare il pin come uscita in DDRx) e che e' scrivibile ma anche leggibile (permette di rileggere quel che e' gia' scritto nelle uscite) e PINx che serve per leggere i livelli dei pin dichiarati come ingressi (MA, attenzione, se ci SCRIVIAMO un "1" in una qualche posizione la cosa ha l'effetto di INVERTIRE il bit corrispondente nel registro PORTx, con tutte le possibili conseguenze!). Quindi, per tutte le operazioni di SCRITTURA dei pin di USCITA vanno usati i registri PORTX, mentre per le operazioni di LETTURA dei pin di INGRESSO bisogna utilizzare i registri PINx e non PORTx come ho erroneamente scritto piu' sopra in un momento di follia...

Comunque, come vedete il controllo degli ingressi/uscite di una MCU ATMEL e' complicato, e quindi ecco perche' Arduino ci ha provvisti di comode funzioni che si occupano di tutta la "urocrazia" per noi (a costo di una certa lentezza).
apollokid
Messaggi: 74
Iscritto il: lun 27 lug 2020, 14:36

Re: controllo in contemporanea di più pin di arduino (uno, mega,...)

Messaggio da apollokid »

pgv ha scritto: mer 14 apr 2021, 7:36 Comunque, come vedete il controllo degli ingressi/uscite di una MCU ATMEL e' complicato, e quindi ecco perche' Arduino ci ha provvisti di comode funzioni che si occupano di tutta la "urocrazia" per noi (a costo di una certa lentezza).
Ma infatti era per quello che avevo fatto la prima domanda: serve veramente tutta questa precisione? E se serve bisogna anche valutare quanto.
Se posso dare un mio piccolo suggerimento a Leonardo (poi magari mi dici che l'hai già letto tutto), investi qualche euro in un libro come "Arduino. Progetti e soluzioni" della Oreily o in un altro libro "mattoni" analogo.
Non dico che ci siano le risposte a tutto, ma trovi sicuramente affrontati tutti i temi dai più più semplici (per un informatico parte del libro è una passeggiata) ai più tecnici e complicati come quello dell'uso dei registri per quelle rare situazioni dove serve davvero spremere al massimo l'hardware.
Non serve studiarlo tutto a memoria, basta leggerlo tutto e farsi l'idea così l'idea di cosa è possibile fare e come, e poi quando serve una cosa si approfondisce quello specifico argomento.
Io almeno ho fatto così prima di iniziare.
Rispondi