La forma más eficiente de leer señales analógicas en c ++

Soy nuevo en la programación de AVR y estoy tratando de aprender las E/S básicas con C++.

Estoy tratando de encontrar la forma más simple y eficiente de leer señales analógicas usando la misma función para diferentes dispositivos, al igual que la analogRead()función en el lenguaje Wiring (código Arduino). Pero no he encontrado nada muy simple que pueda usar para la programación de AVR.

¿Alguna sugerencia?

Respuestas (2)

Lo bueno de AVR es que los puertos de código son muy fáciles de procesador a procesador. Tengo un conjunto de funciones archivadas para configurar los periféricos.

Aquí está mi función de inicialización de ADC:

void init_adc(void)
{
    sei();  
    ADMUX = 0b11100000;
    ADCSRA = 0b10001100;
    ADCSRA = ADCSRA | (1<< ADSC);
}

La función sei() sirve como una habilitación de interrupción global y solo debe llamarse una vez si se utilizan varias interrupciones.

El registro ADMUX tiene el ADC configurado con la referencia interna de 2,56 V, el resultado de la conversión se deja ajustado para que los bits más significativos estén en ADCH y la conversión ADC0, de terminación única.

El registro ADSRA tiene el ADC habilitado, en modo de interrupción, y configura el preescalador para F o s C dieciséis . Este registro también alberga el indicador de interrupción de conversión completa.

ADCSRA = ADCSRA | (1<< ADSC);

Esta línea inicia la conversión. Es importante tener en cuenta que el ADC no funciona libremente. Se debe iniciar una nueva conversión después de cada conversión.

Mi código básico de rutina de servicio de interrupción:

ISR(ADC_vect)
{
    ? = ADCH;
    /*possible incrementing ADMUX for additional channels*/
    ADCSRA = ADCSRA | (1<< ADSC);
}

La parte más significativa del resultado está en ADCH. ADCL contiene los bits menos significativos. Lea de ADCH según sea necesario. Si tiene varios canales que está convirtiendo de un solo extremo, este es un buen lugar para manejar la multiplexación. Por ejemplo:

if (n == 3)
{
    ADMUX = 0b00100000;
    n = 0;
}
else
{
    ADMUX++;
    n++;
}

Recomiendo encarecidamente leer la hoja de datos para comprender el funcionamiento del ADC antes de comenzar a escribir código. Las hojas de datos de Atmel son bastante buenas y ofrecen buenas explicaciones y algunos códigos de muestra.

No uso Arduino, por lo que no sé qué sucede en esas rutinas, pero hay algunas formas comunes de realizar una medición de ADC con AVR. Una de las mejores cosas de los chips AVR en las líneas ATtiny y ATmega es que muchos registros periféricos tienen los mismos (o muy similares) nombres en diferentes chips. Voy a utilizar el ATtinyx4 ( hoja de datos ) en este ejemplo.

También es de destacar que estos chips se programan más comúnmente en C (o ensamblaje), no en C ++, aunque Arduino usa una versión de C ++.

Primero se debe configurar el ADC. Esto generalmente implica configurar el voltaje de referencia, inicializar el multiplexor ADC, configurar el preescalador de reloj ADC, configurar el modo y habilitar la interrupción ADC.

Por ejemplo (en C):

  // _BV(BIT) is defined as (1<<(BIT)) 
  ADMUX = _BV(REFS1);   // Use internal 1.1V reference voltage, multiplexer = 0
  ADCSRA = 
      _BV(ADPS1) |      // Prescaler = 4: F_ADC = F_cpu / prescaler
      _BV(ADEN);        // Enable the ADC
  ADCSRB = _BV(ADLAR);  // Left Adjust Result for 8 bit resolution

Para tomar una medida, harías esto:

  ADCSRA |= ADSC;               // Start an ADC conversion
  while(ADCSRA & _BV(ADSC));    // Wait until conversion is complete
  ADC_Value = ADCH;             // Read the ADC high register

Tenga en cuenta que solo leo el registro alto porque solo estoy usando una resolución de 8 bits. Podría haber leído fácilmente el valor completo de ADC en su lugar.

Si, en cambio, quisiera que el código manejara automáticamente el resultado de ADC, podría habilitar la interrupción completa de conversión de ADC.

  ADCSRA |= _BV(ADIE);     // Enable ADC Interrupt

Y maneje el valor de ADC en la rutina de servicio de interrupción de ADC. Esto podría ser más eficiente en el tiempo, pero utilizará más espacio de código.

También hay un "modo de ejecución libre", lo que significa que el ADC tomará una lectura continuamente mientras esté habilitado para el canal seleccionado. Toda esta información se encuentra en la hoja de datos de los chips AVR con funcionalidad ADC en el capítulo "Convertidor analógico a digital".

Si quisiera crear su propia función para hacer esto, podría verse así:

uint8_t ADC_read(uint8_t channel)
{
  ADMUX &= (_BV(REFS1) | _BV(REFS0));    // Clear the ADC Multiplexer
  ADMUX |= channel;                      // Set the ADC multiplexer
  ADCSRA |= ADSC;               // Start an ADC conversion
  while(ADCSRA & _BV(ADSC));    // Wait until conversion is complete
  return (ADCH);                // Return the ADC high register value
}

