Apr 202018
 

Oggi prendo spunto da una domanda che mi è stata posta sul blog per scrivere un breve articolo introduttivo sugli operatori booleani visto che da un lato sono estremamente importanti, dall’altro sono ampiamente sottovalutati e molto spesso sconosciuti. Cominciamo dalla domanda che mi è stata posta e poi procediamo con ordine.

 ……Vorrei chiederti come posso fare per sostituire l’accensione e lo spegnimento di ogni singola uscita del 595 come si fa con la arduino, per spiegarmi meglio : digitalWrite(ledGiallo, HIGH)……digitalWrite(ledGiallo, LOW); ho capito che se uso My595.Send595Pin(0,1), ma li spengo tutti, vorrei capire come spegnerli individualme……

La domanda riguarda un ramo della mia McMajan Library, ossia quella parte di codice che è dedicata all’utilizzo dei 74HC595 per l’espansione delle uscite digitali di Arduino con la possibilità di utilizzare in contemporanea sul “bus” sia display LCD che uscite digitali vere e proprie. Ora, nemmeno io mi ricordo tutte le funzioni presenti nella libreria che ho programmato, non per niente hoscritto  la documentazione, certo non completa e con il mio inglese maccheronico, ma che utilizzerò come farebbe chiunque per capire il funzionamento della libreria, quindi senza andare a leggere il codice sottostante.

La funzione presa in considerazione è Send595Pin(0,1); andiamo quindi a vedere cosa scrivo nella documentazione:

It works as the sum of Set595Pin and the Send595, in the sense that sets the 8-bit and sends them directly to the shift register. If you have only one shift register to edit you should use this function. If you need to change more than one at the same time you should use two or more Set595 and eventually Send595.

Ok, qualcuno potrebbe non destraggiarsi con l’inglese, se poi l’ho scritto io ha pure una scusa in più, vediamo perciò come lo traduce Google:

Funziona come la somma di Set595Pin e Send595, nel senso che imposta l’8 bit e li invia direttamente al registro a scorrimento. Se si dispone di un solo registro a scorrimento da modificare, è necessario utilizzare questa funzione. Se è necessario modificarne più di uno contemporaneamente, è necessario utilizzare due o più Set595 e infine Send595.

Quindi ci dice che questa funzione setta gli 8 bit e li invia direttamente allo shift register. Poi aggiunge che se disponete di un solo shift register è la funzione ideale, altrimenti è meglio usare due o più Set595 (una per shift register da modificare) e poi la Send595, questo per massimizzare la velocità di esecuzione. La Send595Pin richiede due parametri, il primo è il valore da inviare, il secondo è il numero di shift register della catena,  a partire da 0. Facendo finta di averne uno solo il secondo parametro resterà a zero ma con il primo valore come facciamo a settare le 8 uscite? A dirla tutta esiste la funzione Set595Pin(D7,D6,D5,D4,D3,D2,D1,D0,sr)  che permette di settare tutti i singoli valori, impostando i vari Dx a true o false da far seguire alla funzione Send595 che invia il dato allo shift register.

Ma senza usare questa funzione come facciamo a settare quel valore? Un solo numero come fa a trasportare lo stato di accensione e spegnimento di 8 uscite? E poi come facciamo a cambiarne uno solo quando ne abbiamo bisogno? Cominciamo con il comprendere che tipo di dato è il numero che dobbiamo usare come primo parametro. Esso è un singolo byte che come dovremmo sapere è un numero compreso fra 0 e 255. Si tratta di 256 valori che corrispondono a 2 elevato all’ottava. Infatti è composto da 8 bit ed ogni bit può contenere il valore 0 o 1. Quindi possiamo immaginare il byte come 8 casalle una adiacente all’altra, all’interno della quale possiamo mettere il valore 0 o 1. Quindi a pensarci bene è del tutto lecito usare il valore 0 o 1 per indicare lo stato acceso a spento delle varie uscite del nostro 595 che guarda caso sono proprio 8. Ad esempio se volessimo tenere accesa la prima, sesta e ultima uscita potremmo immaginarci qualcosa di questo tipo:

1 0 1 0 0 0 0

1

