EUR | USD

La manipolazione diretta delle porte

La programmazione è sempre più accessibile al pubblico generale e, di conseguenza, alcuni elementi del codice di base possono finire nel dimenticatoio, come sembra avvenire nel caso della manipolazione diretta delle porte. Alcuni programmatori sostengono che questi comandi siano difficili da leggere e possano creare confusione per chi è meno esperto, e che quindi non debbano mai essere mischiati a strutture più famigliari. Ma perché non usare tutti gli strumenti disponibili per mantenere conciso ed efficiente il codice?

Ad esempio, sono necessari tre registri a 8 bit per controllare l'attività di ogni porta di un microcontroller a 8 bit. Questi registri sono DDRx per la direzione di ingresso/uscita, PORTx per il controllo del livello logico dei pin e PINx che mantiene gli stati correnti dei pin della porta. Per chiarire, prendiamo in considerazione la porta C. Supponiamo che durante la configurazione un programmatore voglia impostare i pin della porta C come uscite con stato iniziale LOW. Spesso si scrive un "for loop" ordinato, che richiede tre o quattro righe di codice ripetuto per analizzare i pin da 0 a 7, per un totale di 32 righe di codice che deve essere eseguito. Tramite funzioni della libreria all'interno del loop, come pinMode() e digitalWrite(), si richiama ulteriore attività non visibile all'utente, che finisce per richiedere l'esecuzione di molto più codice.

Alla fine, gli otto bit del registro della direzione della porta C (DDRC) vengono impostati su HIGH e gli otto bit del registro del livello logico della porta C (PORTC) vengono impostati su LOW. La stessa operazione può essere eseguita impostando i bit su HIGH o LOW con i seguenti comandi diretti sui registri della porta:

DDRC = 0xFF; //Imposta i pin della porta C come USCITE (in codice binario DDRD = 0b11111111;)

PORTC = 0x00; //Imposta i pin della porta C su LOW (in codice binario PORTC = 0b00000000;)

E per impostare i pin della porta A come ingressi?

DDRA = 0x00; //Imposta i pin della porta A come INGRESSI (in codice binario DDRA = 0b00000000;)

E infine i pin della porta D sia come ingressi che come uscite:

DDRD = 0x0F; //Imposta i quattro bit superiori della porta D come INGRESSI e gli inferiori come USCITE (in codice binario DDRD = 0b00001111);

L'uso di un valore esadecimale come 0xFF permette di comprendere meglio l'assegnazione del valore ai bit del registro. "0x" è l'indicatore esadecimale, la prima "F" del numero rappresenta i quattro bit superiori di un registro a 8 bit e la seconda "F" del numero rappresenta i quattro bit inferiori.

Tutte le possibili combinazioni di bit impostate su HIGH o LOW possono essere rappresentate da un valore esadecimale o un (numero) binario letterale. Usare binari letterali è più immediato perché permette di vedere tutti gli otto bit mentre si scrive il codice. Entrambe le scelte sono accettabili se supportate dal compilatore, ma il formato esadecimale è più breve e più bello da vedere.

Nota: l'uso dei binari letterali non è uno standard universale in C/C++.

Ma andiamo oltre

Configurando le porte A e C durante l'impostazione iniziale, sono necessarie ancora solo poche righe di codice con DPM nel loop principale per leggere dispositivi digitali e far funzionare altri dispositivi digitali. In pratica, si possono programmare facilmente due motori passo-passo con interruttori di finecorsa controllati da un joystick digitale con un numero minimo di comandi di registro e una tabella di ricerca. La configurazione hardware di questo scenario è illustrata nella figura 1.

Figura 1: configurazione hardware joystick/motore passo-passo con porte A e C del microcontroller evidenziate. (Immagine per gentile concessione di Digi-Key Electronics)

Hardware:

Abbiamo un solo joystick digitale, con quattro contatti normalmente aperti (NA) collegati a VCC e anche ai pin 0-3 della porta A. Le uscite sono impostate su LOW sul microcontroller. Quando i contatti si chiudono, i pin della porta passano a HIGH. I contatti rappresentano il controllo SU, GIÙ, SINISTRA e DESTRA e sono configurati per consentire l'attivazione contemporanea di due contatti adiacenti, e produrre quindi otto possibili combinazioni per le uscite di commutazione. Una nona uscita rappresenta FERMA TUTTO, che si verifica alla pressione centrale del joystick e quando tutti i contatti sono aperti.

Quattro interruttori di finecorsa con i contatti NA collegati a terra sono collegati anche ai rimanenti pin della porta A corrispondenti alla configurazione SU, GIÙ, SINISTRA, DESTRA del joystick. Le uscite di commutazione sono mantenute HIGH sul microcontroller. Quando i contatti si chiudono, i pin passano a LOW.

Schede driver per motori passo-passo creano il movimento meccanico impostando tre pin di controllo su HIGH e LOW per le funzionalità di passo, direzione e mantenimento. Per questo esempio, tutti gli otto bit della porta C sono dedicati al funzionamento di due circuiti di pilotaggio, anche se due pin non vengono utilizzati.

Programmazione:

Per generare l'uscita corretta per i circuiti di pilotaggio, viene utilizzata una tabella di ricerca e i quattro bit del joystick vengono convertiti negli otto bit del circuito di pilotaggio. Una sola riga di codice nel loop principale chiama la funzione 'get_output()' e le passa il contenuto del registro PINA. Il valore restituito dalla funzione viene scritto direttamente nel registro PORTC:

PORTC = get_output(PINA);

