PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Audio => AVR (LM339)



Jaecko
01.08.2008, 11:03
Moin.

Hab vor einiger Zeit hier schon mal gefragt, wie man ein Audiosignal am besten für nen AVR in ein Rechtecksignal umwandelt.
Damals wurde mir der Tip mit dem LM339 gegeben. Ich habs zu der Zeit auch hinbekommen.
Jetzt - über 1 Jahr später - wollt ich die Schaltung nocheinmal aufbauen, aber es klappt nicht so wirklich.

Ziel ist es einfach, ein Audiosignal aus einer Soundkarte o.ä. so aufzubereiten, dass ich ein Rechtecksignal gleicher Frequenz hab, die dann an nem INT-Pin eines AVR bestimmt werden kann.

µC ist ein ATMega8, 8MHz intern (ausreichend für diese Zwecke).

Das Testprogramm soll, abhänging von der Frequenz einfach mal eine LED blinken lassen. Ein Software-Frequenzteiler teilt durch 5000, so dass z.B. 5 kHz die LED im Sekundentakt blinken lassen müsste.

Die LED blinkt jedoch - unabhängig von der angelegten Frequenz - immer relativ gleich. Als Testsignal wird ein Sweep von 500 - 5000 Hz durchlaufen.

Für das spätere Projekt sollen dann nur Frequenzen von 800 - 3000 Hz erkannt werden.

Hier mal das Testprogramm:


#include "main.h" // Funktionsprototypen

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "glob_defs.h" // Definitionen wie ENABLE = 1, DISABLE = 0, etc
#include "glob_type.h" // Typen, ui8_t, ui16_t etc...

volatile ui16_t tmpcnt = 0;

void Sys_Init(void)
{
DDRC |= (1 << PC2) | (1 << PC3) | (1 << PC4) | (1 << PC5);
}

void INT0_Init(void)
{
// INT0, Steigende Flanke
GICR |= (1 << INT0);
MCUCR |= (1 << ISC01) | (1 << ISC00);
}

void Timer1_Init(void)
{
// Timer einfach zählen lassen, Prescaler 1
TCCR1A = 0;
TCCR1B = (1 << CS10);
TCNT1 = 0;
}

int main(void)
{
Sys_Init();
Timer1_Init();
INT0_Init();
sei();
while(1)
{
if (tmpcnt >= 5000)
{
PORTC ^= (1 << PD5);
tmpcnt = 0;
}
}
return(0);
}


ISR (SIG_INTERRUPT0)
{
tmpcnt++;
}



Hat hier jemand nen Verbesserungsvorschlag, Primär mal für die Hardware, um dort ein sauberes Signal zu bekommen?

shaun
01.08.2008, 23:45
So dürfte sich der Kondensator über den Eingangsstrom des 339 aufladen und der Ausgang immer log. 1 sein, vielleicht hält sich das auch die Waage mit den negativen Komponenten des Eingangssignals. So oder so gehört da aber ein Widerstand zB nach Masse hinter den Kondensator, um einen definierten Gleichstromfad zu haben. Und damit es ohne Signal nicht "flattert" noch einer nach +Ub, so dass am nichtinvertierenden Eingang immer etwas mehr als 0V anliegen (100mV?) Dann schaltet der Komparator nur bei negativen Signalanteilen <-100mV.

Besserwessi
02.08.2008, 10:35
Wenn nur diese einfache Schaltung (bis auf den fehlenden Widerstand nach GND) gebraucht wird, könnte man auch den AVR internen Komperator nehmen.

Jaecko
02.08.2008, 13:15
Also hab jetzt mal den nicht-inv. Eingang (+) mit 10k gegen Masse gezogen; jetzt kommt ein sauberes Signal hinten raus.

Wo der inv. Eingang (-) liegt, scheint egal zu sein. Zumindest verändert sich das Oszibild nicht in Abhängigkeit der Spannung (zwischen 0-5V)

Der interne Komparator kann leider nicht (mehr) verwendet werden, da die Pins schon anderweitig belegt sind.

Besserwessi
02.08.2008, 15:29
Die Spannung am inv. Eingang sollte schon einen Unterschied machen.
Den internen Comperator im AVR kann man auch über normale AD Eingänge erreichen. Dann ist allerdings die Vergleichsspannung fest auf z.B. 1,2 V gelegt (könnte vom AVR Typ abhängen?).

Jaecko
03.08.2008, 15:06
Also die Hardware läuft jetzt fehlerfrei.

