Ce n'est pas bizarre. C'est à quoi ressemble le code MCU normal.
Ce que vous avez ici est un exemple du concept de périphériques mappés en mémoire . Fondamentalement, le matériel MCU a des emplacements spéciaux dans l'espace d'adressage SRAM du MCU qui lui est affecté. Si vous écrivez à ces adresses, les bits de l'octet écrit à l'adresse n contrôlent le comportement du périphérique m .
Fondamentalement, certaines banques de mémoire ont littéralement de petits câbles allant de la cellule SRAM au matériel. Si vous écrivez un "1" à ce bit dans cet octet, il définit cette cellule SRAM à un niveau logique élevé, qui allume ensuite une partie du matériel.
Si vous regardez dans les en-têtes du MCU, il existe de grands grands tableaux de mappages d'adresses de mots clés <->. C'est ainsi que des choses comme TCCR1B
etc ... sont résolues au moment de la compilation.
Ce mécanisme de mappage de la mémoire est extrêmement largement utilisé dans les MCU. Le MCU ATmega de l'arduino l'utilise, tout comme les séries MCU PIC, ARM, MSP430, STM32 et STM8, ainsi que de nombreux MCU que je ne connais pas immédiatement.
Le code Arduino est le truc bizarre, avec des fonctions qui accèdent indirectement aux registres de contrôle MCU. Bien que ce soit un peu plus "joli", il est également beaucoup plus lent et utilise beaucoup plus d'espace programme.
Les mystérieuses constantes sont toutes décrites en détail dans la fiche technique ATmega328P , que vous devriez vraiment lire si vous êtes intéressé à faire autre chose que de basculer occasionnellement des broches sur un arduino.
Sélectionnez des extraits de la fiche technique liée ci-dessus:
Ainsi, par exemple, TIMSK1 |= (1 << TOIE1);
définit le bit TOIE1
dans TIMSK1
. Ceci est réalisé en déplaçant le binaire 1 ( 0b00000001
) vers la gauche par des TOIE1
bits, en TOIE1
étant défini dans un fichier d'en-tête comme 0. Ceci est ensuite OR au niveau du bit dans la valeur actuelle de TIMSK1
, ce qui place effectivement ce bit à un niveau élevé.
En regardant la documentation pour le bit 0 de TIMSK1
, nous pouvons voir qu'il est décrit comme
Lorsque ce bit est écrit sur un et que le drapeau I dans le registre d'état est activé (interruptions activées globalement), l'interruption de dépassement de temporisation / compteur1 est activée. Le vecteur d'interruption correspondant (voir ”Interruptions” à la page 57) est exécuté lorsque l'indicateur TOV1, situé dans TIFR1, est défini.
Toutes les autres lignes doivent être interprétées de la même manière.
Quelques notes:
Vous pouvez également voir des choses comme TIMSK1 |= _BV(TOIE1);
. _BV()
est une macro couramment utilisée à l' origine de l' implémentation AVC libc . _BV(TOIE1)
est fonctionnellement identique à (1 << TOIE1)
, avec l'avantage d'une meilleure lisibilité.
Vous pouvez également voir des lignes telles que: TIMSK1 &= ~(1 << TOIE1);
ou TIMSK1 &= ~_BV(TOIE1);
. Cela a la fonction inverse de TIMSK1 |= _BV(TOIE1);
, en ce qu'elle met à l' arrêt le bit TOIE1
dans TIMSK1
. Ceci est obtenu en prenant le masque de bits produit par _BV(TOIE1)
, en effectuant une opération NON au niveau du bit sur celui-ci ( ~
), puis en effectuant un AND TIMSK1
par cette valeur NOTée (qui est 0b11111110).
Notez que dans tous ces cas, la valeur de choses comme (1 << TOIE1)
ou _BV(TOIE1)
sont entièrement résolues au moment de la compilation , de sorte qu'elles se réduisent fonctionnellement à une simple constante et ne prennent donc aucun temps d'exécution pour calculer au moment de l'exécution.
Un code correctement écrit comportera généralement des commentaires en ligne avec le code qui détaillent les tâches assignées aux registres. Voici une routine soft-SPI assez simple que j'ai écrite récemment:
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
est le registre qui contrôle la valeur des broches de sortie dans PORTC
l'ATmega328P. PINC
est le registre où les valeurs d' entrée de PORTC
sont disponibles. Fondamentalement, des choses comme celles-ci se produisent en interne lorsque vous utilisez les fonctions digitalWrite
ou digitalRead
. Cependant, il existe une opération de recherche qui convertit les "numéros de broche" arduino en numéros de broche matériels réels, ce qui prend quelque part dans le domaine de 50 cycles d'horloge. Comme vous pouvez probablement le deviner, si vous essayez d'aller vite, gaspiller 50 cycles d'horloge sur une opération qui ne devrait nécessiter qu'un seul est un peu ridicule.
La fonction ci-dessus prend probablement quelque part dans le domaine de 100-200 cycles d'horloge pour transférer 8 bits. Cela implique 24 écritures et 8 lectures. C'est beaucoup, beaucoup plus rapide que d'utiliser les digital{stuff}
fonctions.