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?
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 . 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.
Kurt E. Tuchmacher
Benutzer151324
// _BV(BIT) is defined as (1<<(BIT))
" - bedeutet das, dass ich Folgendes tun muss:#define _BV ...
?Kurt E. Tuchmacher
Kurt E. Tuchmacher
Benutzer151324
_BV()
bitte in Ihr Beispiel aufnehmen?