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!