Forum: Mikrocontroller und Digitale Elektronik FFT auf ATmega128


von Matthias B. (caleb)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe bei youtube ein nettes video gesehen 
(http://www.youtube.com/watch?v=GTb6RsJd2yc) und hab mir gedacht diesen 
Aufbau einfach mal nachzubauen.
Ich hab mir dann das Projekt einfach, wie in seiner Notiz beschrieben, 
bei sourceforge geladen. http://sourceforge.net/projects/avrfftlcd/

Meinen ATmega128 betreibe ich auf einem STK501 mit externem 16MHz Quarz.
Die UART Kommunikation (er meldet das UART, LCD und ADC erfolgreich 
initialisiert worden) funktioniert auch soweit, doch sieht es bei mir 
nicht so schön aus wie im Video =/ picture attached

Hab die lcd.h an meine Ports angepasst ...
1
#define  DATA_PORT   PORTC
2
#define  DATA_DDR    DDRC
3
4
#define  CTRL_PORT   PORTE
5
#define  CTRL_DDR    DDRE
6
#define  CTRL_RST    7 
7
#define  CTRL_CS2    4 
8
#define  CTRL_CS1    3 
9
#define  CTRL_E      2 
10
#define  CTRL_RW     1 
11
#define  CTRL_RS     0 
12
#define  CTRL_BL     5

Leider wird weder ein ordentliches Spektrum noch eine vernünftige 
Signalform angezeigt.

Ich bin ganz ehrlich und hätte auch nicht gedacht, dass es auf anhieb 
funktioniert doch bin ich ein wenig überfragt an was es liegen könnte.

Irgendwie scheint auch die linke LCD-Seite zu "hinken" ...

Würde mich freuen, wenn mir jemand ein Denkanstoß in die richtige 
Richtung geben könnte =)

von holger (Gast)


Lesenswert?

Das wird wohl hier dran liegen;)

18                 .global  lcd_delay_us
  20                 lcd_delay_us:
  21                 .LFB2:
  22                 .LM1:
  23                 /* prologue: frame size=0 */
  24                 /* prologue end (size=0) */
  25                 .LVL0:
  26                 /* epilogue: frame size=0 */
  27 0000 0895          ret
  28                 /* epilogue end (size=1) */
  29                 /* function lcd_delay_us size 1 (0) */


lcd_delay_us() wird komplett wegoptimiert.
Mach i und j mal volatile. Stimmen tun die Zeiten
dann aber wohl auch nicht.

Probiers besser mal mit _delay_us() aus util/delay.h.

von Matthias B. (caleb)


Lesenswert?

Das sieht doch viel besser aus =)
Habs gleich mit _delay_us() gemacht.
Aber die Zeiten stimmen noch nicht ... kuck ich mir gleich nochmal an.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Wenn du die avr-libc 1.7.0 aus dem Zeitraum vor Mitte November 2010 
hast, denk an den delay-Bug in _delay_ms() und _delay_us() und dessen 
Bugfix: 
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#avr-libc_Versionen_ab_1.6

von Matthias B. (caleb)


Lesenswert?

Erstmal vielen Dank für Eure Hilfe =)

Ich hätte da aber noch ein paar grundlegende Fragen.

1)
In der dazugehörigen ReadMe schreibt er was von ...

>The ADC clock is set to XTAL frequency/128 and "A normal conversion
>takes 13 ADC clock cycles",which means the sample rate is about 9.6 kHz

Das leuchtet mir auch soweit ein ...
16MHz-CPU-Clock/128-Prescaler/13Zyklen = 9,61kHz Samplerate.
Aber warum initialisiert er dann den Prescaler auf 64?
1
ADCSRA = ((ADCSRA & 0b11111000) | 0b00000110); // clk/64
Oder täusche ich mich?
Dann würde der ADC mit 250kHz fahren ... und was hätte das zur Folge?
"Nur" einen erhöhten Fehler beim Samplen?

2)
>Chan developed this nice FFT routines for megaAVRs. Number of sample points 
>(FFT_N) can be change in ffft.h

Wenn ich da was anderes einstelle als N = 256, z.B. 64 oder 512, kommt 
nur ein gepflegter Zebrastreifen auf dem LCD.
Was könnte da das Problem sein?

Habt ein wenig Nachsehen mit mir =)
Bin mehr oder weniger Anfänger und würde mich wieder über Denkanstöße 
freuen =)

Grüße

von holger (Gast)


Lesenswert?

>Aber warum initialisiert er dann den Prescaler auf 64?

Das tut er tatsächlich;) Kannst du ja ändern.

>Dann würde der ADC mit 250kHz fahren ... und was hätte das zur Folge?

200kHz ist das was im Datenblatt steht für volle 10Bit.

