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

3

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

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

¿Alguna sugerencia?

    
pregunta user151324

2 respuestas

3

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

Aquí está mi función de inicialización 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 están utilizando múltiples interrupciones.

El registro ADMUX tiene el ADC configurado con la referencia interna de 2.56V, el resultado de la conversión se ha ajustado para que los bits más significativos estén en ADCH, y la conversión de ADC0, con un solo extremo.

El registro ADSRA tiene el ADC habilitado, en modo de interrupción, y establece el prescaler en \ $ \ frac {f_ {osc}} {16} \ $. Este registro también contiene la bandera 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 se ejecuta libremente. Se debe iniciar una nueva conversión después de cada conversión.

El código de la rutina de servicio de interrupción de mis funciones básicas:

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 en 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 el código. Las hojas de datos de Atmel son bastante buenas y ofrecen buenas explicaciones y algunos ejemplos de código.

    
respondido por el Matt Young
3

No uso Arduino, por lo que no sé lo que está sucediendo en esas rutinas, pero hay algunas formas comunes de tomar una medida 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 nombres (o muy similares) 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. Por lo general, esto implica configurar el voltaje de referencia, inicializar el multiplexor ADC, configurar el prescaler 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

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

Si, por el contrario, desea que el código maneje automáticamente el resultado de ADC, puede habilitar la interrupción completa de la 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 desea crear su propia función para hacer esto, si se parece a esto:

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) tendrá que tomar dos medidas consecutivas, descartando la primera uno. Por supuesto, los pines que se están utilizando deben configurarse como entradas con los buffers de entrada digital desactivados (DIDRx).

Mis ejemplos anteriores utilizaron la macro _BV () para establecer un bit en particular en un byte, pero esta es solo una forma diferente de desplazar a la izquierda en 1 tantos lugares. La macro _BV () se define normalmente 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 en Arduino.

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

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

Dado que un byte es de 8 bits, puede pensarse así: [bit7, bit6, ... bit1, bit0]. Al establecer un bit individual alto al desplazar a la izquierda un 1, muchos bits más significa que puede hacer que el código sea 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 portátil y más fácil de editar.

A algunas personas no les gusta usar (o ver) esta macro, por lo que prefieren usar solo el operando de cambio a la izquierda. De cualquier manera, está bien y utiliza la misma cantidad de espacio de código / ciclos de reloj para operar. Abandonando 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
}

Nuevamente, estos ejemplos se basaron en los registros para el ATtinyx4. Un chip AVR diferente con un ADC puede tener nombres o ubicaciones de registro y valores de bits ligeramente diferentes ... Pero usar el registro real y los nombres de bits con comentarios significa que es fácil de modificar si es necesario.     

respondido por el Kurt E. Clothier

Lea otras preguntas en las etiquetas