Der effizienteste Weg, analoge Signale in c++ zu lesen

Ich bin neu in der AVR-Programmierung und versuche, die grundlegende I/O mit C++ zu lernen.

Ich versuche, den einfachsten und effizientesten Weg zu finden, analoge Signale mit derselben Funktion für verschiedene Geräte zu lesen, genau wie die analogRead()Funktion in der Wiring-Sprache (Arduino-Code). Aber ich habe nichts sehr einfaches gefunden, was ich für die AVR-Programmierung verwenden könnte.

Irgendwelche Vorschläge?

Antworten (2)

Das Schöne an AVR ist, dass Codeports sehr einfach von Prozessor zu Prozessor übertragen werden können. Ich habe eine Reihe von Funktionen zum Einrichten der Peripheriegeräte gespeichert.

Hier ist meine ADC-Initialisierungsfunktion:

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

Die Funktion sei() dient als globale Interrupt-Freigabe und muss nur einmal aufgerufen werden, wenn mehrere Interrupts verwendet werden.

Das ADMUX-Register hat den ADC mit der internen 2,56-V-Referenz eingerichtet, das Konvertierungsergebnis links angepasst, so dass die höchstwertigen Bits in ADCH sind, und ADC0 unsymmetrisch konvertiert.

Das ADSRA-Register hat den ADC im Unterbrechungsmodus aktiviert und setzt den Vorteiler auf F Ö S C 16 . Dieses Register beherbergt auch das Interrupt-Flag für den Abschluss der Konvertierung.

ADCSRA = ADCSRA | (1<< ADSC);

Diese Zeile startet die Konvertierung. Es ist wichtig zu beachten, dass der ADC nicht frei läuft. Nach jeder Konvertierung muss eine neue Konvertierung gestartet werden.

Mein Bare-Bones-Interrupt-Service-Routine-Code:

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

Der signifikanteste Teil des Ergebnisses liegt in ADCH. ADCL enthält die niederwertigsten Bits. Lesen Sie bei Bedarf von ADCH. Wenn Sie mehrere Kanäle haben, die Sie Single-Ended konvertieren, ist dies ein guter Ort, um das Multiplexing zu handhaben. Zum Beispiel:

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

Ich empfehle dringend, das Datenblatt zu lesen, um die Funktionsweise des ADC zu verstehen, bevor Sie mit dem Schreiben von Code beginnen. Die Datenblätter von Atmel sind ziemlich gut und bieten gute Erklärungen und einige Beispielcodes.

Ich verwende Arduino nicht, daher weiß ich nicht, was in diesen Routinen passiert, aber es gibt nur wenige gängige Möglichkeiten, eine ADC-Messung mit AVR durchzuführen. Eines der großartigen Dinge an AVR-Chips in den ATtiny- und ATmega-Reihen ist, dass viele periphere Register die gleichen (oder sehr ähnlichen) Namen in verschiedenen Chips haben. Ich werde in diesem Beispiel den ATtinyx4 ( Datenblatt ) verwenden.

Bemerkenswert ist auch, dass diese Chips häufiger in C (oder Assembly) programmiert werden, nicht in C++, obwohl Arduino eine Version von C++ verwendet.

Der ADC muss zunächst eingerichtet werden. Dies umfasst typischerweise das Einstellen der Referenzspannung, das Initialisieren des ADC-Multiplexers, das Einstellen des ADC-Takt-Prescalers, das Einstellen des Modus und das Aktivieren des ADC-Interrupts.

Zum Beispiel (in 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

Um eine Messung durchzuführen, würden Sie Folgendes tun:

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

Beachten Sie, dass ich nur das hohe Register lese, da ich nur eine Auflösung von 8 Bit verwende. Ich hätte stattdessen leicht den gesamten ADC-Wert lesen können.

Wenn Sie stattdessen möchten, dass der Code das ADC-Ergebnis automatisch verarbeitet, können Sie den ADC-Konvertierungsabschluss-Interrupt aktivieren.

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

Und behandeln Sie den ADC-Wert in der ADC-Interrupt-Serviceroutine. Dies ist möglicherweise zeiteffizienter, verbraucht jedoch mehr Codespeicherplatz.

Es gibt auch einen "freilaufenden Modus", was bedeutet, dass der ADC kontinuierlich eine Messung durchführt, während er für den ausgewählten Kanal aktiviert ist. All diese Informationen finden Sie im Datenblatt der AVR-Chips mit ADC-Funktionalität im Kapitel „Analog-Digital-Wandler“.

Wenn Sie dafür eine eigene Funktion erstellen möchten, könnte dies folgendermaßen aussehen:

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
}