Jetzt gibt nur noch in der Software ein paar Probleme.
Hab mal den Basic-Code von http://home.arcor.de/output/elektronik/5ton-AVR.pdf versucht zu verwenden; klappt "eigentlich" auch. "Eigentlich" deswegen, weil die erkannte Frequenz nicht mit der wirklichen übereinstimmt.
Im angefügten Code muss ich von den #defines für FTONx noch etwas abziehen, damits stimmt.
Also werden z.B. 1060 Hz gesendet, werden diese als ca. 1040 erkannt; bzw. ich muss 1080 senden, damit 1060 erkannt werden.

Evtl ne Idee dazu? Ich denk mal, dass diese zusätzliche Zeit einfach dadurch zustande kommt, dass die ISR ja erst mal aufgerufen werden muss etc.


EDIT: Mal wieder den Code vergessen:



// µC: ATMega8
// Takt: 8MHz intern
// Pwr: 5 VDC

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <string.h>
#include "glob_type.h" // Datentypen ui8_t, ui16_t, etc.

#define FTON1 1060 -20
#define FTON2 1160 -20
#define FTON3 1270 -20
#define FTON4 1400 -20
#define FTON5 1530 -23
#define FTON6 1670 -25
#define FTON7 1830 -25
#define FTON8 2000 -25
#define FTON9 2200 -25
#define FTON0 2400 -30
#define FTONR 2600 -30



#define VSF_IS_NEW (statusflags & 0x01)
#define VSF_IS_VALID (statusflags & 0x02)
#define VSF_IS_GOTOMAIN (statusflags & 0x04)

#define VSF_SET_VALNEW() (statusflags |= 0x01)
#define VSF_SET_VALOLD() (statusflags &= ~0x01)

#define VSF_SET_GOTOMAIN() (statusflags |= 0x04)
#define VSF_CLR_GOTOMAIN() (statusflags &= ~0x04)

#define VSF_SET_VALID() (statusflags |= 0x02)
#define VSF_SET_INVALID() (statusflags &= ~0x02)

#define TIMEOUT_MAX_VALUE 40 // Max. Timeout-Zyklen
#define AVG_VALUES 15 // Anzahl Werte für Durchschnittsbildung


ui32_t lgFreq; // "Rohfrequenz" nach 1. Berechnung
ui32_t lgFreqSum; // Summe für Durchschnittswert-Bildung

volatile ui16_t T1Value; // Gemessener Zählwert von T1 bei Signal an INT0
ui16_t freqs[AVG_VALUES]; // Array für Mittelwertbildung
ui16_t freq_avg; // Durchschnittsfrequenz
ui16_t fBorderLo;
ui16_t fBorderHi;

ui8_t TimeOut; // Timeout-Variable
volatile ui8_t statusflags; // Enthält Status-Bits (Value status flags, VSF):
// Bit 0: Neuer Messwert per INT0 eingegangen
// Bit 1: Messwert ungültig (max. Zähldauer überschritten)
// Bit 2: "Goto Main"-Ersatz
ui8_t Sequence[5]; // enthält empfangene Sequenz
ui8_t SeqCnt; // Anzahl Töne in Sequenz
ui8_t Tone; // Aktuell korrekt erkannter Ton
ui8_t Tone_Old; // Zuletzt korrekt erkannter Ton
ui8_t icnt; // allg. Zählvariable


