Traté de compilar el siguiente código C:
period = TCNT0L;
period |= ((unsigned int)TCNT0H<<8);
El código ensamblador que obtengo es el siguiente:
period = TCNT0L;
d2: 22 b7 in r18, 0x32 ; 50
d4: 30 e0 ldi r19, 0x00 ; 0
d6: 30 93 87 00 sts 0x0087, r19
da: 20 93 86 00 sts 0x0086, r18
period |= ((unsigned int)TCNT0H<<8);
de: 44 b3 in r20, 0x14 ; 20
e0: 94 2f mov r25, r20
e2: 80 e0 ldi r24, 0x00 ; 0
e4: 82 2b or r24, r18
e6: 93 2b or r25, r19
e8: 90 93 87 00 sts 0x0087, r25
ec: 80 93 86 00 sts 0x0086, r24
Entonces, en lugar de 4 instrucciones, ¡tiene 11!
Traté de elegir las opciones de optimización O1, O2, O3 y Os. El resultado es el mismo (excepto que la O3
opción optimizó este código).
Podría escribir el código fuente de la siguiente manera:
period = TCNT0L | ((unsigned int)TCNT0H<<8);
Obtendré un código más pequeño, pero aún no óptimo:
de: 22 b7 in r18, 0x32 ; 50
e0: 34 b3 in r19, 0x14 ; 20
e2: 93 2f mov r25, r19
e4: 80 e0 ldi r24, 0x00 ; 0
e6: 82 2b or r24, r18
e8: 90 93 87 00 sts 0x0087, r25
ec: 80 93 86 00 sts 0x0086, r24
Sin embargo, ya no tendré garantía de que se accederá primero al byte inferior (este es un requisito esencial para mantener la lectura correcta de 16 bits). Y aún así el código tiene muchas instrucciones adicionales innecesarias.
¿Puedo cambiar las opciones del compilador y/o cambiar el código fuente para mejorarlo? Evitaría ir al ensamblador.
ACTUALIZACIÓN1:
Probé el código que sugirió @caveman:
((unsigned char*)(&period))[0] = TCNT0L;
((unsigned char*)(&period))[1] = TCNT0H;
Pero el resultado tampoco es muy bueno:
((unsigned char*)(&period))[0] = TCNT0L;
dc: 82 b7 in r24, 0x32 ; 50
de: e6 e8 ldi r30, 0x86 ; 134
e0: f0 e0 ldi r31, 0x00 ; 0
e2: 80 83 st Z, r24
((unsigned char*)(&period))[1] = TCNT0H;
e4: 84 b3 in r24, 0x14 ; 20
e6: 81 83 std Z+1, r24 ; 0x01
Un método es usar cargas directas a las mitades del período. Si bien esto parece complicado en C, generalmente generará un ensamblaje muy ajustado, es decir, 2 cargas y 2 tiendas.
((uint8_t*)(&period))[0] = TCNT0L;
((uint8_t*)(&period))[1] = TCNT0H;
A veces, usar la matriz matemática puede causar problemas, por lo que puede intentar esto:
*((uint8_t*)(&period)) = TCNT0L;
*((uint8_t*)(&period) + 1) = TCNT0H;
Esto realmente produce un código óptimo. Mira cómo se usan 12 bytes.
((unsigned char*)(&period))[0] = TCNT0L;
dc: 82 b7 in r24, 0x32 ; 50
de: e6 e8 ldi r30, 0x86 ; 134
e0: f0 e0 ldi r31, 0x00 ; 0
e2: 80 83 st Z, r24
((unsigned char*)(&period))[1] = TCNT0H;
e4: 84 b3 in r24, 0x14 ; 20
e6: 81 83 std Z+1, r24 ; 0x01
Si hizo esto con ensamblaje, probablemente parecería mejor hacerlo así. También es de 12 bytes, por lo que son equivalentes.
dc: 82 b7 in r24, 0x32 ; 50
de: 80 93 86 00 sts 0x0086, r24
e2: 84 b3 in r24, 0x14 ; 20
e4: 80 93 87 00 sts 0x0087, r24
Por supuesto, cuando digo "equivalente", me refiero al tamaño del código. Si el tiempo es más importante, entonces hay que mirar los ciclos. En este caso parece que la versión del ensamblador es de 6 ciclos y la versión del compilador es de 8 ciclos.
En mi avr-gcc 5.4.0 simple period = TCNT1;
para attiny841 parece emitir el código así:
in r24,0x2c
in r25,0x2d
sts 0x0110,r25
sts 0x010f,r24
Parece que el compilador ya conoce la forma en que se debe acceder a los registros de 16 bits y, por lo tanto, el código como el anterior es seguro.
La rama avr de gcc generalmente no es muy buena, incluso en optimizaciones simples como los ejemplos en la pregunta, pero de todos modos, actualizar la versión de avr-gcc a menudo ayuda.
Otra preocupación es que gccs posteriores y avr-libcs posteriores podrían admitir el acceso a TCNT0 como registro único de 16 bits, lo que parece faltar en el gcc utilizado en la pregunta.
uint32_t
(o más) en un ISR. En estos casos, el compilador a veces puede salirse con la suya con 0 registros (¡use el registro temporal!) pero empuja cuatro de ellos a la pila y los vuelve a abrir al regresar. Tengo una macro asm lista para esos casos especiales.Si está dispuesto a desperdiciar un pin, puede obtener una captura de TCNT de 1 instrucción/2 ciclos cuando se llama al ISR utilizando la Unidad de captura de salida.
1
.TNCT
el valor capturado fuera del ICR
registro.Si desea ir a fondo con el ahorro de ciclos, ¡podría reducir esta captura de TCNT a un solo ciclo en el ISR!
Puede aprovechar el hecho de que el byte alto del registro TCNT se almacena en el búfer cada vez que se lee el byte bajo.
Entonces, si preasignó un registro (por ejemplo, r16) para esta tarea...
register unsigned char tcnt_low_byte asm("r16");
...luego llenó este registro con el byte bajo del TCNT
interior del ISR así...
R16 = TCNTL;
...que debería compilarse hasta el ciclo 1...
IN R16,TCNTL
... luego podría leer el TCNT
valor completo de la instantánea en primer plano de esta manera ...
period = (TCNTH << 8)| R16;
Solo asegúrese de leer TCNTH
antes de acceder a cualquier otro registro de temporizador de 16 bits, ya que todos ellos comparten ese registro temporal.
El trabajo total realizado en el ISR es solo uno, in R16, TCNTL
que es 1 ciclo.
El OP no indicó cómo señalaría el proceso de primer plano que ocurrió un ISR, pero si estaba precargando period
y 0
luego buscando un cambio, entonces se necesita algo de trabajo adicional...
0
en el TEMP
registro de 16 bits (puede hacerlo escribiendo a 0
en cualquier registro de 16 bits).0
en R16
.Luego puede sondear para ver si el ISR sucedió con...
x=TCNTH
if (x || R16) {
period=(x<<8 | R16)
// Process new period capture here...
}
period
su ejemplo (no se muestra)? Respuesta actualizada para el caso si su mecanismo fuera preestablecer el período 0
y luego sondearlo.
Golaž
romano matveev
Golaž
romano matveev
PedroJ
0x00FF
punto0x0100
en el que podría terminar leyendo0x01FF
.romano matveev
PedroJ
scott seidman