Audio-PWM auf atmega644

Ich versuche, Audio über eine 3,5-mm-Buchse mit einem Atmega644 zu erzeugen. Ich habe versucht, diesem Tutorial zu folgen , aber selbst mit dem bereitgestellten Sampling höre ich nur ein hohes Jammern. Ich habe viel im Datenblatt gelesen und bin auf diesen Code gekommen:

#define F_CPU 1000000
#include <stdint.h>
#include <avr/io.h>
#include "pcm_sample.h"
#include <avr/interrupt.h>
#define SAMPLE_RATE 8000;

volatile uint16_t sample = 0;

ISR(TIMER1_OVF_vect) {
    if (sample >= pcm_length) {
        sample = 0;
    }
    OCR1A = pcm_samples[sample++];
}

void init(void) {
    DDRD = (1<<PD5); // OCR1A

    // Fast PWM Mode 14, TOP=ICR1(0x7D), toggle OC1A on compare match
    TCCR1A = (1<<COM1A1) |  (1<<WGM11); 
    TCCR1B = (1<<CS10) | (1<<WGM12) | (1<<WGM13);
    TIMSK1 = (1<<TOIE1); // Interrupt on overflow
    // Timer ticks at 1us, samples every 125us, so overflow every 125us
    ICR1 = 0x7D; // Overflow at 125
    OCR1A = pcm_samples[0]; // Set initial duty
    sei();
}

int main(void) {
    init();
    while (1);
}

Aber dann kommt nichts aus der Kopfhörerbuchse. Mein erster Gedanke war, dass der Interrupt nicht ausgelöst wird, aber ich bin mir ziemlich sicher, dass er richtig eingerichtet ist. Irgendwelche Ideen?

BEARBEITEN: Das WGM12-Bit befindet sich im TCCR1B-Register und Timer 1 benötigt eine Taktquelle, wie Bruce Abbot unten betonte. Ich habe dies im Code geändert und jetzt wird der Interrupt ausgelöst, der Ton wird jedoch immer noch nicht wiedergegeben. Stattdessen bekomme ich ein hohes Heulen und gelegentlich von einem pochenden Geräusch unterbrochen.

Außerdem wurde mir klar, dass das Timing wahrscheinlich falsch war, also habe ich mich geändert, ich habe ein bisschen gerechnet und bin auf das Obige gekommen (hoffentlich ist es richtig). Ich musste einstellen können, wann Timer 1 übergelaufen ist, also habe ich von Modus 5 zu Modus 14 gewechselt. Nachdem ich einige Rechenfehler korrigiert habe, wird es besser: Fuzzy-Rauschen anstelle eines hohen Heulens.

Die Lösung hierfür war eine Kombination der folgenden Antworten und Kommentare. Der obige Code funktioniert, obwohl die Qualität ziemlich niedrig ist, können Sie deutlich hören, dass "es funktioniert" (für diese Anwendung mache ich mir keine Sorgen um die Qualität, aber ich nehme an, um das Problem zu beheben, müssen Sie nur nach einer höheren Abtastrate suchen als Chris Stratton unten erwähnt). Danke an alle die geholfen haben.

Wenn Sie beim Debuggen wissen möchten, ob der Mikrocontroller eine bestimmte Zeile überschritten hat, fügen Sie nach dieser Zeile eine Zeile hinzu, in der eine LED oder so etwas blinkt.
@BlueSky Ich habe das versucht, init wird definitiv aufgerufen und ausgeführt, aber der Interrupt wird nie ausgelöst
Versuchen Sie zu vermeiden, durch 255 zu teilen - es ist wahrscheinlich sowieso nicht das, was Sie wollen, und das Teilen durch 256 als 8-Bit-Verschiebung (oder realistischer, nur mit dem High-Byte) ist viel, viel schneller.
@ChrisStratton OK, es scheint, dass mir etwas Grundlegendes in meinem Verständnis von PWM fehlt (nach all den Antworten und Kommentaren unten zu urteilen). Sagen Sie mir, wo ich hier falsch liege: Alles, was ich versuche, ist, ein digitales Signal dazu zu bringen, sich wie ein analoges Signal zu verhalten, indem ich schnell einen Stift umschalte; Die Zahlen im Sample-Array stellen die Höhe der Welle zu diesem Zeitpunkt dar und stimmen damit überein, wie lange ich den Stift hoch halten muss, bevor ich ihn für den verbleibenden Teil dieses Intervalls ausschalte
Teilen Sie eine 8-Bit-Ganzzahl durch 255 und multiplizieren Sie sie dann mit 128, was erhalten Sie? Für jede Zahl kleiner als 255, Null! Sie versuchen, denselben Timer sowohl zum Erzeugen von PWM als auch zum Einstellen der Abtastrate zu verwenden, was nicht richtig funktioniert. Sie sollten einen separaten Sample-Wiedergabe-Timer verwenden. Der ATmega644 verfügt nur über 4 KB RAM. Wenn Sie also die Sample-Daten im RAM statt im ROM haben, sind Sie auf eine maximale Wiedergabezeit von 0,5 Sekunden beschränkt. Ich schlage vor, dass Sie zum ursprünglichen Tutorial-Code zurückkehren und nur die wenigen Änderungen vornehmen, die für Ihre MCU erforderlich sind.

