¿Cómo usar AVR ADC con multiplicador?

Estoy aprendiendo sobre la programación de microcontroladores AVR y actualmente tengo una luz intermitente LED simple configurada en un ATtiny26.

Un potenciómetro de 10k está conectado a ADC7 (pin 7) como en el ejemplo de la parte 1 de este tutorial . Los pines de salida del puerto B están conectados a los LED a través de transistores en una matriz multiplexada. (El circuito funciona bien sin el ADC involucrado).

Para probar/aprender, estoy parpadeando un solo LED y esperando una cantidad de milisegundos igual a lo que se lee del ADC. Tengo un problema cuando trato de multiplicar el valor para implementar demoras más largas.

Pensé que el problema era que la variable en la que almaceno el ADCH era una intpero necesita ser otra cosa porque este es un microcontrolador de 8 bits y intsolo puede ser 0-255. Sin embargo, sustituir longno funcionó.

Aquí está el código que funciona:

#define F_CPU 1000000UL
#define ADC_VREF_TYPE = 0x40
int SD = 1;
int main()
{
    // Set port A to output (1)
    DDRA = 0b11111111;
    // Set port B, pin 3, to input (0)
    DDRB &= ~(1 << PB3);
    // Set control and status register
    ADCSR = 0b11100111;
    // Set ADC multiplexer selection register
    ADMUX  = 0b00100111;

    while (1)
    {
        // Set SD (step delay) to value from ADC
        SD = ADCH;
        // Turn on LED (it is a multiplexer, so two pins are used)
        PORTA = 0b00010001;
        // Wait for SD milliseconds
        milli_delay(SD);
        // Turn off LED
        PORTA = 0b00000000;
        // Wait for SD milliseconds
        milli_delay(SD);
    }
}

El problema ocurre cuando trato de usar un multiplicador como este:

SD = ADCH * 10;

Al hacer esto, el LED parpadeará rápidamente en un extremo del barrido del potenciómetro, pero se iluminará fijo y permanecerá así en algún punto mientras gira el potenciómetro a velocidades más lentas (valores ADCH más grandes); esencialmente "bloqueándose" y requiriendo un reinicio mientras el potenciómetro está nuevamente en la posición original. Supongo que estoy tratando de almacenar un valor mayor que el que contiene el tipo numérico, pero parece que no puedo superar esto cambiando el tipo de variable. Lo he intentado longy uint32_trealmente no entiendo qué tienen los tipos numéricos en este micro.

P: ¿Qué debo hacer para poder implementar demoras que van desde 0 hasta, por ejemplo, 2000 milisegundos?

FYI tengo las siguientes bibliotecas incluidas:

#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <math.h>

Editar:

Lo había colocado _delay_ms(x)directamente en mi ejemplo para evitar publicar las funciones que realmente estoy usando y que llaman _delay_ms(1)dentro de un bucle. Pensé que sería más conciso, pero como señaló una respuesta, en realidad parecía ser un error.

¿Puedes publicar un esquema de tu configuración de transistor/mux también? Quién sabe, alguien podría ver un problema allí.
Hice el esquema primero en papel cuadriculado a mano, y ayer lo volví a hacer en Eagle. Si crees que es útil, lo incluiré; aunque cuando ignoro el ADC y uso un valor codificado, todo funciona como se esperaba. Además, si uso el valor ADC sin intentar multiplicarlo, obtengo buenos resultados (retrasos de 0-255 milisegundos).

Respuestas (2)

intsolo puede ser 0-255

Eso no es correcto. En el Clenguaje de programación, an intsiempre tiene al menos 16 bits de ancho, incluso en micros de 8 bits. A chartiene 8 bits de ancho.

Pero al usar inttypes.h, recomiendo enfáticamente usar los tipos explícitos como uint8_t, int16_to uint32_t.

Su verdadero error es usar _delay_ms()con un parámetro variable. Esto no está permitido, ya que es una macro que involucra cálculos de coma flotante, al menos en avr gcc. Se supone que debes usar algo como esto:

void DelayMs(int delay) {
  while(delay--) _delay_ms(1);
}
Je, lo gracioso es que modifiqué las líneas _delay_ms() para publicar mi pregunta, ya que en realidad son llamadas a funciones que hacen exactamente lo que especificas para avr gcc. Los modifiqué pensando que sería menos código para publicar.
Si el valor int es de 16 bits, creo que ADCH * 10funcionaría perfectamente. ¿Alguna idea adicional de por qué podría no ser así?
Chicos, ¿están seguros de esto de las macros? El util/delay.h que estoy viendo define _delay_ms() como una función regular.
Estoy pensando que tiene algo que ver con el uso del lenguaje o algo así. Consulte stackoverflow.com/q/12095890/161052
La solución a mi problema resultó ser la configuración de optimización en el compilador. Aún así, su información fue muy útil y me mantuvo en el camino correcto. ¡Gracias!

Creo que el problema es que tienes el resultado ADC justificado a la izquierda en el registro de resultados:

ingrese la descripción de la imagen aquí

Su código a continuación muestra que ha configurado ADLAR en 1 (justificado a la izquierda):

 // Set ADC multiplexer selection register
    ADMUX  = 0b00100111;

Esto significa que cuando está leyendo el valor ADC en ADCH, está saltando por completo los dos bits menos significativos (LSB). Esto significa que está leyendo el número como si fuera 4 veces más pequeño (desplazamientos de dos bits a la derecha).

Lo que quieres hacer es configurarlo justificado a la DERECHA:

 // Set ADC multiplexer selection register
    ADMUX  = 0b00000111;

y lea su valor ADC de ADCL (el registro de resultado ADC inferior):

while (1)
{
    // Set SD (step delay) to value from ADC
    SD = ADCL;
    // Turn on LED (it is a multiplexer, so two pins are used)
    PORTA = 0b00010001;
    // Wait for SD milliseconds
    milli_delay(SD);
    // Turn off LED
    PORTA = 0b00000000;
    // Wait for SD milliseconds
    milli_delay(SD);
}
Esto no ayudó, lo siento. Si uso el valor ADCH y justifico el resultado a la izquierda, obtengo mis valores esperados (0-255). Probé esto escribiendo un poco de código que enciende una cantidad de LED igual a 1/16 del valor. Girar el potenciómetro produce los LED encendidos esperados. Sospecho que hay un problema de código/idioma/uso, consulte stackoverflow.com/q/12095890/161052