mercoledì 8 dicembre 2021

Advanced.arduino.copybit

Copia di bit per bit

Copiare dati un bit alla volta significa leggere un bit da una variabile (es:myBitDataOrg) salvarlo in una variabile (es:myBit) per poi scrivere myBit nella variabile myBitDataDest. Fare ciò in C/C++ ha poco senso poiché basta usare l'operatore di assegnamento (=) tra le due variabili per avere lo stesso risultato della copia bit per bit. Tuttavia sperimentalmente è utile scrivere l'algoritmo perché ci si può trovare nella condizione di dovere leggere bit per bit con un digitalRead(), questo vuole dire che non sappiamo se digitalRead(pin) restituisce 1 o 0 e lo scopriamo solo dopo che digitalRead(pin) è stata eseguita. 

Un tipico esempio di lettura bit per bit lo possiamo vedere nel metodo HX711::read() della libreria per HX711, qui. Nello specifico la funzione uint8_t shiftInSlow(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder) legge un bit alla volta e lo salva nella variabile value per poi restituirlo al chiamante.

Qui di seguito semplifico la funzione shiftInSlow per vedere come legge un bit per volta:

digitalWrite(clockPin, HIGH);   // alza clockPin
byte bitValue = digitalRead(dataPin); // legge lo stato di dataPin
digitalWrite(clockPin, LOW);  // abbassa clockPin 

Quanto sopra ripetuto in ciclo 24 volte legge dall'HX711 i 24 bit della ultima conversione. Attenzione che questo non è il modo corretto per leggere bit per bit dal chip HX711 e quanto meno durante la lettura dei 24 bit dobbiamo prima spegnere gli interrupt globali. 

Detto questo, noi non abbiamo il chip su banco di lavoro e allora creiamo la funzione hxread() la quale ci restituisce un bit ogni volta che la chiamiamo. La funzione in questione preleva un bit per volta da una variabile di origine di nome appunto myDataOrg di tipo senza segno grande 32 bit (uint32__t).

uint32_t myDataOrg = 157690;
int8_t bitPos = 23;  // nota il tipo con segno da 8 bit

uint8_t hxread() {
    uint8_t bit = (((myDataOrg) >> (bitPos)) & 1);
    bitPos--;
    if (bitPos < 0 )
        bitPos = 23;

    return bit;
}

Provate a richiamarla 24 volte all'interno della funzione setup() e ad ogni iterazione stampate il valore resituito ad esempio così:

void setup() {
    Serial.begin(9600);
    for (int i=0; i<24; i++)
        Serial.print( hxread() );
    Serial.println("");
}

Nel monitor seriale dovreste vedere stampato:


000000100110011111111010

  

La stessa sequenza di seguito, ma separata da ' ogni 4 bit, così sarà più facile contarli.

 

0000'0010'0110'0111'1111'1010

 

Contandoli in effetti sono 24 bit, vedendoli così in binario non siamo capaci di esprimere il valore in decimale, ma siamo avvantaggiati poiché quella in binario è la rappresentazione (in binario appunto) del valore decimale 157690.

 

Questo esempio già ci permette di stampare la rappresentazione binaria di qualunque numero grande 24 bit contenuto nella variabile myDataOrg. Ricordiamoci, il massimo valore che possiamo assegnare alla variabile myDataOrg è 16777215, che in binario equivale ad accendere tutti i 24 bit.

 

Quella binaria per noi è solo una rappresentazione, ma se potessimo guardare all'interno della memoria ram (composta in questo caso da 4 celle, grande ogni una 1 byte) troveremo la stessa sequenza di bit ad eccezione del fatto che 4 celle da 1 byte fanno 32 bit per cui gli 8 bit a sinistra avranno valore 0. 

 

Adesso grazie alla nostra funzione hxread() possiamo provare a copiare ogni bit nella variabile di destinazione myDataDest per poi stamparne il valore. Se il nostro algoritmo è valido la stampa di myDataDest dove essere 157690, diversamente il nostro algoritmo non è corretto. Allora vediamolo questo algoritmo sempre all'interno della funzione setup():

 

uint32_t myDataOrg = 157690;
int8_t bitPos = 23;  // nota il tipo con segno da 8 bit
uint8_t hxread() {
    uint8_t bit = (((myDataOrg) >> (bitPos)) & 1);
    bitPos--;
    if (bitPos < 0 )
        bitPos = 23;
    return bit;
}
void setup() {
    Serial.begin(9600);
    uint32_t myDataDest = 0; // fondamentale che myDataDest deve avere valore 0 
    uint8_t data = 0;
    for (int i = 24; i > 0; i--) {
        data = hxread();
        Serial.print( data );
        myDataDest |= ((uint32_t)data << (i-1));  // occhio al cast (uin32_t)
    }
    Serial.println("");
    Serial.println(myDataDest);  // dovrà stampare 157690
}

void loop() {
    // empty loop 
} 
 

Nel caso in cui la funzione hxread() dovesse restiuire i bit leggendoli da destra verso sinistra cioè partendo dal bit meno significativo LSB, anche noi nel copiarli in myDataDest dobremmo iniziare a scrivere dal bit più a destra e via via salire verso il 23 esimo bit.

 

In merito al commento "// occhio al cast... dobbiamo dire che è necessario con architettura AVR, mentre ad esempio su ESP dovrebbe funzionare anche senza il cast esplicito. 

 

L'inganno sta nel fatto che, se la variabile data è grande solo 8 bit, come è possibile accendere il bit 23? infatti, ma c'è da fare il conto con la promozione del tipo di dato attuata dal compilatore in base alla architettura. Ad esempio sul PC il cast esplicito non serve e quindi ci pensa il compilatore a promuovere il risultato di (data << (i - 1)) ad un tipo di dato sufficientemente capiente, cioè uno di quelli che almeno ha 24 bit che di default è il tipo long. Su architettura AVR la promozione del risultato è al massimo il tipo int.

 

Quanto qui trattato ci mostra che anche senza hardware (in attesa che arrivi) possiamo portarci avanti anziché rimanere con le mani in mano.


Licenza Creative Commons
Quest'opera è distribuita con Licenza Creative Commons Attribuzione - Condividi allo stesso modo 4.0 Internazionale

Nessun commento:

Posta un commento