Si noti che il conteggio avviene da DESTRA. Ora dobbiamo convertire questo schema in un numero da trasmettere. Per farlo dobbiamo usare un po’ di matematica ma nulla di così complesso. Ogni 1 corrisponderà a 2 elevato a n dove n è corrispondente a 0 per la casella più a destra e 7 per quella più a sinistra per cui abbiamo 27+25+20, ossia 128+32+1=161. Quindi per inviare qul tipo di stato delle uscite dovrete usare l’istruzione Set595Pin(161,0). Ok, direte voi, ma facciamo finta che siamo nella condizione qui sopra e vogliamo accendere anche la quinta uscita. Dobbiamo rifare tutti quei calcoli di potenza visti sopra? E quindi devo ricordarmi ogni volta anche lo stato di tutti i restanti bit? Deve per forza essere tutto così complicato?

Assolutamente no, ed è per questo che gli operatori booleani ci vengono in soccorso. Non andremo a studiarli tutti, ci soffermiamo solo su quelli che ci servono per questo scopo. Intanto vediamo quali sono questi operatori:

Simbolo Operatore
& AND
| OR
^ XOR
~ Complemento a uno
Shift a destra >>
Shift a sinistra <<

 

E vediamo come funzionano alcuni di essi:

 

A B A AND B A OR B A XOR AB
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

Cerchiamo di capire. Prendiamo a titolo di esempio l’operatore AND il cui simbolo è &. Quardiamo come agisce sui due operatori A e B. Se A e B valgono entrambi 0, l’operazione di AND restituisce 0, quindi 0&0=0. Procedendo con la tabella scopriamo velocemente che AND darà un risultato pari a zero in tutte le condizioni, tranne quando entrambi gli operatori valgono 1 per cui 1&1=1 ed è l’unica condizione in cui il risultato vale 1. L’OR (che in inglese significa oppure) fornisce 1 quando uno degli operatori vale 1 mentre restituisce 0 solo quando entrambi valgono 0 per cui 0|0=0. Questi due sono operatori che agiscono su due valori, ma ve ne sono altri come l’operatore di complemento a uno che agisce su un solo valore, infatti esso agisce invertendo il valore che da 0 diventa 1 e da 1 diventa 0. Lo so sembra una noia mortale, ma quando vedrete quanto sono utili forse cambierete idea. Vediamo ancora un solo operatore, ossia lo shift a sinistra. Cosa fa? Esso “sposta” tutti i bit del byte che gli forniamo come argomento (quindi non agisce su un solo bit) verso sinistra di un numero di bit da noi stabilito. Proviamo ad applicare lo shift verso sinistra di un solo bit al nostro esempio, il 161 di prima e vediamo cosa succede:

1 0 1 0 0 0 0 1 << 1
0 1 0 0 0 0 1 0

Quindi la prima casellina che valeva 1 finisce a sinistra, fuori dal byte quindi semplicemente scompare e se ne perde il valore, la seconda con lo zero passa in prima posizione, la terza con l’uno passa in seconda e così via sino all’ultima che non ha un valore da ricevere alla sua destra e viene percò impostato a zero.

Bene torniamo un attimo alla situazione originale e impostiamo quel valore (161) ad una variabile:

1 0 1 0  0 
 0 0

1

unsigned char val=161;

Ok, di base abbiamo deciso quindi che le uscite 1,6 ed 8 (bit 0,5 e 7)  sono accese, le altre spente, ma questo è solamente un esempio, nella realtà il chip appena acceso ha tutte le uscite a zero per cui quasi certamente partirete da quel valore, ma adesso mi serve qualcosa di più complesso per mostrarvi come fare.

Facciamo finta di voler accendere il pin 4 che nello schema sopra ho voluto sottolineare in rosso. Guardiamo la formulina qui sotto che è la chiave di tutto:

val=(val&~(1<<bit)) | (HL<<bit);

Dove val l’abbiamo definito prima, bit vale 3 in quanto il conteggio deve essere fatto da destra ed il primo è 0 (e non 1, mi raccomando) ed HL indica Zero se vogliamo spegnere il bit o Uno se vogliamo accenderlo. Andiamo ora a valutare le varie porzioni della formula:

la parte (1<<bit) imposterà il primo bit a destra ad 1:

0 0 0 0 0 0 0 1

e poi lo sposterà verso sinistra di 3 posizioni

0 0 0 0 1 0 0 0

che in decimale varrà 8 (23).

E HL<<bit quanto varrà? Essendo HL pari a 1 e bit pari a 3 otteniamo lo stesso identico valore appena visto quindi la nostra espressione per ora è:

val=(val&~8) | 8;

Come vedete ora dobbiamo sottoporre il nostro valore, quello che mantiene in memoria tutte le uscite dello shift register ad un AND del complemento a 1 di 8…..mamma mia quanto sembra difficile. Come detto il complemento a 1 inverte tutti  i bit per cui varrà:

1 1 1 1 0 1 1 1

Ora dobbiamo applicare l’and fra il nostro valore iniziale e quello appena ottenuto ricordando che l’AND fornisce 1 solo se entrambi i bit a cui viene applicato sono pari a 1, per cui

1 0 1 0 0 0 0 1 &
1 1 1 1 0 1 1 1 =
1 0 1 0 0 0 0 1

A questo dobbiamo applicare l’OR finale:

1 0 1 0 0 0 0 1 |
0 0 0 0 1 0 0 0 =
1 0 1 0 1 0 0 1

Ora confrontiamo il valore iniziale e quello ottenuto alla fine di tutti i calcoli:

1 0 1 0 0 0 0 1
1 0 1 0 1 0 0 1

Come vedete è stato impostato a 1 il bit che volevamo all’inizio. E se vogliamo reimpostarlo a zero usando la medesima formula?

val=(val&~(1<<bit)) | (HL<<bit);

1<<bit ovviamente non cambia, ciò che invece cambia è HL<<bit in quanto impostando un bit a zero possiamo spostarlo a sinistra di quante posizioni vogliamo ma varrà sempre zero per cui la formulina diventa.

val=(val&~8) | 0;

Visto che rispetto a prima anche val ha assunto un valore diverso ci tocca rifare i calcoli:

1 0 1 0 1 0 0 1 &
1 1 1 1 0 1 1 1 =
1 0 1 0 0 0 0 1

e poi:

1 0 1 0 0 0 0 1 |
0 0 0 0 0 0 0 0 =
1 0 1 0 0 0 0 1

Per cui siamo tornati al valore originale.

Mi rendo conto che possa sembrare magia nera, ma alla fin dei conti si tratta di una semplice formuletta che anche se non riuscite a comprendere a fondo è sufficiente copiarla ed utilizzarla. Ora potreste quindi costruire una semplice funzione per modificare le varie uscite, qualcosa del tipo:

unsigned char ChangePinValue(unsigned char old_value,char pin_number, bool value)
{
 unsigned char newval=(old_value&~(1<<pin_number)) | (value<<pin_number);
 return(newval);
}

Ma la domanda che potreste farmi è: perchè non hai implementato una funzione del genere nella tua libreria? La verità è che effettivamente esiste già sia la funzione per leggere che scrivere i singoli bit e a dirla tutta è più sofisticata di quanto vi ho spiegato ora, infatti è in grado di funzionare su aree di memoria superiori al singolo byte, in una catena di 10 74Hc595 potreste modificare in modo diretto il bit 50 senza alcun problema, anzi, internamente la libreria lo fa al posto vosto prorio con questa funzione. Se ad esempio avete definito:

hc595 My595(12,11,13,2); // latch,clock,data,number of 74hc595

Con una funzionde del tipo SetBitN(My595.Buffer,2,0); porterete a low il bit numero due (terza uscita). Allo stesso modo se volete cambiare il medesimo bit ma del secondo shift register avete due possibilità:

SetBitN(My595.Buffer[1],2,0); // secondo bit del secondo shift register: si noti che Buffer[1] punta al secondo shift register ed il 2 successivo indica il bit 2 di quel register

SetBitN(My595.Buffer,10,0); // decimo bit, accesso diretto: Buffer (sottinteso [0]) punta al primo shift register per cui il 10 punta al terzo bit del secondo (il primo ne ha 7 e 10-7=3)

Dopo la modifica del buffer dovete però ricordarvi di inviare il risultato alla catena di shift register con Send595();

Anche se questo articolo può sembrare un po’ ostico da comprendere, vi permette comunque di riuscire a modificare un bit a vostro piacimento con la funzione qui sopra che potete semplicemente copiare ed inserire nel vostro progetto o, se proprio state usando la mia libreria, potete copiare le linee di codice qui sopra ed adattarle al vostro progetto senza grosse modifiche. Se invece cercavate solamente una breve introduzione agli operatori booleani, spero che l’articolo via sia stato comunque utile.

Concludo facendomi un minimo di pubblicità, ricordandovi che tutte queste cose le trovate nel mio libro di programmazione C che si chiama EasyC e che trovate sul blog, su Google Play e su Amazon in un vero formato eBook e non quindi banali pdf come il 90% dei prodotti che trovate in rete. Acquistandolo contribuite allo sviluppo di questo blog e magari troverete un fedele riferimento per questo linguaggio che è e resta uno dei più difficili da padroneggiare fino in fondo.

 