Andere zu beachtende Dinge: Die erste Messung nach dem Aktivieren des ADC ist normalerweise Müll. Wenn Sie ihn also nicht aktiviert lassen (verbraucht auf diese Weise mehr Strom), müssen Sie zwei aufeinanderfolgende Messungen durchführen und die erste verwerfen. Natürlich sollten die verwendeten Pins als Eingänge mit deaktivierten digitalen Eingangspuffern (DIDRx) eingestellt werden.


In meinen obigen Beispielen wurde das Makro _BV() verwendet, um ein bestimmtes Bit in einem Byte zu setzen, aber dies ist nur eine andere Art, eine 1 um so viele Stellen nach links zu verschieben. Das Makro _BV() wird normalerweise in einer der AVR-Header-Dateien zusammen mit den Definitionen für chipspezifische Registernamen definiert, die oben in einem Programm enthalten sind, aber ich weiß nicht, wie das in Arduino gehandhabt wird.

Wenn das Makro _BV() nicht funktioniert, können Sie es selbst wie folgt definieren:

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

Da ein Byte aus 8 Bit besteht, kann man es sich folgendermaßen vorstellen: [bit7, bit6, ... bit1, bit0]. Wenn Sie ein einzelnes Bit hoch setzen, indem Sie eine 1 um so viele Bits nach links verschieben, können Sie besser lesbaren (und veränderbaren) Code erstellen, als nur ein Byte auf einen bestimmten Wert zu setzen. Zum Beispiel dieser Code:

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

macht viel mehr Sinn als dieser Code:

ADCSRA = 0x8A;          // 0b10001010

zusätzlich dazu, dass es portabler und einfacher zu bearbeiten ist.

Einige Leute verwenden (oder sehen) dieses Makro nicht gern, also ziehen sie es vor, einfach den linken Umschaltoperanden zu verwenden. Beide Wege sind in Ordnung und verwenden die gleiche Menge an Coderaum/Taktzyklen für den Betrieb. Ohne das Makro _BV() würden meine Beispiele so aussehen:

// 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
}

Auch diese Beispiele basierten auf den Registern für den ATtinyx4. Ein anderer AVR-Chip mit einem ADC kann leicht unterschiedliche Register- und Bitwertnamen oder -orte haben ... Aber die Verwendung der tatsächlichen Register- und Bitnamen mit Kommentaren bedeutet, dass sie bei Bedarf leicht geändert werden können.

@coding_corgi Kein Problem. Ich sollte auch darauf hinweisen, dass alle in Ihrer Hauptdatei enthaltenen Funktionen als "statisch" deklariert werden sollten, es sei denn, Sie verwenden sie auch in anderen Dateien. Dadurch wird einiges an Codeplatz gespart. Beispiel: Statisch uint8_t ADC_read (uint8_t Kanal);
Ok, ich werde diese Funktionen in eine Header-Datei einfügen, in Ihrem Beispiel sagen Sie " // _BV(BIT) is defined as (1<<(BIT))" - bedeutet das, dass ich Folgendes tun muss: #define _BV ...?
@coding_corgi Mit avr-gcc ist das Makro _BV() in einer der standardmäßig enthaltenen Header-Dateien definiert, aber andererseits weiß ich nichts über Arduino, sodass Sie es immer selbst definieren können, wie Sie es erwähnt haben. Obwohl Sie es nicht verwenden müssen, bleibt es nur übrig, eine 1 um so viele Bits zu verschieben. Wenn beispielsweise BAR Bit 2 ist, dann (1<<BAR) = 0b00000100 = 0x04. Ich denke nur, dass es den Code einfacher zu lesen macht. BV steht für „Bitwert“. Sie können es auch für mehrere Bits verwenden, indem Sie sie einfach mit ODER verknüpfen, z. B.: BYTE = _BV(bit2) | _BV(bit6); Setzen Sie dies an den Anfang Ihrer Datei: #define _BV(BIT) (1<<(BIT))
Dies ist eine einfache Möglichkeit, die tatsächlichen Register- und Bitnamen aus dem Datenblatt zu verwenden, anstatt nur einige Register als REG_BYTE = 0b00100101 zu initialisieren. Sie können tatsächlich die genauen zu setzenden Bits angeben: REG_BYTE = _BV(bit0) | _BV(bit2) | _BV(bit5); Die tatsächlichen Register- und Bitnamen sollten in den standardmäßig enthaltenen AVR-Header-Dateien definiert werden.
Könnten Sie das anstelle von _BV()bitte in Ihr Beispiel aufnehmen?