Antworten (3)

Ihr Interrupt-Vektor ist falsch. Die ISR-Routine sucht nach TIMER0_1VF_vect, obwohl Sie den TIMER0-Timer nie eingerichtet haben und TIMER0_1VF_vect laut Atmels-Liste kein gültiger Vektor ist .

Sie richten TIMER1 so ein, dass es bei Überlauf unterbricht, also sollten Sie stattdessen TIMER1_OVF_vect verwenden, das bei TIMER1-Überlauf ausgelöst wird

Entschuldigung, das war ein Fehler beim Kopieren meines Codes, er ist in dem Code, den ich verwendet habe, korrekt
@TheBeanstalk funktioniert es mit diesem Fix nicht? Kannst du die pcm_samples dann posten? Ich frage mich, ob Ihre pcm_samples zu groß sind, um allein in das OCR1A-Register zu passen. OCR1 ist eigentlich 16-Bit aufgeteilt auf OCR1A und OCR1B
Die pcm_samples sind dieselben, die im ersten Tutorial verwendet wurden (sie sind etwas lang, um sie hier zu posten). Ich habe es überprüft und sie sind alle kleiner als ein Byte (255, 0xFF).
In Ordnung, dann stoßen Sie möglicherweise auf ein Catch-All-Interrupt-Problem. Wenn Sie mehr Interrupts aktiviert haben, als Sie ISRs haben, wird das gesamte Programm zurückgesetzt und erneut main aufgerufen. Schauen Sie sich den Catch-All-Abschnitt an: nongnu.org/avr-libc/user-manual/…
@TheBeanstalk Ich habe diese Informationen von hier abgerufen: avrfreaks.net/forum/seems-while1-loop-not-working
@TheBeanstalk Wenn dies nicht hilft, habe ich keine Ideen mehr. An diesem Punkt werde ich meine Antwort löschen, da sie nicht mehr hilfreich ist
Dies ist wirklich nützlich, um sicherzustellen, dass ich den richtigen Interrupt-Vektor verwende, aber es hat das Problem leider nicht gelöst ...
#define SAMPLE_RATE 8000;

Abgesehen von allen anderen Problemen, die Sie haben könnten, ist eine Abtastrate von 8 KSPS gut im hörbaren Bereich und entspricht ohne weiteres der Beschreibung eines "hohen Heulens".

Um eine so niedrige Abtastrate zu verwenden, ohne dass das Framing hörbar ist, ist ein externes Tiefpassfilter mit ausreichendem Roll-Off erforderlich, um das Framing zu dämpfen, was Ihre Audiobandbreite erheblich einschränkt - im Grunde genommen bleibt Ihnen bei richtiger Implementierung möglicherweise " Bandbreite der Telefonklasse.

Für PWM-Audio benötigen Sie wahrscheinlich eine höhere Bildrate. Die ATtiny-Familie kann PWM mit einer ungenauen Taktrate von ~64 MHz ausführen, die vom internen Oszillator multipliziert wird. Verschiedene ARM-Teile können mit einer Rate von einem präziseren externen Quarz multipliziert werden.

Aber solange ich keine Samples von einer Sinuswelle verwende, sollte ich nicht etwas anderes als einen konstanten Ton bekommen?
Möglicherweise erhalten Sie auch etwas anderes, aber wenn Ihr PWM-Arbeitszyklus nicht 100% beträgt oder vollständig gefiltert ist, hören Sie die 8-kHz-PWM selbst.
Das brachte mich dazu, über die Timer- und Taktgeschwindigkeiten nachzudenken, und mir wurde klar, dass sie nicht aufeinander abgestimmt waren. Würde das Ändern der Rate, mit der der Timer überläuft, um mit der Abtastrate (wie oben) übereinzustimmen, dieses Problem beheben?
Nein, nur das Bewegen der PWM-Bildrate über die hörbare Frequenz oder eine Filterabschaltung würde verhindern, dass die PWM-Bildrate hörbar ist. Natürlich können Sie auch andere Probleme haben.

