Extraño comportamiento del ADC de Attiny

#include <avr/io.h>
#include <util/delay.h>

void init(){
    // LED output.
    DDRB |= 1<<4;
    // Turn on ADC on third pin, continuous mode, prescaler 128.
    ADMUX |= (1<<MUX0) | (1<<MUX1);
    ADCSRA |= (1<<ADEN) | (1<<ADSC) | (1<<ADATE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
}

int main(){
    init();

    uint16_t adc = 0;
    while(1){
        adc = (ADCH << 8) | ADCL;
        // adc = (adc + 1) & 1023;
        // _delay_ms(1);

        if(adc > 512){
            PORTB |= 1<<4;
        }
        else{
            // PORTB = 0;
            PORTB &= ~(1<<4);
        }
    }
}

Este es el código que tengo corriendo en mi ATtiny13A. Hay un LED conectado al pin B4 y un potenciómetro a B3. Se supone que el código enciende el LED si el valor analógico es mayor que 512 (la mitad del máximo 1024). Sin embargo, no funciona y el LED parece estar desactivado todo el tiempo.

Normalmente asumiría que hay algún problema en la configuración de los periféricos, pero están sucediendo algunas cosas muy extrañas. Por ejemplo, si cambio PORTB &= ~(1<<4);a PORTB = 0(que en este caso debería hacer exactamente lo mismo), el código mágicamente comienza a funcionar tal como se esperaba. Otra cosa que probé es cambiar el estado del LED usando un contador simple quitando el comentario de las dos líneas cercanas _delay_ms, y nuevamente, el LED comienza a parpadear como se esperaba.

El único momento en que este código no funciona es cuando está exactamente en esta forma, por lo que debe haber algún problema con la combinación de lectura de ADC y escritura de bits del puerto de salida.

¿Por qué pasó esto?

Una cosa más: incluso con el código que se muestra, parece que el LED podría encenderse UNA vez, por lo que si el potenciómetro comenzó bajo (con el LED apagado), podría encender el LED y luego apagarlo, pero la próxima vez Intenté girar el potenciómetro, falla. Esto es tan extraño...
De acuerdo, tengo una buena pista: si uso PORTB = 0(la línea que funcionó), pero luego agrego una sola nop, se vuelve a romper. Parece ser un problema de tiempo por alguna razón: agregar 4 nops lo soluciona nuevamente ...

Respuestas (1)

  1. Patrón de acceso de registro de 16 bits incorrecto

    Cuando se lee ADCL, el registro de datos ADC no se actualiza hasta que se lee ADCH.

En C, el orden de evaluación del |operador no está definido, por lo que este código podría leerse ADCHprimero y luego leer ADCL. Una vez que haya leído ADCL, habrá bloqueado el registro de datos hasta el próximo paso por el ciclo, que ciertamente no es lo que desea.

Intenta cambiar esta línea a...

adc = ADC;

(el compilador de C debería generar la codificación de orden de acceso correcta).

  1. Lectura de ADC mientras se actualiza.

Este diagrama sugiere fuertemente que el registro ADC se está actualizando hasta que ADIF sube...

ingrese la descripción de la imagen aquí

Espero que los nuevos bits se muevan a su lugar con cada aproximación sucesiva, y la demora adicional de noppodría ser suficiente para que el ADC esté listo para cuando lo golpee por casualidad.

Intenta reemplazar...

adc = (ADCH << 8) | ADCL;

...con...

while (! ADCSRA & _BV(ADIF) );
adc = ADC;
ADCSRA |= _BV( ADIF );

... y ver si eso resuelve el problema del tiempo.

Informe de nuevo si esto soluciona el problema. Si no, ¡pasaremos al próximo intento!

Ajá, eso resolvió el problema. ¡Buena atrapada! De hecho, el ensamblaje generado leyó ADCH primero (sin embargo, el orden de operación generalmente no está garantizado por C).
"En C, [...] el |operador se evalúa de izquierda a derecha" - esto no es correcto. De hecho, el orden de evaluación no está especificado (a diferencia de, digamos, para ||, donde es de izquierda a derecha para permitir la evaluación de cortocircuito).
No revisé la hoja de datos por ahora, pero ¿funciona la verificación de ADIF cuando el ADC está en modo de ejecución libre? Comienza una nueva conversión justo después de la anterior, por lo que el registro de datos debe estar completamente almacenado en el búfer o debe ser ilegible en todo momento...