>"Nur" einen erhöhten Fehler beim Samplen?

Ja.

Noch ein Tip:

Nimm aus lcd_print_point(), lcd_print_signal() und lcd_print_column()
mal das lcd_update() raus. Dann gehts zügiger zur Sache.

von holger (Gast)


Lesenswert?

>Wenn ich da was anderes einstelle als N = 256, z.B. 64 oder 512, kommt
>nur ein gepflegter Zebrastreifen auf dem LCD.
>Was könnte da das Problem sein?

64 müsste gehen. Mach mal ein Clean Build. Also alles
neu übersetzen. Bei 512 fliegt dir das RAM um die Ohren;)

von Matthias B. (caleb)


Lesenswert?

Hab Dank holger.
Läuft jetzt schön flüssig.
Aber eine primitive Frage bleibt mir leider nicht erspart =/

Was zeigt mir denn das Display an?

Nochmal kurz zusammengefasst:

Ich taste das Analogsignal mit 9,61 kHz ab.
Ich freu mich und weiß, dass ich den AD besser mal nicht mit mehr als 
4,8kHz stressen sollte.

Ich nutze die 256-Punkte-FFT und lass mir die ersten 128 Spektralwerte 
ausgeben. "Zufällig" ist ja mein LCD auch 128 Dots breit.
Nun hab ich ein wenig Google benutzt und kann mir daraus ableiten, dass 
meine Frequenzauflösung delta_f = f_abtast / N ist.
Das würde dann ja rund 37,5 Hz Frequenzauflösung entsprechen, oder?
Damit könnte/müsste/sollte ich ja die 4,8kHz auf meinem 128Punkt-LCD 
darstellen können.

Nun komme ich aber nur bis knapp 3MHz und dann bin ich schon am 128ten 
Punkt angelangt.

Wo liegt denn da bitte mein Denkfehler?

Grüße

von holger (Gast)


Lesenswert?

>Nun komme ich aber nur bis knapp 3MHz und dann bin ich schon am 128ten
>Punkt angelangt.

Du meinst 3kHz?

>Wo liegt denn da bitte mein Denkfehler?

Die Wandlung dauert halt etwas länger. Bei mir wird
(gemessen) mit 8,93kHz gesampelt. Liegt wohl am Schleifenoverhead
und möglicherweise auch am SingleConversion. Der letzte Balken
ist bei mir bei 4,5kHz. Passt also.

Wenn du den Zebrastreifen loswerden möchtest geh mal in lcd_cls()
und ändere folgende Zeile:

 vram[page][y] = 1 + page;

in

 vram[page][y] = 0;

von Matthias B. (caleb)


Lesenswert?

holger schrieb:
>>Nun komme ich aber nur bis knapp 3MHz und dann bin ich schon am 128ten
>>Punkt angelangt.
>
> Du meinst 3kHz?
Jo meine ich. Sorry

Wie hast du denn die Samplerate gemessen?

Mit deinen Werten würde ich mich auch abfinden.
Ich frag mich nur warum das bei mir so stark abweicht =/

von holger (Gast)


Angehängte Dateien:

Lesenswert?

>Wie hast du denn die Samplerate gemessen?

Pin toggeln und Osci dran.

>Mit deinen Werten würde ich mich auch abfinden.
>Ich frag mich nur warum das bei mir so stark abweicht =/

Keine Ahnung. Ich hab aber schon ein bisserl am Code
rumgefummelt. Inzwischen läufts im FreeRunning Mode
mit 9600Hz Samplerate. Siehe Anhang.

von holger (Gast)


Lesenswert?

>>Ich frag mich nur warum das bei mir so stark abweicht =/

>Keine Ahnung.

Nimm das sendchar aus der Sampleschleife;)

von Matthias B. (caleb)


Lesenswert?

Ok dank dir. Das läuft ja soweit ganz gut.

Wenn mich nun nur Frequenzen bis 1kHz interessieren bzw. eine bessere 
Auflösung bis 1kHz?
Wie geht man da vor?

-> dann müsste ich mit mind. 2kHz abtasten
-> die Samplerate errechnet sich ja aus Clock/Prescaler/Zyklen

Die einzige Variable an der ich drehen kann sind ja die Zyklen oder?
Die normale Wandlung dauert 13 Zyklen ... kann ich diesen Prozess 
definiert mit Zyklen "verlängern"? Damit meine Samplerate auf rund 2kHz 
bekomme?

Quasi: 16MHz/128/(13+X)=2kHz
In diesem Fall könnte ich ja den Prozess um 49 Zyklen "verlängern" 
(wenns überhaupt funktioniert?) und komme dann auf eine Auflösung von 
rund 8Hz.

Oder bin ich da falsch rangegangen?

