Quelqu'un pourrait-il expliquer ce code étrange, utilisé pour configurer les minuteries?

10

En regardant les croquis que d'autres personnes ont écrits, je tombe parfois sur du code qui ressemble un peu à ceci:

TCCR1A = 0;
TCCR1B = 0;

TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);

Tout ce que je sais, c'est que ça a quelque chose à voir avec le timing / les minuteries (je pense). Comment puis-je déchiffrer et créer un code comme celui-ci? Quels sont TCCR1A, TCCR1B, TCNT1, CS12, TIMSK1et TOIE1?

Le gars avec le chapeau
la source
Je ne sais pas assez pour répondre, mais: electronics.stackexchange.com/questions/92350/… , forum.arduino.cc/index.php?topic=134602.0 et stackoverflow.com/questions/9475482/… . Je ne sais pas si vous les avez déjà vus.
Anonymous Penguin
1
Téléchargez la fiche technique «complète» de votre appareil sur le site Web d'Atmel et lisez les chapitres sur les minuteries. La fiche technique est étonnamment bonne à lire à mon avis.
jippie

Réponses:

15

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 TCCR1Betc ... 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:

entrez la description de l'image ici entrez la description de l'image ici entrez la description de l'image ici

Ainsi, par exemple, TIMSK1 |= (1 << TOIE1);définit le bit TOIE1dans TIMSK1. Ceci est réalisé en déplaçant le binaire 1 ( 0b00000001) vers la gauche par des TOIE1bits, 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 TOIE1dans 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 TIMSK1par 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;
}

PORTCest le registre qui contrôle la valeur des broches de sortie dans PORTCl'ATmega328P. PINCest le registre où les valeurs d' entrée de PORTCsont disponibles. Fondamentalement, des choses comme celles-ci se produisent en interne lorsque vous utilisez les fonctions digitalWriteou 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.

Connor Wolf
la source
Notez que ce code devrait également fonctionner avec Atmega32u4 (utilisé dans Leonardo) car il contient plus de temporisations que ATmega328P.
jfpoilpret
1
@Ricardo - Probablement 90% + du petit code embarqué MCU utilise la manipulation directe du registre. Faire des choses avec des fonctions utilitaires indirectes n'est pas du tout le mode commun de manipulation des E / S. Il existe des boîtes à outils pour abstraire le contrôle matériel (The Atmel ASF, par exemple), mais cela est généralement écrit pour compiler le plus possible afin de réduire le temps d'exécution, et nécessite presque toujours de comprendre les périphériques en lisant les fiches techniques.
Connor Wolf
1
Fondamentalement, les choses arduino, en disant "voici des fonctions qui font X", sans vraiment se soucier de faire référence à la documentation réelle ou à la façon dont le matériel fait les choses, ce n'est pas normal. Je comprends que sa valeur est un outil d'introduction, mais à l'exception du prototypage rapide, il n'est pas vraiment fait dans des environnements professionnels réels.
Connor Wolf
1
Pour être clair, ce qui rend le code arduino inhabituel pour le micrologiciel MCU intégré n'est pas unique au code arduino, c'est une fonction de l'approche globale. Fondamentalement, une fois que vous avez une bonne compréhension du MCU réel , faire les choses correctement (par exemple en utilisant directement les registres matériels) prend peu ou pas de temps supplémentaire. En tant que tel, si vous voulez apprendre le vrai développement de MCU, il vaut mieux s'asseoir et comprendre ce que fait réellement votre MCU , plutôt que de s'appuyer sur l' abstraction de quelqu'un d' autre , qui a tendance à être fuyante.
Connor Wolf
1
Notez que je peux être un peu cynique ici, mais beaucoup de comportements que je vois dans la communauté Arduino programment des anti-patterns. Je vois beaucoup de programmation "copier-coller", le traitement des bibliothèques comme des boîtes noires, et juste de mauvaises pratiques générales de conception dans la communauté en général. Bien sûr, je suis assez actif sur EE.stackexchange, donc j'ai peut-être une vue quelque peu oblique, car j'ai quelques outils de modérateur, et en tant que tel, je vois beaucoup de questions fermées. Il y a certainement un biais dans les questions Arduino que j'ai vues là-bas vers "dites-moi ce que C&P doit corriger", plutôt que "pourquoi cela ne fonctionne-t-il pas".
Connor Wolf
3

TCCR1A est le registre de contrôle du temporisateur / compteur 1 A

TCCR1B est le registre de commande du temporisateur / compteur 1 B

TCNT1 est la valeur du compteur du temporisateur / compteur 1

CS12 est le 3e bit de sélection d'horloge pour le temporisateur / compteur 1

TIMSK1 est le registre de masque d'interruption du temporisateur / compteur 1

TOIE1 est l'activation de l'interruption de dépassement du temporisateur / compteur 1

Ainsi, le code active le temporisateur / compteur 1 à 62,5 kHz et définit la valeur à 34286. Ensuite, il active l'interruption de débordement, donc lorsqu'il atteindra 65535, il déclenchera la fonction d'interruption, très probablement étiquetée comme ISR(timer0_overflow_vect)

Le docteur
la source
1

CS12 a une valeur de 2 car il représente le bit 2 du registre TCCR1B.

(1 << CS12) prend la valeur 1 (0b00000001) et la décale 2 fois vers la gauche pour obtenir (0b00000100). L'ordre des opérations dicte que les choses dans () se produisent en premier, donc cela se fait avant que le "| =" ne soit évalué.

(1 << CS10) prend la valeur 1 (0b00000001) et la décale vers la gauche 0 fois pour obtenir (0b00000001). L'ordre des opérations dicte que les choses dans () se produisent en premier, donc cela se fait avant que le "| =" ne soit évalué.

Alors maintenant, nous obtenons TCCR1B | = 0b00000101, qui est le même que TCCR1B = TCCR1B | 0b00000101.

Depuis "|" est "OU", tous les bits autres que CS12 dans TCCR1B ne sont pas affectés.

VENKATESAN
la source