Sie müssen eine Taktquelle für Timer 0 bereitstellen. Im ATmega644 wird dies durch die Bits 2:0 von TCCR0B ausgewählt, fügen Sie Ihrer init()Funktion also die folgende Zeile hinzu:

TCCR0B = (1<<CS00);

Das PWM-Modus-Bit WGM12 befindet sich im Register TCCRnB (nicht TTCRnA), und Sie müssen auch eine Taktquelle für PWM auswählen. Also ändere das:-

TCCR1A = (1<<COM1A1) | (1<<WGM12) | (1<<WGM10);

dazu:-

TCCR1A = (1<<COM1A1) | (1<<WGM10);
TCCR1B = (1<<WGM12) | (1<<CS10); 

Schließlich müssen Sie Überlauf-Interrupts für Timer 0 (nicht Timer 1) aktivieren, also ändern Sie: -

TIMSK1 = (1<<TOIE1); // Interrupt on overflow

Zu:-

TIMSK0 = (1<<TOIE0); // Interrupt on Timer 0 overflow

Dies sollte den Interrupt auslösen.

Wenn Ihre Beispieldaten jedoch im Flash-ROM (PROGMEM) gespeichert sind, werden Sie möglicherweise feststellen, dass sie nicht richtig wiedergegeben werden. Der Programmspeicher befindet sich in einem anderen Adressraum und erfordert einen speziellen Code, um darauf zuzugreifen. Daher müssen Sie möglicherweise die Funktion verwenden, pgm_read_byte()um den Compiler anzuweisen, die richtigen Anweisungen zu generieren, wie folgt: -

OCR1A = pgm_read_byte(&(pcm_samples[sample++])); 
Ich bin leicht von dem in meiner Frage verlinkten Tutorial abgewichen und habe die Beispieldaten nicht in PROGMEM gespeichert, also sollte es funktionieren, OCR1A einfach auf ppm_samples[sample] zu setzen, oder?
Warum muss er den Timer 0 anstelle des Timers 1 einrichten, wenn er die TCCR1A/B-Register ausfüllt? Es scheint, als würden Sie beide Sätze von TIMER-Registern etwa zur Hälfte ausfüllen. OP füllt bereits das Taktquellenbit und alle Bits aus, die Sie für TIMER1 notieren ...
@Beanstalk ja.
Ich habe nicht bemerkt, dass Sie Ihren Code bearbeitet haben. Der ISR-Vektor sollte wie im Tutorial TIMER0_ O VF_vect sein. Timer 0 wird verwendet, um die Samples mit 8000 Hz wiederzugeben. Timer 1 ist der PWM-Periode zugeordnet, die mit einer höheren Frequenz arbeitet.
Also auf der Hardwareseite, lasse ich die Buchse an OCR1A angeschlossen oder muss ich sie an OCR0A verschieben?
Sie schließen die Buchse an OC1A (PD5) an, weil dort die PWM herauskommt. Dies verwendet Timer 1 (TCCR1A, OCR1A usw.).
Ich denke, ich bin verwirrt, warum Timer0 dann benötigt wird ... Timer1 ist so eingerichtet, dass er PD5 umschaltet, wenn er das erreicht, was im OCR1A-Register steht, und auch einen Überlauf-Interrupt auslöst, wenn er 0xFF erreicht, richtig? Ist das nicht alles, was ich brauche, um das Audio zu erzeugen?
Nein. PWM muss mit einer viel höheren Frequenz als der Abtastrate laufen, sonst erhalten Sie eine Rechteckwelle mit voller Spannung von 8000 Hz, die ein schreckliches Heulen erzeugt. Selbst wenn der PWM-Timer überläuft (was nicht der Fall ist, wenn er für 8-Bit-PWM konfiguriert ist), ist die Interrupt-Frequenz falsch. Sie müssen einen separaten Timer verwenden, um die 8000 Interrupts pro Sekunde zu generieren, die erforderlich sind, um die Samples mit der richtigen Rate abzuspielen.