Otras cosas a considerar: la primera lectura después de habilitar el ADC suele ser basura, por lo que si no lo va a dejar habilitado (usa más energía de esta manera), deberá tomar dos mediciones consecutivas, descartando la primera. Por supuesto, los pines que se utilizan deben configurarse como entradas con los búferes de entrada digital desactivados (DIDRx).


Mis ejemplos anteriores usaron la macro _BV() para establecer un bit particular en un byte, pero esta es solo una forma diferente de desplazar un 1 a la izquierda tantos lugares. La macro _BV() normalmente se define en uno de los archivos de encabezado AVR junto con las definiciones de los nombres de registro específicos del chip que se incluyen en la parte superior de un programa, pero no sé cómo se maneja eso en Arduino.

Si la macro _BV() no funciona, puede definirla usted mismo como:

#define _BV(BIT)    (1<<(BIT))

Dado que un byte tiene 8 bits, se puede considerar así: [bit7, bit6, ... bit1, bit0]. Establecer un bit individual alto desplazando a la izquierda un 1 tantos bits significa que puede hacer un código más legible (y modificable) y luego simplemente establecer un byte en un valor específico. Por ejemplo, este código:

ADCSRA = 
      _BV(ADPS1) |      // Prescaler = 4: F_ADC = F_cpu / prescaler
      _BV(ADIE) |       // Enable ADC Interrupt
      _BV(ADEN);        // Enable the ADC

tiene mucho más sentido que este código:

ADCSRA = 0x8A;          // 0b10001010

además de ser más portable y fácil de editar.

A algunas personas no les gusta usar (o ver) esta macro, por lo que prefieren usar el operando de desplazamiento a la izquierda. De cualquier manera está bien y usa la misma cantidad de espacio de código/ciclos de reloj para operar. Prescindiendo de la macro _BV(), mis ejemplos se verían así:

// Setting up the ADC
ADMUX = (1<<REFS1);     // Use internal 1.1V reference voltage, multiplexer = 0
ADCSRA = 
    (1<<ADPS1) |        // Prescaler = 4: F_ADC = F_cpu / prescaler
    (1<<ADEN);          // Enable the ADC
ADCSRB = (1<<ADLAR);    // Left Adjust Result for 8 bit resolution

// Taking a measurement
ADCSRA |= ADSC;               // Start an ADC conversion
while(ADCSRA & (1<<ADSC));    // Wait until conversion is complete
ADC_Value = ADCH;             // Read the ADC high register

// Enabling the ISR
ADCSRA |= (1<<ADIE);          // Enable ADC Interrupt

//Function to read an ADC Channel (once the ADC is setup)
uint8_t ADC_read(uint8_t channel)
{
  ADMUX &= (1<<REFS1) | (1<<REFS0));     // Clear the ADC Multiplexer
  ADMUX |= channel;                      // Set the ADC multiplexer
  ADCSRA |= ADSC;               // Start an ADC conversion
  while(ADCSRA & (1<<ADSC));    // Wait until conversion is complete
  return (ADCH);                // Return the ADC high register value
}

Una vez más, estos ejemplos se basaron en los registros del ATtinyx4. Un chip AVR diferente con un ADC puede tener nombres o ubicaciones de registro y valor de bit ligeramente diferentes... Pero usar el registro real y los nombres de bit con comentarios significa que es fácil de modificar si es necesario.

@codificación_corgi No hay problema. También debo señalar que cualquier función incluida en su archivo principal debe declararse como "estática" a menos que también las use en otros archivos. Esto ahorrará bastante espacio de código. Ej: estático uint8_t ADC_read (canal uint8_t);
Ok, pondré esas funciones en un archivo de encabezado, en tu ejemplo dices " // _BV(BIT) is defined as (1<<(BIT))" - ¿eso significa que tengo que hacer esto #define _BV ...:?
@coding_corgi Con avr-gcc, la macro _BV() se define en uno de los archivos de encabezado estándar incluidos, pero, de nuevo, no sé sobre Arduino, por lo que siempre puede definirlo usted mismo como mencionó. Aunque no tienes que usarlo, solo queda desplazar un 1 tantos bits. Por ejemplo, si BAR es el bit 2, entonces (1<<BAR) = 0b00000100 = 0x04. Solo creo que hace que el código sea más fácil de leer. BV significa "valor de bits". También puede usarlo para múltiples bits, simplemente O juntos, ej: BYTE = _BV(bit2) | _BV(bit6); Pon esto en la parte superior de tu archivo: #define _BV(BIT) (1<<(BIT))
Esta es una manera fácil de usar el registro real y los nombres de bits que se encuentran en la hoja de datos en lugar de simplemente inicializar algunos registros como REG_BYTE = 0b00100101, en realidad puede especificar los bits exactos para configurar: REG_BYTE = _BV (bit0) | _BV(bit2) | _BV(bit5); El registro real y los nombres de bits deben definirse en los archivos de encabezado AVR incluidos por defecto.
¿Podría incluir eso en su ejemplo en lugar de _BV()por favor?