Salva

  13 Responses to “Operatori booleani, questi sconosciuti – it”

  1.  

    grande ste 😉

  2.  

    sei davvero un grande……
    ma… resta di fatto che a parte la fantastica lezione da povero inetto ci ho capito poco, sopratutto perchè provando a inserire
    ” SetBitN(My595.Buffer,10,0); ” oppure ” My595.SetBitN(My595.Buffer,10,0); ” il programma mi da errore dicendomi che non è contemplato nella libreria……..
    Scusa , ma come ti dicevo magari sono duro di comprendonio, ma ti assicuro che leggo le tue lezioni con molta attenzione e vorrei davvero poter sfruttare la tua libreria per lo sketch che ti ho inviato.

    Ti ringrazio ancora per il tuo tempo

    •  

      Ho visto che mi hai mandato lo sketch, ma purtroppo è un periodo lavorativo impegnativo, ho tutta una serie di notti ravvicinate e sono un po’ stanchetto….Appena sono un po’ tranquillo gli do un occhio e vedo. Intanto però: My595.SetBitN(My595.Buffer,10,0); non può funzionare perchè la SetBinN è una funzione generica utilizzabile da tutta la libreria e non fa quindi parte della classe hc595. La SetBitN(My595.Buffer,10,0); dovrebbe invece funzionare. Purtroppo non ho un prototipo hardware pronto per fare le prove, ma ho provato a prendere l’esempio “BasicUsage” che trovi nella libreria, ho inserito la SetBitN(My595.Buffer,10,0); per vedere se mi da qualche problema, ma mi compila tutto al primo colpo. Mi puoi scrivere esattamente che tipo di errore ti da?

  3.  

    lcd_con_led_3_fili_rfid_commentato:75: error: ‘SetBitN’ was not declared in this scope

    SetBitN(My595.Buffer,2,1);

    ^

    exit status 1
    ‘SetBitN’ was not declared in this scope

  4.  

    Nel listato che mi hai mandato manca la #include <Ss_McMajan_Config.h> ….. l’hai messa nel listato? Ho inserito la SetBitN(My595.Buffer,2,1); nel tuo listato verso la linea 75, sostituendo una Set595Pin(0,0,0,0,0,0,0,0,x); e mi compila tutto senza problemi.

  5.  

    ho inserito la #include , e con SetBitN(My595.Buffer,2,1); me lo compila,ma se metto SetBitN(My595.Buffer[1],7,1); mi da questo errore:
    C:\Users\USUL\Documents\Arduino\libraries\Ss_McMajan_Config/Ss_FastArduino.h:31:8: error: initializing argument 1 of ‘void SetBitN(uint8_t*, unsigned int, bool)’ [-fpermissive]

    void SetBitN(uint8_t * ,unsigned int, bool );

    ^

    exit status 1
    invalid conversion from ‘uint8_t {aka unsigned char}’ to ‘uint8_t* {aka unsigned char*}’ [-fpermissive]

    •  

      boooh, a me funziona, faccio fatica a capire perchè lo stesso identico listato a te non lo compila. Potrei suggerirti delle sintassi alternative, anzichè usare SetBitN(My595.Buffer[1],7,1); potresti provare con l’equivalente SetBitN(My595.Buffer+1,7,1); , oppure se non ne vuole sapere puoi provare un SetBitN(My595.Buffer,7+(8*sr),1); dove sr rappresenta il numero di shift register, sempre partendo da zero.

  6.  

    allora l’unica che funziona, e non so perchè, è “SetBitN(My595.Buffer+1,7,1);”.
    ti ringrazio tanto per il tempo che mi hai dedicato…..

  7.  

    dopo varie prove ho visto che se imposto un led1 a 1 e poi un led2 a 1, il led 1 va a zero. come mai non rimane a i finchè non gli dico di andarci io?
    grazie

  8.  

    Ciao, Domanda 1: se io scrivo” lcdwrite(“1”), mi scrive 1 sul display; se scrivo lcdwrite(int o str 0 char) mi scrive quello contenuto nelle memorie. Ho provato la stessa cosa con my595.displaywrite(int….ecc,0) ma non funziona come mai?

    Domanda 2: hai visto il mio sketch, e mi domandavo se c’era il modo di accorciarlo un po, nel senso che se preparassi un array con i codici e uno con i nomi, alla lettura del tag confronterebbe il codice con il nome e lo metterei ( appunto la domanda di prima) nella display write, dando avvio alla procedura di apertura ecc.
    Potresti dirmi se si può e se si mi daresti qualche dritta?

    grazie