Je développe un petit analyseur logique avec 7 entrées. Mon appareil cible est un ATmega168
avec une fréquence d'horloge de 20 MHz. Pour détecter les changements logiques, j'utilise des interruptions de changement de broche. Maintenant, j'essaie de trouver le taux d'échantillonnage le plus bas que je puisse détecter ces changements de broches. J'ai déterminé une valeur minimale de 5,6 µs (178,5 kHz). Chaque signal en dessous de ce taux, je ne peux pas capturer correctement.
Mon code est écrit en C (avr-gcc). Ma routine ressemble à:
ISR()
{
pinc = PINC; // char
timestamp_ll = TCNT1L; // char
timestamp_lh = TCNT1H; // char
timestamp_h = timerh; // 2 byte integer
stack_counter++;
}
Mon changement de signal capturé est situé à pinc
. Pour le localiser, j'ai une valeur d'horodatage longue de 4 octets.
Dans la fiche technique, j'ai lu que la routine de service d'interruption prend 5 horloges pour sauter et 5 horloges pour revenir à la procédure principale. Je suppose que chaque commande de mon ISR()
prend 1 horloge à exécuter; Donc, en somme, il devrait y avoir une surcharge d' 5 + 5 + 5 = 15
horloges. La durée d'une horloge doit être fonction de la fréquence d'horloge de 20 MHz 1/20000000 = 0.00000005 = 50 ns
. Le total des coûts indirects en quelques secondes devrait être alors: 15 * 50 ns = 750 ns = 0.75 µs
. Maintenant, je ne comprends pas pourquoi je ne peux pas capturer quoi que ce soit en dessous de 5,6 µs. Quelqu'un peut-il expliquer ce qui se passe?
Réponses:
Il y a quelques problèmes:
AND
est une instruction à une horloge,MUL
(multiplier) prend deux horloges, tandis queLPM
(charger la mémoire du programme) est de trois, etCALL
est de 4. Donc, en ce qui concerne l'exécution de l'instruction, cela dépend vraiment de l'instruction.RETI
instructions, le compilateur ajoute toutes sortes d'autres codes, ce qui prend également du temps. Par exemple, vous pourriez avoir besoin de variables locales qui sont créées sur la pile et doivent être sautées, etc. La meilleure chose à faire pour voir ce qui se passe réellement est de regarder le désassemblage.Si
x
c'est le temps qu'il faut pour réparer votre interruption, le signal B ne sera jamais capturé.Si nous prenons votre code ISR, le collons dans une routine ISR (j'ai utilisé
ISR(PCINT0_vect)
), déclarons toutes les variablesvolatile
et compilons pour ATmega168P, le code désassemblé se présente comme suit (voir la réponse de @ jipple pour plus d'informations) avant d'arriver au code qui "fait quelque chose" ; en d'autres termes, le prologue de votre ISR est le suivant:donc,
PUSH
x 5,in
x 1,clr
x 1. Pas aussi mauvais que les vars 32 bits de jipple, mais toujours pas rien.Une partie de cela est nécessaire (développez la discussion dans les commentaires). De toute évidence, puisque la routine ISR peut se produire à tout moment, elle doit préconfigurer les registres qu'elle utilise, sauf si vous savez qu'aucun code où une interruption peut se produire utilise le même registre que votre routine d'interruption. Par exemple, la ligne suivante dans l'ISR démonté:
Est là parce que tout passe
r24
: votrepinc
est chargé là-bas avant d'être mis en mémoire, etc. Donc, vous devez d'abord l'avoir.__SREG__
est chargé dansr0
puis poussé: si cela peut passer,r24
vous pouvez vous épargner unPUSH
Quelques solutions possibles:
ISR_NAKED
, gcc ne génère pas de code prologue / épilogue, et vous êtes responsable de sauvegarder tous les registres modifiés par votre code, ainsi que d'appelerreti
(retour d'une interruption). Malheureusement, il n'y a aucun moyen d'utiliser des registres à C gcc-AVR directement (vous pouvez évidemment dans l' assemblage), cependant, ce que vous pouvez faire est de lier les variables à registres spécifiques avec lesregister
+asm
mots - clés, comme celui - ci:register uint8_t counter asm("r3");
. Si vous faites cela, pour l'ISR, vous saurez quels registres vous utilisez dans l'ISR. Le problème est alors qu'il n'y a aucun moyen de générerpush
etpop
pour sauvegarder les registres utilisés sans assemblage en ligne (cf. point 1). Pour éviter d'avoir à enregistrer moins de registres, vous pouvez également lier toutes les variables non ISR à des registres spécifiques, cependant, vous ne rencontrez pas de problème lorsque gcc utilise des registres pour mélanger les données vers et depuis la mémoire. Cela signifie qu'à moins de regarder le démontage, vous ne saurez pas quels registres votre code principal utilise. Donc, si vous envisagezISR_NAKED
, vous pourriez aussi bien écrire l'ISR dans l'assemblage.la source
Il y a beaucoup de registres PUSH'ing et POP'ing à empiler avant le démarrage de votre ISR, c'est-à-dire en plus des 5 cycles d'horloge que vous mentionnez. Jetez un oeil au démontage du code généré.
En fonction de la chaîne d'outils que vous utilisez, le vidage de l'assemblage nous énumère de différentes manières. Je travaille sur la ligne de commande Linux et voici la commande que j'utilise (elle nécessite le fichier .elf en entrée):
Jetez un oeil à un fragment de code que j'ai récemment utilisé pour un ATtiny. Voici à quoi ressemble le code C:
Et voici le code d'assembly généré pour cela:
Pour être honnête, ma routine C utilise quelques variables supplémentaires qui provoquent tous ces push'es et pop, mais vous avez l'idée.
Le chargement d'une variable 32 bits ressemble à ceci:
L'augmentation d'une variable 32 bits de 1 ressemble à ceci:
Le stockage d'une variable 32 bits ressemble à ceci:
Ensuite, bien sûr, vous devez faire apparaître les anciennes valeurs une fois que vous quittez l'ISR:
Selon le résumé des instructions de la fiche technique, la plupart des instructions sont à cycle unique, mais PUSH et POP sont à cycle double. Vous avez l'idée d'où vient le retard?
la source
avr-objdump -C -d $(src).elf
!avr-objdump
crachent, elles sont brièvement expliquées dans la fiche technique sous Résumé des instructions. À mon avis, c'est une bonne pratique de se familiariser avec les mnémoniques car cela peut beaucoup aider lors du débogage de votre code C.Makefile
: donc chaque fois que vous construisez votre projet, il est également démonté automatiquement afin que vous n'ayez pas à y penser ou à vous rappeler comment le faire manuellement.