Edit: Irgendwelche NOPs evtl.?

von holger (Gast)


Lesenswert?

>Wenn mich nun nur Frequenzen bis 1kHz interessieren bzw. eine bessere
>Auflösung bis 1kHz?
>Wie geht man da vor?

Siehe unten.

>-> dann müsste ich mit mind. 2kHz abtasten

Richtig.

>-> die Samplerate errechnet sich ja aus Clock/Prescaler/Zyklen

Nur mit dem Prescaler kommst du aber nicht auf die Samplerate.

So müsste das gehen:
1. ADC auf SingleConversion einstellen
2. Timer auf CTC mit 0.5ms Interruptrate einstellen
3. Im Timerinterrupt Conversion starten
4. Im ADC Interrupt die Daten abholen

Mit dem Timer kann man dann verschiedene Sampleraten einstellen.

von Matthias B. (caleb)


Lesenswert?

Aso ... dank Dir.
Werds wohl heut nicht mehr schaffen es auszuprobieren (Geschenke kaufen 
etc.)

Probier ich gleich nach den Feiertagen mal aus.

Frohes Fest
Matze

von Matthias B. (caleb)


Lesenswert?

Ich hab mir das nun nochmal angekuckt =)

holger schrieb:
> So müsste das gehen:
> 1. ADC auf SingleConversion einstellen
> 2. Timer auf CTC mit 0.5ms Interruptrate einstellen
> 3. Im Timerinterrupt Conversion starten
> 4. Im ADC Interrupt die Daten abholen

1) Der Timer stand/steht auf SingleConversion

2) Hab mir den 8bit-Timer/Counter0 herbei genommen
1
  // Timer 0 in CTC-Modus bringen
2
  TCCR0 |= (1<<WGM01); // CTC Modus  
3
  
4
  TCCR0 |= ((1<<CS01) | (1<<CS00)); // Timer0 Prescaler 64
5
6
  // Timer0 bis 125 zählen lassen ... müsste 0,5ms entsprechen
7
  OCR0 = 250-1;   //125-1;
8
      
9
  // CTC-Interrupt erlauben
10
  TIMSK |= (1<<OCIE0);

Tsja bei den Interrupts bin ich ein wenig ins straucheln geraten ...
Letztendlich bin ich bei folgender Variante angelangt ...
1
ISR (TIMER0_COMP_vect)
2
{
3
    flag = 1;
4
}
Von dem Interrupt lass ich mir halt die volatile Variable "flag" auf 1 
setzen, um dann ...
1
void adc_capture (unsigned char channel, unsigned int *voltage)
2
{
3
  unsigned int i;
4
5
  ADMUX = 0x40 + channel;  // selects single-ended conversion on pin PF0 ~ PF7 (ADC0 ~ ADC7)
6
              // selects AVCC as Vref
7
              // selects right adjust of ADC result
8
                        
9
  for (i = 0; i < FFT_N; i++)  // averaging the ADC results
10
  {
11
    while (flag != 1)
12
    {
13
         
14
    }
15
    
16
    sbi(ADCSRA,ADSC); // start a conversion by writing a one to the ADSC bit (bit 6)
17
    while(!(ADCSRA & 0b00010000)) // wait for conversion to complete: ADIF = 1
18
      {
19
        //sentchar1('.');    // for debugging
20
      }
21
    flag = 0;
22
    *voltage++ = ((ADCL) | (ADCH<<8));
23
    
24
  }
25
}
immer mit Timer-Interrupt die Conversion zu starten.

Den Interrupt hab ich vor/nach dem Aufruf von adc_capture an/aus 
geschalten.
1
  sei();
2
  
3
  // ADC 5v to 10-bit digital (0x00 - 0x3FF)
4
  adc_capture(channel, voltage);
5
    
6
  cli();

Nun mal zur Sache =)
Es ist sicherlich völlig grauenhaft programmiert, aber ich bin wie 
gesagt noch recht frisch in der Materie.
Mein LCD löst mir jetzt den Frequenzbereich von 0-1000 Hz nun mit einem 
Frequenzraster von 8Hz auf ...
ABER nur wenn ich den Zähler bis 255 laufen lasse ? =/
Warum denn das?
Ich arbeite mit einem Clock von 16MHz den ich, wie oben gezeigt, durch 
64 teile. Dann komme ich auf 250kHz, so das mein Timer/Counter0 bis 125 
zählen müsste um die von mir gewollte 0,0005s Interruptrate zu 
erreichen.
Wenn ich bis 125 zählen lasse stellt mir das Display 2MHz dar.
Wo hab ich denn den Faktor 2 liegen lassen?

Grüße Matze
#hält für verbale Ohrfeigen seine Wange hin#

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.