Estoy desarrollando un pequeño analizador lógico con 7 entradas. Mi dispositivo objetivo es ATmega168
con una frecuencia de reloj de 20MHz. Para detectar cambios lógicos, uso interrupciones de cambio de pin. Ahora estoy tratando de encontrar la frecuencia de muestreo más baja que pueda detectar estos cambios de pin. Determiné un valor mínimo de 5.6 µs (178.5 kHz). Cada señal por debajo de esta velocidad no puedo capturar correctamente.
Mi código está escrito en C (avr-gcc). Mi rutina se ve así:
ISR()
{
pinc = PINC; // char
timestamp_ll = TCNT1L; // char
timestamp_lh = TCNT1H; // char
timestamp_h = timerh; // 2 byte integer
stack_counter++;
}
Mi cambio de señal capturado se encuentra en pinc
. Para localizarlo, tengo un valor de marca de tiempo de 4 bytes.
En la hoja de datos que leí, la rutina de servicio de interrupción toma 5 relojes para saltar y 5 relojes para volver al procedimiento principal. Supongo que cada comando en mi ISR()
toma 1 reloj para ejecutarse; En resumen, debería haber una sobrecarga de 5 + 5 + 5 = 15
relojes. La duración de un reloj debe estar de acuerdo con la velocidad del reloj de 20MHz 1/20000000 = 0.00000005 = 50 ns
. La sobrecarga total en el segundo debe ser a continuación: 15 * 50 ns = 750 ns = 0.75 µs
. Ahora no entiendo por qué no puedo capturar nada por debajo de 5.6 µs. ¿Alguien puede explicar lo que está pasando?
Respuestas:
Hay un par de problemas:
AND
es una instrucción de un reloj,MUL
(multiplicar) toma dos relojes, mientras queLPM
(cargar la memoria del programa) es tres yCALL
es 4. Entonces, con respecto a la ejecución de la instrucción, realmente depende de la instrucción.RETI
instrucciones, el compilador agrega todo tipo de otro código, lo que también lleva tiempo. Por ejemplo, es posible que necesite variables locales que se crean en la pila y se deben quitar, etc. Lo mejor que puede hacer para ver lo que realmente está sucediendo es mirar el desmontaje.Si
x
es el tiempo que lleva reparar su interrupción, entonces la señal B nunca será capturada.Si tomamos su código ISR, lo pegamos en una rutina de rutina ISR (que usé
ISR(PCINT0_vect)
), declaramos todas las variablesvolatile
y compilamos para ATmega168P, el código desmontado se ve de la siguiente manera (consulte la respuesta de @ jipple para obtener más información) antes de llegar al código que "hace algo" ; en otras palabras, el prólogo de su ISR es el siguiente:entonces,
PUSH
x 5,in
x 1,clr
x 1. No es tan malo como los vars de 32 bits de jipple, pero aún así no es nada.Algo de esto es necesario (amplíe la discusión en los comentarios). Obviamente, dado que la rutina ISR puede ocurrir en cualquier momento, debe preseleccionar los registros que usa, a menos que sepa que ningún código donde puede ocurrir una interrupción usa el mismo registro que su rutina de interrupción. Por ejemplo, la siguiente línea en el ISR desmontado:
Está ahí porque todo pasa
r24
: tupinc
se carga allí antes de que vaya a la memoria, etc. Entonces debes tener eso primero.__SREG__
se cargar0
y luego se empuja: si esto pudiera pasar,r24
entonces podría ahorrarse unPUSH
Algunas posibles soluciones:
ISR_NAKED
, gcc no genera un código de prólogo / epílogo, y usted es responsable de guardar los registros que modifique su código, así como de llamarreti
(regresar de una interrupción). Desafortunadamente, no hay manera de utilizar registros de avr-gcc C directamente (obviamente se puede en el montaje), sin embargo, lo que puede hacer es variables se unen a registros específicos con losregister
+asm
palabras clave, como esto:register uint8_t counter asm("r3");
. Si hace eso, para el ISR sabrá qué registros está utilizando en el ISR. El problema es que no hay forma de generarpush
ypop
para guardar los registros usados sin ensamblaje en línea (ver punto 1). Para asegurarse de tener que guardar menos registros, también puede vincular todas las variables que no son ISR a registros específicos, sin embargo, no tiene ningún problema de que gcc use registros para barajar datos hacia y desde la memoria. Esto significa que, a menos que mire el desmontaje, no sabrá qué registros utiliza su código principal. Entonces, si está considerandoISR_NAKED
, también podría escribir el ISR en conjunto.fuente
Hay muchos registros PUSH'ing y POP'ing para apilar antes de que comience su ISR real, que está por encima de los 5 ciclos de reloj que menciona. Eche un vistazo al desmontaje del código generado.
Dependiendo de la cadena de herramientas que use, deshacerse del ensamblaje que nos enumera se realiza de varias maneras. Trabajo en la línea de comandos de Linux y este es el comando que uso (requiere el archivo .elf como entrada):
Eche un vistazo a un fragmento de código que usé recientemente para un ATtiny. Así es como se ve el código C:
Y este es el código de ensamblaje generado para ello:
Para ser honesto, mi rutina C usa un par de variables más que causan todos estos empujones y estallidos, pero entiendes la idea.
La carga de una variable de 32 bits se ve así:
El aumento de una variable de 32 bits en 1 se ve así:
El almacenamiento de una variable de 32 bits se ve así:
Luego, por supuesto, debe reventar los valores anteriores una vez que abandona el ISR:
De acuerdo con el resumen de instrucciones en la hoja de datos, la mayoría de las instrucciones son de ciclo único, pero PUSH y POP son ciclos dobles. ¿Tienes idea de dónde viene la demora?
fuente
avr-objdump -C -d $(src).elf
!avr-objdump
escuchan, se explican brevemente en la hoja de datos en Resumen de instrucciones. En mi opinión, es una buena práctica familiarizarse con los mnemónicos, ya que puede ayudar mucho al depurar su código C.Makefile
: por lo tanto, cada vez que construye su proyecto, también se desmonta automáticamente para que no tenga que pensarlo o recordar cómo hacerlo manualmente.