All'interno della funzione, viene eseguito l'accesso alla tabella di ricerca 'lookup_output[ ]' e viene restituito un valore indicizzato. Ma all'interno della funzione succede anche altro che riguarda gli interruttori di finecorsa. Il valore dell'indice della tabella di ricerca contenuto all'interno delle parentesi quadre di 'lookup_output[ ]' è un'espressione di spostamento e mascheramento di bit. La variabile di indice risultante è l'AND bit per bit dei quattro bit superiori del registro di ingresso (valori dell'interruttore di finecorsa) e dei quattro bit inferiori (valori del joystick). Se uno o più dei contatti dell'interruttore di finecorsa sono chiusi ed è presente un valore zero in almeno uno dei quattro bit superiori, il corrispondente bit inferiore viene azzerato.

Nota: dato che quattro bit possono rappresentare 16 combinazioni di bit univoche, le sette posizioni di indice della tabella di ricerca non utilizzate vengono convertite in 0x00 per evitare errori.

Copyuint8_t get_output(uint8_t porta_val)
{
  return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)];         
}

Esempio:

Il joystick nella posizione SU e DESTRA con tutti gli interruttori di finecorsa aperti genererebbe un valore binario del registro PINA di 0b11111001 (o 0xF9 esadecimale) che verrebbe passato alla funzione. La funzione azzera i quattro bit superiori utilizzando un AND bit per bit di 0b00001111 (0x0F) che genera 0b00001001 (0x09) come valore di indice della tabella di ricerca. Il confronto di questo risultato tramite un altro AND bit per bit con il valore di stato dell'interruttore di finecorsa spostato 0b00001111 (0x0F) lascia invariato il valore precedente restituendo 0b00001001 (0x09) come valore di indice finale che punta a 0b00100001 (0x21) nella tabella di ricerca. Questa è la conversione del circuito di pilotaggio passo-passo per SU e DESTRA. Vedere la Figura 2.

Figura 2: Interpretazione del microcontroller degli ingressi delle porte A e C quando il joystick è nella posizione SU e DESTRA. (Immagine per gentile concessione di Digi-Key Electronics)

Se fosse stato attivato l'interruttore di finecorsa SU generando un bit zero, il valore spostato sarebbe stato 0b00000111 (0x07) anziché 0b00001111 (0x0F), negando il corrispondente valore SU del joystick e risultando in un valore di indice finale di 0b00000001 (0x01) anziché 0b00001001 (0x09). Il valore convertito di 0b00000001 (0x01) è 0x26 nella tabella di ricerca. Questa è la conversione del circuito di pilotaggio passo-passo solo per DESTRA. Vedere la Figura 3.

Figura 3: Interpretazione del microcontroller degli ingressi delle porte A e C quando il joystick è nella posizione SU e DESTRA, mentre l'interruttore di finecorsa SU è attivato. (Immagine per gentile concessione di Digi-Key Electronics)

Conclusione

È sempre buona norma ridurre il codice al minimo, indipendentemente dal fatto che un programmatore adotti o meno DPM come strumento per configurare, leggere o scrivere i dati sulle porte. Le stesse operazioni eseguite con funzioni standard della libreria richiedono molto più codice e possono occupare una parte importante della memoria di programma disponibile. Il codice riportato qui sotto occupa meno dell'1% della memoria del microcontroller ATMEGA328P utilizzato durante le prove su banco. L'aggiunta di commenti adeguati nel codice è fondamentale per capire la funzionalità DPM e per semplificarne il debug per i programmatori.

Esempi di hardware fornito da Digi-Key:

Motori passo-passo - https://www.digikey.com/short/pdnfp4

Controller passo-passo - https://www.digikey.com/short/pdnf4r

Joystick - https://www.digikey.com/short/pdnf57

Interruttori di finecorsa - https://www.digikey.com/short/pdnfwm

Codice di esempio:

Copyconst uint8_t lookup_output[16] = {
0x09,   //Index 0  All Stop. Apply hold current
0x26,   // Index 1 Right			 
0x34,   // Index 2 Left
0x00,   // Index 3 Unused
  	0x36,   // Index 4 Down
  	0x0C,   // Index 5 Down/Right
  	0x31,   // Index 6 Down/Left
  	0x00,   // Index 7 Unused
  	0x24,   // Index 8 Up
  	0x21,   // Index 9 Up/Right
  	0x0E,   // Index 10 Up/Left
  	0x00,   // Index 11 Unused
  	0x00,   // Index 12 Unused
  	0x00,   // Index 13 Unused
  	0x00,   // Index 14 Unused
  	0x00    // Index 15 Unused
};

void setup() {
     // Set all bits in port A direction register as INPUTs; 
    // Limits (up, down, left, right) Joystick (up, down, left, right)
    DDRA = 0x00;

    // Set all bits of port C direction register as OUTPUTs;
   // Motor control (Not Used, Mot_1, Dir_1, En_1, Not Used, Mot_2, Dir_2, En_2
   DDRC = 0xFF; 
}

void loop() {
    //Send the port A values to the function. Write the return value to port C.
   PORTC = get_output(PINA);   
}


/***** Input Value Translation Function *******/ 
uint8_t get_output(uint8_t porta_val)
{
    // Compare the limit switch and joystick values. Retrieve and return the translated value.
    return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)];    
}

Informazioni su questo autore

Image of Don Johanneck

Don Johanneck, Technical Content Developer presso Digi-Key Electronics, lavora in azienda dal 2014. Di recente promosso alla sua attuale posizione, è responsabile delle descrizioni per i video e dei contenuti di prodotto. Don ha conseguito una laurea tecnico-scientifica in Electronics Technology & Automated Systems presso Northland Community & Technical College attraverso il programma di borse di studio Digi-Key. Ama il modellismo radiocomandato, il restauro di auto d'epoca e il bricolage.

More posts by Don Johanneck