Tengo un problema muy extraño de una posible corrupción de memoria cuando uso variables globales y una interrupción de desbordamiento del temporizador en ATTiny10 (usando avr-gcc 4.9.2). No puedo entenderlo, pero logré reducirlo a reproducirlo con un programa muy simple:
#include <avr/io.h>
/* Timer overflow counter */
volatile unsigned int ovrf = 0;
/* Some global variables used in main() */
/* (MOVING THESE INTO main() FIXES THE ISSUE) */
unsigned long foo;
unsigned int bar;
int main(void) {
/* Fast PWM 8 Bit Mode */
TCCR0A |= _BV(WGM00);
TCCR0B |= _BV(WGM02);
/* Enable Timer Overflow Interrupt */
TIMSK0 |= _BV(TOIE0);
/* /8 prescaler */
TCCR0B |= _BV(CS01); //
/* PB0 as output */
DDRB |= _BV(PB0);
/* Enable interrupts */
sei();
for (;;) {
/* Some random code that uses the global vars */
/* (REMOVING THIS FIXES THE ISSUE) */
if (foo > bar) {
foo = 0;
}
}
}
ISR(TIM0_OVF_vect) {
ovrf++;
/* Toggle LED (about once per second) */
if ((ovrf / 500) % 2 == 0) {
PORTB &= ~(_BV(PB0));
} else {
PORTB |= _BV(PB0);
}
}
Todo lo que hace es lo siguiente:
ovrf
) y enciende y apaga un LED según el valor de este contador.Espero que el LED parpadee periódicamente, lo que demuestra que la interrupción funciona y el contador se incrementa correctamente. Pero no se enciende, o cuando se modifica ligeramente el programa, por ejemplo, al agregar más código o main()
más variables, parpadea de forma errática oa un ritmo muy rápido. A partir de esto, después de muchas pruebas, tratando de excluir cualquier otra explicación, asumo que el contador ( ovrf
) de alguna manera se está corrompiendo desde el ciclo principal.
Encontré varios cambios que pueden hacer que el problema desaparezca:
foo
y bar
) amain()
foo
aint
-O0
(el valor predeterminado era -Os
), pero esto hace que el código sea ~1,6 veces más grande.Pero todavía no puedo ver ninguna explicación sobre la causa real. ¿Me estoy perdiendo por completo algo obvio...? Me he quedado sin ideas y no puedo pensar en otra cosa que no sea un error del compilador, pero eso es muy poco probable, dado que este ejemplo es muy simple.
ACTUALIZAR
Basado en la sugerencia de @MarkU, traté de jugar con varias configuraciones de optimización, tratando de encontrar la opción exacta que podría causar el problema:
También lo intenté -O1
, pero tampoco ayuda.
¡ Descubrí que eso -Os -fno-toplevel-reorder
también soluciona el problema! -- Sin embargo, sospecho que esto podría ser solo un efecto accidental:
En mi programa original (muy similar al ejemplo simplificado anterior), donde encontré el problema, nada de lo anterior ayuda (ni siquiera -O0
). Allí tengo una variable global más (a bool
), y lo único que parece ayudar es eliminar una asignación inicial (por ejemplo, "bool ledOn = true;" --> "bool ledOn;"
).
Así que definitivamente tiene algo que ver con la forma en que se asignan las variables, pero no simplemente con su tamaño general. (No hay otras dependencias, ni llamadas a funciones, etc.)
ACTUALIZAR 2
Siguiendo el consejo de @Curd, también intenté reemplazar ovrf / 500
con ovrf >> 9
(más o menos lo mismo, no me importa el tiempo exacto aquí de todos modos). Esto redujo el código en 74 bytes (!), ¡y esto también soluciona el problema!
Eché un vistazo al código desensamblado para el ISR: este cambio reduce la cantidad de bytes pushed
al principio de 13 a 7, ¡lo que podría explicar por qué ayuda!
(Esto ovrf / 500
solo pretendía ser una prueba rápida y simple para verificar que el ISR estaba funcionando, ¡pero no me di cuenta de que en la implementación real no es tan simple en absoluto! En mi programa original no hay división, mantengo un valor aproximado de milisegundos. cuente simplemente multiplicando ovrf
por 2.)
También comparé el código desensamblado para -Os
("malo") y -Os -fno-toplevel-reorder
("bueno"), pero aparte de que el código se reordenó al principio, tanto el contenido como main()
el ISR parecen iguales (la misma cantidad de pop/pulsaciones, etc.)
--
Parece que puedo solucionar el problema en este ejemplo concreto con una de las soluciones anteriores, pero todavía me siento incómodo por no entender realmente la causa real y no saber cómo evitar esto en el caso general. Y no sé lo suficiente sobre ensamblaje para analizar el código generado.
Tal vez también debería hacer algunas de estas preguntas:
¿Este tipo de proceso de prueba y error es "normal" cuando se usa C para ATTiny10? (Quiero decir: no hay suficientes recursos y / o soporte de compilador insuficiente para que esto sea confiable, así que no espere que funcione y simplemente vuelva al ensamblaje si no es así).
¿Hay algo que deba evitarse en general (p. ej., no usar vars globales u optimización)?
ACTUALIZAR 3
Gracias por todos los comentarios y respuestas, hay muchas sugerencias útiles en ellos, ¡vale la pena revisarlas todas para cualquiera que tenga un problema similar!
Me quedaba un "misterio" más con mi programa original donde reemplazar a bool ledOn = true;
con bool ledOn;
era la única solución.
Ahora que entiendo más, eché otro vistazo al ensamblaje generado y al uso de la memoria: resulta que la inicialización hace que el compilador produzca un .data
segmento y se asigna un byte más en la memoria, que está justo por encima del límite para causar una colisión con el pila. Aunque (creo) usa un registro para esta variable al final, al igual que en el caso de "inicialización no explícita", por lo que la asignación adicional no debería ser necesaria. Supongo que el compilador simplemente no tiene optimización para este caso extremo con una cantidad tan pequeña de RAM.
Con solo 32 bytes de memoria (como menciona MarkU en un comentario), la memoria del ATtiny10 es increíblemente limitada. El compilador AVR-GCC no proporciona ninguna herramienta para verificar la pila y felizmente generará un código que invadirá la pila. Por ejemplo, esto es lo que generó para el prólogo de su ISR:
000000ba <__vector_4>:
ba: 1f 93 push r17
bc: 0f 93 push r16
be: 0f b7 in r16, 0x3f ; 63
c0: 0f 93 push r16
c2: 10 e0 ldi r17, 0x00 ; 0
c4: 4f 93 push r20
c6: 5f 93 push r21
c8: 6f 93 push r22
ca: 7f 93 push r23
cc: 8f 93 push r24
ce: 9f 93 push r25
d0: af 93 push r26
d2: bf 93 push r27
d4: ef 93 push r30
d6: ff 93 push r31
Cuento 13 push
es allí. Eso hará que la pila se expanda a casi la mitad de la memoria de su dispositivo. Combinado con otro rcall
en el cuerpo del ISR, así como un par de push
es y rcall
s en el prólogo de main
, la pila del ISR terminará chocando con la memoria utilizada para almacenar sus variables globales, sobrescribiéndolas con datos inesperados.
El ATtiny10 no es un buen objetivo para un compilador de C. Si su aplicación admite un microcontrolador un poco más grande, es posible que se justifique la actualización a la familia tiny25/45/85. De lo contrario, recomendaría apuntar a este dispositivo con ensamblaje.
ISR_NAKED
.main()
).volatile unsigned int ovrf = 0;
unsigned long foo;
unsigned int bar;
+8 bytes de ram
int main(void) {
+2 bytes o ram, use el atributo ((OS_main))
ISR(TIM0_OVF_vect) {
Si no especifica nada, empuja un marco de pila, para una llamada de función. Eso será alrededor de 14 bytes, si no recuerdo mal de mis problemas con ATTiny10. La dirección de retorno (2 bytes) y algunos registros.
Con solo 32 bytes, puede realizar una llamada de función completa. Si quiere más, necesitará usar __attribute__((naked))
y escribir ensamblador.
if ((ovrf / 500) % 2 == 0) {
Esto probablemente llama a funciones de biblioteca, de las cuales no puede almacenar el marco de pila.
Y también hay solo 1024 bytes de memoria de programa. Esto es muy pequeño para C, ~100 líneas pequeñas.
Ignacio Vázquez-Abrams
Arce
MarkU
-O0
como para-Os
la configuración; mi suposición es que una de las banderas de optimización (como-fcaller-saves
?) golpea el código que guarda/restaura el estado o los registros de índice dentro de la rutina del servicio de interrupción. Consulte la hoja de datos de Atmel ATtiny10, sección 5.8 Manejo de reinicio e interrupción. Consulte también las opciones de optimización de gcc-4.9.2MarkU
pdenes
avr-objdump -D
y-S
para.hex
los.elf
archivos y puedo ver diferencias, algunas tienen sentido, pero realmente no puedo seguir lo que está pasando. (No quería publicar todo el resultado, pero estoy feliz de hacerlo, ¡o una parte si eso ayuda!)pdenes
long
s, el problema sigue ahí ... así que no es eso, sino probablemente algo sobre el acceso a estas variables en el ciclo principal. (Sin embargo, estoy desconcertado, ya que definitivamente no estoy escribiendo nada allí...)pdenes
pdenes
Cuajada
ovrf / 500
? El ATtiny ni siquiera tiene una instrucción para la multiplicación; por no hablar de la división! Esta prueba se puede implementar mucho más con un microcontrolador. Tal vez se necesiten más ciclos para procesar el ISR de los que ha configurado en el intervalo del temporizador. Por cierto: ¡no veo el temporizador intevrall inicializado!pdenes