int main(void)
{
// einige Pins von PortC als Ausgang für Status-LEDs:
DDRC |= (1 << PC0) | (1 << PC1) | (1 << PC2) | (1 << PC3) | (1 << PC4) | (1 << PC5);
DDRB |= (1 << PB1) | (1 << PB2) | (1 << PB3) | (1 << PB4) | (1 << PB5);

// Timer 1 setzen
TCCR1A = 0; // Standard Timer
TCCR1B = (1 << CS10); // Prescaler 1
TCNT1 = 0; // Startwert 0
TIMSK |= TOIE1; // Interrupt für Überlauf aktiv

// INT0 auf steigender Flanke
GICR |= (1 << INT0);
MCUCR |= (1 << ISC01) | (1 << ISC00);

// Interrupts global zulassen
sei();

// ************************************************** **********************************************
// MAIN LOOP START
// ************************************************** **********************************************

PORTC = (1 << PC5); _delay_ms(100); PORTC = 0;
PORTC = (1 << PC4); _delay_ms(100); PORTC = 0;
PORTC = (1 << PC3); _delay_ms(100); PORTC = 0;
PORTC = (1 << PC2); _delay_ms(100); PORTC = 0;
PORTC = (1 << PC1); _delay_ms(100); PORTC = 0;
PORTC = (1 << PC0); _delay_ms(100); PORTC = 0;

PORTB = (1 << PB5); _delay_ms(100); PORTB = 0;
PORTB = (1 << PB4); _delay_ms(100); PORTB = 0;
PORTB = (1 << PB3); _delay_ms(100); PORTB = 0;
PORTB = (1 << PB2); _delay_ms(100); PORTB = 0;
PORTB = (1 << PB1); _delay_ms(100); PORTB = 0;


while(1)
{
VSF_CLR_GOTOMAIN();
_delay_ms(1); // kleine Pause

// Prüfen, ob T1Value in sinnvollem Bereich liegt:
// 800 Hz => T1Value = 10000
// 3000 Hz => T1Value = 2666
if ((VSF_IS_VALID) && (VSF_IS_NEW))
{
if ((T1Value > 10000) || (T1Value < 2666))
{
// Wenn Wert ausserhalb: ungültig:
VSF_SET_INVALID();
}
}

// Folgendes nur, wenn: Wert gültig + in sinnvollem Bereich
if ((VSF_IS_VALID) && (VSF_IS_NEW))
{
if (T1Value != 0) { lgFreq = F_CPU / T1Value;} // "Rohfrequenz" berechnen
T1Value = 1; // Worst Case: Division durch 0 verhindern.

TimeOut++;
if (TimeOut >= TIMEOUT_MAX_VALUE)
{
memset(Sequence, 0x00, 5);
Tone_Old = 0;
TimeOut = 0;
SeqCnt = 0;
PORTB = 0;
PORTC = 0;
}

// Wertearray weiterschieben; ältesten Wert verwerfen
for (icnt = AVG_VALUES-1 ; icnt > 0 ; icnt--)
{
freqs[icnt] = freqs[icnt-1];
}

// Errechnete Frequenz in Array ablegen
freqs[0] = (ui16_t) lgFreq;

// Durchschnitt berechnen
lgFreqSum = 0;
for (icnt = 0 ; icnt < AVG_VALUES ; icnt++)
{
lgFreqSum += freqs[icnt];
}
freq_avg = lgFreqSum / AVG_VALUES;

// Überprüfen, ob Durchschnitt "gehalten" wird
fBorderLo = ((ui16_t) lgFreq) - 10;
fBorderHi = ((ui16_t) lgFreq) + 10;

if ((freq_avg > fBorderLo-1) && (freq_avg < fBorderHi+1))
{
Tone = 0;
if ((freq_avg > FTON1 - 10 ) && (freq_avg < FTON1 + 10)) { Tone = '1'; }
else if ((freq_avg > FTON2 - 10 ) && (freq_avg < FTON2 + 10)) { Tone = '2'; }
else if ((freq_avg > FTON3 - 10 ) && (freq_avg < FTON3 + 10)) { Tone = '3'; }
else if ((freq_avg > FTON4 - 10 ) && (freq_avg < FTON4 + 10)) { Tone = '4'; }
else if ((freq_avg > FTON5 - 10 ) && (freq_avg < FTON5 + 10)) { Tone = '5'; }
else if ((freq_avg > FTON6 - 10 ) && (freq_avg < FTON6 + 10)) { Tone = '6'; }
else if ((freq_avg > FTON7 - 10 ) && (freq_avg < FTON7 + 10)) { Tone = '7'; }
else if ((freq_avg > FTON8 - 10 ) && (freq_avg < FTON8 + 10)) { Tone = '8'; }
else if ((freq_avg > FTON9 - 10 ) && (freq_avg < FTON9 + 10)) { Tone = '9'; }
else if ((freq_avg > FTON0 - 10 ) && (freq_avg < FTON0 + 10)) { Tone = '0'; }
else if ((freq_avg > FTONR - 10 ) && (freq_avg < FTONR + 10)) { Tone = 'R'; }
else
{ VSF_SET_GOTOMAIN(); }
}
else
{
VSF_SET_GOTOMAIN();
}

if (!VSF_IS_GOTOMAIN)
{
switch (Tone)
{
case '1':
PORTB = 0;
PORTC = (1 << PC5);
break;
case '2':
PORTB = 0;
PORTC = (1 << PC4);
break;
case '3':
PORTB = 0;
PORTC = (1 << PC3);
break;
case '4':
PORTB = 0;
PORTC = (1 << PC2);
break;
case '5':
PORTB = 0;
PORTC = (1 << PC1);
break;
case '6':
PORTB = 0;
PORTC = (1 << PC0;
break;
case '7':
PORTB = (1 << PB5);
PORTC = 0;
break;
case '8':
PORTB = (1 << PB4);
PORTC = 0;
break;
case '9':
PORTB = (1 << PB3);
PORTC = 0;
break;
case '0':
PORTB = (1 << PB2);
PORTC = 0;
break;
case 'R':
PORTB = (1 << PB1);
PORTC = 0;
break;
default:
PORTB = 0;
PORTC = 0;
}
}
// Wert abgearbeitet und damit alt:
VSF_SET_VALOLD();
}



}
// ************************************************** **********************************************
// MAIN LOOP ENDE
// ************************************************** **********************************************
return(0);
}


ISR (SIG_OVERFLOW1) // Interrupt Timer 1 Überlauf
{
//Letzter Impuls liegt zu lange zurück => Wert verwerfen/ungültig
VSF_SET_INVALID();
}

ISR (SIG_INTERRUPT0)
{
T1Value = TCNT1; // Wert sichern
VSF_SET_VALID(); // Wert gültig
VSF_SET_VALNEW(); // Neuer Wert
TCNT1 = 0; // Timer zurücksetzen
}

zerush
03.08.2008, 15:14
Das liegt möglicherweise ganz einfach daran, dass du den internen Takt benutzt, der ist meistens ziemlich ungenau! Wenn du statt dessen einen Quarz nimmst, wird das Ergebnis genauer!
Alternativ kannst du natürlich den Fehler software-technisch kalibrieren...

Jaecko
03.08.2008, 15:39
Also bisher ist der fehler softwaretechnisch ausgeglichen.
Dass der interne Takt ungenau ist, wusst ich zwar. Aber dass der gleich so schief geht... Bei einem meiner ersten Projekte (Binär-Uhr) hab ich auch nur den internen Takt verwendet, dabei war pro Tag nur ne Abweichung von ca. 10 Sekunden drin, also rechnerisch ca. 0,01%.

In dem Bereich des Fehlers der Software hier isses aber etwa der 100-fache Fehler.


Ne andere, etwas aufwändigere Idee wäre, für jeden Ton einfach nen einzelnen NE567 zu verwenden (wären halt dann 11 Stück). Nur weiss ich nicht, was da genauer ist... ein ATMega + Quarz und "guter" Software oder ein NE567 mit den richtigem Bauteilgemüse aussen rum.

zerush
03.08.2008, 15:52
Also ich würde es einfach mal mit einem Quarz probieren, das ist ja recht schnell und einfach gemacht.

Die internen Oszillatoren sind halt mal besser, mal schlechter, das kann sich von Chip zu Chip ändern.

At 5V, 25°C and 1.0 MHz Oscillator frequency
selected, this calibration gives a frequency within ± 3% of the nominal frequency.

Besserwessi
03.08.2008, 21:02
Der NE567 hat nur eine begrentzte Trennschärfe. Außerdem hat man da immernoch die Temperaturabhängigkeit. Abgesehen vom Aufwand wird da der Controller (mit Quarz) mit einem guten Programm deultich besser. Selbst mit dem internen Takt (mit Kalibrierung) sollte man etwa die Qualität des NE567 erreichen.

Jaecko
03.08.2008, 21:03
So..., hab jetzt mal nen 16MHz-Quarz angeschlossen und siehe da, kaum füttert man 1060 Hz, schon werden auch 1060 Hz erkannt. Perfekto.

Besserwessi
03.08.2008, 21:09
Der NE567 hat nur eine begrentzte Trennschärfe. Außerdem hat man da immernoch die Temperaturabhängigkeit. Abgesehen vom Aufwand wird da der Controller (mit Quarz) mit einem guten Programm deultich besser. Selbst mit dem internen Takt (mit Kalibrierung) sollte man etwa die Qualität des NE567 erreichen.

je nach art des Audio-signals sollte man eine etwas aufwendigere Auswertung nutzen. Wenn das nicht eine Reine Frequenz ist, ist nicht sicher das genau eine Flanke pro Periode erzeugt wird. Durch das sehr begrenzte RAM ist es aber nicht ganz einfach hier per Software tolleranter zu werden.