¿Monitorear ciclos de reloj para código en arduino / AVR?


11

¿Es posible monitorear un bloque de código y determinar el número de ciclos de reloj del procesador que tomó el código en un procesador Atdu Arduino y / o AVR? o, ¿debería más bien monitorear los microsegundos pasados ​​antes y después de ejecutar el código? Nota: No me preocupa tanto el tiempo real (como en cuántos segundos reales pasaron) como "cuántos ciclos de reloj requiere este código de la CPU"

La solución actual que se me ocurre es de time.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

cableado.c agrega:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

Con esta cuenta, podría calcular los ciclos de reloj pasados ​​mediante el monitoreo de microsegundos pasados ​​y luego pasar eso a microsecondsToClockCycles (). Mi pregunta es, ¿hay una mejor manera?

nota al margen: ¿existen buenos recursos para el monitoreo del desempeño del AVR? lmgtfy.com y varias búsquedas en foros no proporcionan resultados obvios, aparte de la exploración de temporizadores

Gracias

Respuestas:


6

El método más simple es hacer que su código levante algún pin antes de que ejecute el código que desea cronometrar, y lo baje una vez que haya terminado de hacer lo que sea. Luego, realice el bucle de código (o use un osciloscopio digital con memoria en modo de disparo único) y solo alcance y luego fije. La longitud del pulso le indica cuánto tiempo tardó en ejecutar la pieza de código más un ciclo de reloj para cambiar el estado del pin (creo que toma un ciclo, no 100% seguro).


Gracias. Sí, puedo ver que esta es probablemente la solución más precisa. Todavía estoy desechando el código que me daría al menos incluso un análisis general del uso del ciclo dentro del código. Voy a usar esto para construir algunas herramientas de prueba y sería bueno establecer mis límites superiores para parámetros como el tiempo de ejecución máximo permitido en función de qué tan eficientemente el código + todo lo relacionado con él se ejecuta en la CPU Atmel actual en uso
cyphunk

4

¿Qué quieres decir con "monitor"?

No debería ser difícil contar los ciclos de reloj para AVR para pequeñas piezas de código de ensamblaje.

También puede configurar un puerto antes de que se ejecute el código y luego reiniciarlo, y monitorearlo con un analizador lógico o un osciloscopio para obtener el tiempo.

Y también puede leer el tiempo de un temporizador de funcionamiento rápido, como usted dice.


Por monitor quiero decir determinar el número de ciclos utilizados por el código. algo así como (nota, el formato del código probablemente será aplanado por el motor de comentarios): relojes = startCountingAtmegaClocks (); para ... {para ... {digitalRead ...}} Serial.print ("número de ciclos utilizados:"); Serial.print (currentCountingAtmegaClocks () - relojes, DEC);
cyphunk

Pero sí, su respuesta es lo que asumí que son mis opciones. Supongo, supongo que si puedo calcular los ciclos de reloj que el ensamblador se llevaría a mano que alguien ha quizá ya escrito algo de código agradable de hacer esto mediante programación
cyphunk

3

Este es un ejemplo para Arduino usando la función clockCyclesPerMicrosecond () para calcular los relojes que han pasado. Este código esperará 4 segundos, luego imprimirá el tiempo transcurrido desde el inicio del programa. Los 3 valores de la izquierda son el tiempo total (microsegundos, milisegundos, ciclos de reloj totales) y los 3 más a la derecha son los tiempos transcurridos:

Salida:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

Estoy seguro de que hay una explicación razonable de por qué los primeros bucles también tenían ciclos de reloj transcurridos más cortos que la mayoría y por qué todos los demás bucles alternan entre dos longitudes de ciclos de reloj.

Código:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

Nota al margen: si elimina el retraso de 4 segundos, comenzará a ver los efectos de Serial.print () mucho más claramente. Tenga en cuenta que aquí se comparan 2 carreras. Solo he incluido 4 muestras cercanas entre sí de sus registros respectivos.

Ejecución 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

Ejecución 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

El tiempo transcurrido aumenta sobre el tiempo total de ejecución. Después de un segundo, los relojes aumentan en promedio de 40k a 44k. Esto sucede constantemente unos pocos milisegundos después de 1 segundo y los relojes transcurridos permanecen alrededor de 44k durante al menos los siguientes 10 segundos (no lo he probado más). Es por eso que el monitoreo es útil o necesario. ¿Quizás la disminución de la eficiencia tiene que ver con la configuración o los errores en serie? O tal vez el código no está usando la memoria correctamente y tiene una fuga que afecta el rendimiento, etc.


muchos años después, todavía me gustaría algo que muestre los relojes con mayor precisión con el código (como se le aplica a un osciloscopio). En este momento trato de determinar la cantidad de ciclos de reloj necesarios para un digitalWrite () en 16MHZ y 8MHZ. En 16MHZ obtengo 8us / 64clk. Pero en 8MHZ obtengo 0us / 0clk.
cyphunk

1

Dado que cada línea de código agregada a su fuente tendrá un impacto en el rendimiento y podría cambiar las optimizaciones aplicadas. Los cambios deben ser el mínimo requerido para realizar la tarea.

Acabo de encontrar un complemento de Atmel Studio llamado "Depurador de archivos de ensamblaje anotado". http://www.atmel.com/webdoc/aafdebugger/pr01.html Parece que pasa por el lenguaje ensamblador real generado, aunque probablemente tedioso le mostrará exactamente lo que está sucediendo. Es posible que aún tenga que decodificar cuántos ciclos se necesitan para cada instrucción, pero se acercaría mucho más que algunas de las otras opciones publicadas.

Para aquellos que no saben en la carpeta Salida de su proyecto, hay un archivo con una extensión LSS. Este archivo contiene todo su código fuente original como comentarios y debajo de cada línea está el lenguaje ensamblador que se generó en función de esa línea de código. La generación del archivo LSS se puede desactivar, así que verifique la siguiente configuración.

Propiedades del proyecto | Cadena de herramientas | AVR / GNU Común | OutputFiles

Casilla de verificación ".lss (Generar archivo lss)


1

Puede usar uno de los temporizadores integrados. Configura todo para preescalador = 1 y TCNT = 0 antes del bloque. Luego active el temporizador en la línea antes del bloque y desactívelo en la línea después del bloque. El TCNT ahora mantendrá el número de ciclos que tomó el bloque, menos los ciclos fijos para el código de activación y desactivación.

Tenga en cuenta que el TNCT se desbordará después de 65535 ciclos de reloj en un temporizador de 16 bits. Puede usar el indicador de desbordamiento para duplicar el tiempo de ejecución. Si aún necesita más tiempo, puede usar un preescalador, pero obtendrá menos resolución.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.