Estoy trabajando con un microcontrolador Atmel ATMEGA32U4 - hoja de datos aquí con un cristal de 16 MHz para el reloj del sistema.
Según tengo entendido, este chip tiene un fusible 'Dividir reloj por 8' programado de fábrica, lo que hace que el reloj de mi sistema sea de 2 MHz. (Página 348 de la hoja de datos. CKDIV8 (bit 7) el valor predeterminado es 0, programado).
Me gustaría confirmar la velocidad del reloj de mi sistema, así que escribí un código para generar un pin bajo, retrasar un ciclo de reloj y luego poner el pin alto nuevamente. Medir el tiempo que el pin está bajo debe ser igual a un ciclo de reloj.
Aquí está el código que usé para lograr esto:
//Set PORT E as output
DDRE = 0xFF;
asm volatile("nop\n\t"::);
PORTE |= 1<<2;
asm volatile("nop\n\t"::);
PORTE &=~ (1<<2);
asm volatile("nop\n\t"::);
PORTE |= 1<<2;
asm volatile("nop\n\t"::);
PORTE &=~ (1<<2);
Un 'nop' es igual a un ciclo de reloj, según el manual del conjunto de instrucciones del AVR , página 108.
Con base en esta información, asumiría una instrucción 'nop' para tomar 500 nanosegundos. ¿Es correcta esta suposición? (16 MHz/8 = 2 MHz. = 500ns)
Aquí hay un diagrama de alcance de mis hallazgos:
Parecería que el tiempo de ejecución de un 'nop' es de solo 200 ns (5 MHz). Esperaría que hubiera una sobrecarga adicional usando C para establecer el puerto alto/bajo, por lo que 500 ns es en realidad el tiempo mínimo que esperaría que el pin estuviera bajo. . Pero como puede ver en mis cursores de medición 'a' y 'b', ni siquiera está cerca de 500 ns.
¿Alguien puede explicar qué está pasando?
¿Hay un error en mi método?
¿Me estoy perdiendo algo estúpido 'face palming-ly'? :p ¡
Gracias por cualquier ayuda!
Este es un mal enfoque. Idealmente, mediría una gran cantidad de comandos NOP, digamos, un millón, en lugar de solo uno. Creo que es la primera tontería que puedo encontrar. No me sorprendería mucho si su número cambia justo después de implementar ese cambio.
Además, ¿puedes ver el desmontaje de tu código C? Ni siquiera intentaría juzgar el tiempo sin ver exactamente qué código ensamblador se generó a partir de mi código C. Intentaría trabajar hacia atrás desde el ensamblaje para calcular cuál debería ser el tiempo de ejecución (a través del manual del conjunto de instrucciones que encontró). De esa manera, puede ver si la medición del alcance se desvía un poco o mucho. Un poco podría significar que cometiste un error matemático. Muchas pueden significar que una de sus suposiciones es incorrecta.
Verifiqué varias configuraciones de reloj de un ATtiny45 hace un tiempo con el código C a continuación. Aunque no es perfecto con el bucle while, las rutinas de retardo vienen bastante bien optimizadas con las bibliotecas avr-gcc. Llamo al programa '1kHz.cpp'. Verifique con la hoja de datos qué pin exacto genera la onda de bloque.
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = 0x10;
PORTB = 0x00;
while (1)
{
PORTB = 0x00; _delay_us(500); // pin low, then wait
PORTB = 0x10; _delay_us(500); // pin high, then wait
}
}
El truco con el reloj es compilar el código con la configuración de reloj adecuada. En Linux se ve así, pero Windows debería ser razonablemente similar. Como puede ver, el ATtiny45 tiene un reloj predeterminado de fábrica de 1 MHz.
freq=8000000/8
baud=19200
src=1kHz.cpp
avr=attiny45
port=/dev/ttyUSB1
# Compile
avr-gcc -g -DF_CPU=$freq -Wall -Os -mmcu=$avr -c -o tmp.o $src
# Link
avr-gcc -g -DF_CPU=$freq -Wall -Os -mmcu=$avr -o tmp.elf tmp.o
# Convert to Intel .hex (required for my programmer)
avr-objcopy -j .text -j .data -O ihex tmp.elf tmp.hex
# Program the device
avrdude -p $avr -c stk500v1 -P $port -b $baud -v -U flash:w:tmp.hex
Por cierto, experimentar con la configuración del reloj puede bloquear tu dispositivo, así que ten cuidado con los fusibles que configuras o desactivas. El desbloqueo solo se puede hacer con un programador de alto voltaje (que se puede hacer con un Arduino de repuesto).
Si está haciendo un análisis de tiempo en el nivel de instrucciones individuales, ciertamente debe mirar el desensamblado de su programa, o escribirlo en ensamblador en primer lugar. Luego puede saber las duraciones exactas de la hoja de datos contando los ciclos para cada instrucción:
Sin embargo, si solo desea verificar que la frecuencia del reloj principal es correcta, hay una solución más fácil: simplemente genere una salida de baja frecuencia. (De 0,1 Hz a algunos kHz, dependiendo de lo que use para medirlo, un LED parpadeante y un cronómetro pueden ser suficientes). Puede usar uno de los temporizadores de 16 bits para eso, o simplemente usar un bucle con un tiempo ocupado largo. espera, por ej _delay_us()
. El tiempo dedicado a los saltos del bucle será despreciable.
kevin vermeer
volatile
, GCC aún moverá esas declaraciones de ensamblaje por gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html si tiene la optimización activada (cualquier otra cosa que no sea-O0
).dext0rb
Stefan Paul Noack
out DDRE, 0xFF; nop; sbi PORTB, 2; nop; cbi PORTB, 2; nop; sbi PORTB, 2; nop; cbi PORTB, 2;
que, dado que todas estas son instrucciones de 1 ciclo, el tiempo entre los cambios de salida debe ser de 2 ciclos (uno para elnop
, uno parasbi
/cbi
). sin embargo, su compilador podría optimizar losnop
s o no ser lo suficientemente inteligente como para usarlosbi
ycbi