Questo non è strano. È come appare il normale codice MCU.
Quello che hai qui è un esempio del concetto di periferiche mappate in memoria . Fondamentalmente, l'hardware MCU ha posizioni speciali nello spazio degli indirizzi SRAM dell'MCU assegnato ad esso. Se si scrive a questi indirizzi, i bit del byte scritti sull'indirizzo n controllano il comportamento della periferica m .
Fondamentalmente, alcuni banchi di memoria hanno letteralmente dei piccoli fili che vanno dalla cella SRAM all'hardware. Se si scrive un "1" su questo bit in quel byte, imposta questa cella SRAM su un valore logico alto, che quindi attiva una parte dell'hardware.
Se osservi le intestazioni dell'MCU, ci sono grandi tabelle di mappature di indirizzi di parole chiave <->. Ecco come cose come TCCR1B
etc ... vengono risolte in fase di compilazione.
Questo meccanismo di mappatura della memoria è ampiamente utilizzato nelle MCU. L'ATMega MCU nell'Arduino lo usa, così come le serie MCU PIC, ARM, MSP430, STM32 e STM8, così come molte MCU che non conosco immediatamente.
Il codice Arduino è roba strana, con funzioni che accedono indirettamente al controllo MCU. Anche se è un po 'più "bello", è anche molto più lento e utilizza molto più spazio del programma.
Le costanti misteriose sono tutte descritte in dettaglio nel foglio dati ATmega328P , che dovresti davvero leggere se sei interessato a fare qualcosa di più che attivare e disattivare i pin su un arduino.
Seleziona estratti dal foglio dati collegato sopra:
Quindi, ad esempio, TIMSK1 |= (1 << TOIE1);
imposta il bit TOIE1
in TIMSK1
. Ciò si ottiene spostando i binari 1 ( 0b00000001
) a sinistra di TOIE1
bit, TOIE1
definendoli in un file di intestazione come 0. Questo viene quindi OR bit a bit nel valore corrente di TIMSK1
, che di fatto imposta questo bit alto.
Guardando la documentazione per il bit 0 di TIMSK1
, possiamo vedere che è descritta come
Quando questo bit viene scritto su uno e viene impostato il flag I nel registro di stato (interrupt abilitati a livello globale), viene attivato l'interrupt di overflow Timer / Counter1. Il vettore di interrupt corrispondente (vedere "Interrupt" a pagina 57) viene eseguito quando viene impostato il flag TOV1, situato in TIFR1.
Tutte le altre righe dovrebbero essere interpretate allo stesso modo.
Alcune note:
Potresti anche vedere cose come TIMSK1 |= _BV(TOIE1);
. _BV()
è una macro comunemente usata originariamente dall'implementazione di libc AVR . _BV(TOIE1)
è funzionalmente identico a (1 << TOIE1)
, con il vantaggio di una migliore leggibilità.
Inoltre, potresti anche vedere righe come: TIMSK1 &= ~(1 << TOIE1);
o TIMSK1 &= ~_BV(TOIE1);
. Questo ha la funzione opposta di TIMSK1 |= _BV(TOIE1);
, in quanto disinserisce il bit TOIE1
in TIMSK1
. Questo si ottiene prendendo la maschera di bit prodotta da _BV(TOIE1)
, eseguendo un'operazione NOT bit a bit su di essa ( ~
), e quindi ANDing TIMSK1
con questo valore NOTed (che è 0b11111110).
Si noti che in tutti questi casi, il valore di cose come (1 << TOIE1)
o _BV(TOIE1)
sono completamente risolte al momento della compilazione , quindi si riducono funzionalmente a una costante semplice e quindi non richiedono tempi di esecuzione per il calcolo in fase di esecuzione.
Il codice scritto correttamente avrà generalmente dei commenti in linea con il codice che descrivono in dettaglio cosa devono fare i registri. Ecco una routine soft-SPI abbastanza semplice che ho scritto di recente:
uint8_t transactByteADC(uint8_t outByte)
{
// Transfers one byte to the ADC, and receives one byte at the same time
// does nothing with the chip-select
// MSB first, data clocked on the rising edge
uint8_t loopCnt;
uint8_t retDat = 0;
for (loopCnt = 0; loopCnt < 8; loopCnt++)
{
if (outByte & 0x80) // if current bit is high
PORTC |= _BV(ADC_MOSI); // set data line
else
PORTC &= ~(_BV(ADC_MOSI)); // else unset it
outByte <<= 1; // and shift the output data over for the next iteration
retDat <<= 1; // shift over the data read back
PORTC |= _BV(ADC_SCK); // Set the clock high
if (PINC & _BV(ADC_MISO)) // sample the input line
retDat |= 0x01; // and set the bit in the retval if the input is high
PORTC &= ~(_BV(ADC_SCK)); // set clock low
}
return retDat;
}
PORTC
è il registro che controlla il valore dei pin di uscita all'interno PORTC
di ATmega328P. PINC
è il registro in cui sono disponibili i valori di input di PORTC
. Fondamentalmente, cose del genere sono ciò che accade internamente quando si usano le funzioni digitalWrite
o digitalRead
. Tuttavia, esiste un'operazione di ricerca che converte i "numeri di pin" arduino in numeri di pin hardware effettivi, che porta da qualche parte nel regno di 50 cicli di clock. Come probabilmente puoi immaginare, se stai cercando di andare veloce, sprecare 50 cicli di clock in un'operazione che dovrebbe richiedere solo 1 è un po 'ridicolo.
La funzione sopra probabilmente porta da qualche parte nel regno di 100-200 cicli di clock per trasferire 8 bit. Ciò comporta 24 pin-write e 8 letture. Questo è molte, molte volte più veloce dell'uso delle digital{stuff}
funzioni.