Forum: Digitale Signalverarbeitung / DSP / Machine Learning Subsampling von 50kHz Signal mit ADC (76.9kSPS)


von Claas H. (black_jacky)



Lesenswert?

Hi,

ich bin grade dabei eine Schaltung + Software zur Impedanzmessung 
aufzubauen.
In diesem Thread habe ich meine Grundidee vorgestellt:
Titel Beitrag "Phasenerkennung 50kHz Sinus Signal"
Jetzt sitze ich an der Umsetzung der Software um mein periodisches 
Signal zu erfassen mit Hilfe von Subsampling.
Verwendet wird der uC ATmega328p mit einer Taktrate von 16Mhz un der 
intern ADC mit einem Takt von 1Mhz (entspricht 76,9kSPS bei 8Bit 
Auflösung).
Für den Anfang erzeuge ich mir ein Rechtecksignal, welches ich später 
durch einen TP in einen Sinus umwandle. Vorerst messe ich bei meinem 
aktuellen Aufbau aber das Rechtecksignal direkt.
Das Signal wir mit Hilfe des Timer2 erzeugt (Ohne Prescale) und toggelt 
den Pin PB8 mit Hilfe des Compar2A (Vergleichswert 159 -> alle 100kHz). 
Somit erhalte ich am PB3 ein 50kHz Rechteck Signal.

Nun meine Idee zum Subsampling:
Ich verwende den Compare2B um zu einem definierten Zeitpunkt bezogen auf 
das Rechtecksignal die AD-Wandlung zu starten. Den Wert vom Compare2B 
verändere ich Schrittweise von 4-156 in 8er Schritten.
Beim Timer2_CompareB_Interrupt starte ich die AD-Wandlung.
Dies mache ich einmal für den Teil wo das Rechteck-Signal am Ausgang 0 
ist und einmal wo es 1 ist.
Somit erhalte ich jeweils 20 Werte pro Zustand vom Rechtecksignal.
Das ganze wiederhole ich 10-mal und bilde so den Mittelwert der 
Messwerte und schreibe diese in ein Array, welches ich mir am Ende 
anzeigen lasse.

Nun zu meinem Problem: Ich bekomme die Werte wiederholbar gemessen, nur 
haben die gemessenen Werte nur mit sehr viel Fantasy etwas mit einem 
Sinus zu tun... Im Anhang die geplotteten Werte.
Hier noch mein Programm-Code:
1
volatile uint16_t convert = 0;
2
volatile uint8_t adc_aktiv = 0, sin_pin_value = 0, signal_half = 0;
3
volatile uint16_t adc_messwerte[40][2];
4
5
void sin_sig_init( void )
6
{
7
  TCCR2A = (1<<COM2A0)|(1<<WGM21);        //OC2A auf toggle bei Compar Match setzen, Modus 2 wählen (CTC)
8
  OCR2A = F_CPU/(2*F_SIN)-1;            //Zähler einstellen, bis zu dem gezählt werden soll
9
  DDRB |= 1<<PB3;                  //Pin PB3 als Ausgang definieren (OCRA mapped on PB3)
10
}
11
12
void sin_sig_start( void )
13
{
14
  TCCR2B = 1<<CS20;                //Prescale auf "No Prescaling"
15
}
16
17
void sin_sig_stop( void )
18
{
19
  TCCR2B = 0;                    //Prescale auf 0 -> Timer deaktivieren
20
}
21
22
void adc_init(void)
23
{
24
  ADMUX = (1<<REFS0);                //Analog Refferenz auf extern VCC
25
  ADMUX |= (1<<ADLAR);              //linksbündig ausgerichtet um einfacher die obersten 8Bit auszulesen
26
  ADCSRA = (1<<ADEN);                //ADC einschalten
27
  ADCSRA |= (1<<ADPS2);              //Prescale der Systemclock auf 16 setzen (16MHz/16=1MHz)
28
  
29
}
30
31
void adc_set_pin (uint8_t pin)
32
{
33
  ADMUX &= (1<<REFS1)|(1<<REFS0)|(1<<ADLAR);    //MUX3..0 auf 0 setzen
34
  ADMUX |= pin;                  //MUX3..0 auf den jeweiligen Eingangspin setzten
35
}
36
37
void adc_stop(void)
38
{
39
  if(adc_aktiv) adc_aktiv = 0;          //falls noch nicht gestoppt wurde, auf stoppen setzen
40
  ADCSRA &= ~(1<<ADSC);            //stoppen der Analog-Digital Umwandlung
41
}
42
43
void adc_start(void)
44
{
45
  adc_aktiv = 1;              //auf starten setzten
46
  signal_half = 0;            //auf die erste Singalhälfte setzen
47
  for (int i = 0; i<40; i++)
48
  {
49
    adc_messwerte[i][0] = 0;
50
    adc_messwerte[i][1] = 0;
51
  }
52
  ADCSRA |= (1<<ADIE);          //Interrupt einschalten
53
  OCR2B = 4;                //Compare Wert für Timer2CompareB setzen
54
  TIMSK2 |= (1<<OCIE2B);          //Compare Interrupt aktivieren
55
}
56
57
void adc_dummy_read(void)
58
{
59
  adc_aktiv = 0;
60
  ADCSRA |= (1<<ADIE);          //Interrupt einschalten
61
  ADCSRA |= 1<<ADSC;            //Starten der Analog-Digital Umwandlung
62
  _delay_ms(2);              //Warten bis die Werte ausgelesen wurden
63
}
64
65
uint16_t adc_read (uint8_t zeile, uint8_t spalte)
66
{
67
  return adc_messwerte[zeile][spalte];
68
}
69
70
ISR(TIMER2_COMPB_vect)
71
{
72
  sin_pin_value = (PINB & (1<<PB3))>>PB3;    //Speichern des aktuellen Zustands von Pin PB3
73
  if(sin_pin_value==signal_half)
74
  {
75
    ADCSRA |= 1<<ADSC;            //Starten der Analog-Digital Umwandlung
76
    TIMSK2 &= ~(1<<OCIE2B);          //Compare Interrupt deaktivieren
77
  }
78
}
79
80
ISR(ADC_vect)
81
{
82
  TIMSK2 &= ~(1<<OCIE2B);            //Compare Interrupt deaktivieren
83
  uint8_t temp = ((OCR2B+4)/8);
84
  if(adc_aktiv && signal_half)
85
  {
86
    adc_messwerte[temp+19][0] = (adc_messwerte[temp+19][0]+(ADCH))/2;    //Speichern des Wertes der AD Wandlung
87
    adc_messwerte[temp+19][1] = OCR2B +160;  //Startzeitpunkt der Wandlung speichern
88
    if(OCR2B >= 156) 
89
    {
90
      //adc_aktiv=0;      //Wenn die zweite Signalhälfte auch abgetastet ist die Messung deaktivieren
91
      signal_half = 0;
92
      OCR2B = 4;
93
      adc_aktiv += 1;
94
      if(adc_aktiv==10) adc_aktiv = 0;
95
    }
96
    else OCR2B += 8;            //Falls noch nicht vollständig abgetastet Compare2B auf den nächst höheren setzen
97
  }
98
  else if(adc_aktiv)
99
  {
100
    adc_messwerte[temp-1][0] = (adc_messwerte[temp-1][0]+(ADCH))/2;    //Speichern des Wertes der AD Wandlung
101
    adc_messwerte[temp-1][1] = OCR2B;    //Startzeitpunkt der Wandlung speichern
102
    if(OCR2B >= 156)
103
    {                    //wenn 144 erreicht ist (5 Abtastungen der ersten Hälfte)
104
      signal_half = 1;          //Auf die zweite Hälfte des Signals stelle
105
      OCR2B = 4;              //Compare2B Werte zurücksetzen auf 16
106
    }else OCR2B += 8;            //Falls noch nicht vollständig abgetastet Compare2B auf den nächst höheren setzen
107
    
108
  }
109
  else convert = (ADCL)|(ADCH<<8);      //Werte aus dem ADC speichern
110
  //if(start) ADCSRA |= 1<<ADSC;        //erneut starten der Analog-Digital Umwandlung
111
  //else ADCSRA &= ~(1<<ADIE);        //Interrupt ausschalten
112
  if(adc_aktiv)  TIMSK2 |= (1<<OCIE2B);      //Compare Interrupt einschalten
113
}

Und hier mein Aufruf in der Main:
1
int main(void)
2
{
3
  adc_init();
4
  adc_set_pin(PHASE_PIN);
5
  adc_dummy_read();
6
  lcd_init();
7
  key_init();
8
  sin_sig_init();
9
  sin_sig_start();
10
  _delay_ms(5);
11
  adc_start();
12
  do
13
  {
14
    if(get_key_short(1<<KEY0) && !get_key_short(1<<KEY1)) ebene++;
15
    else if (get_key_short(1<<KEY1) && !get_key_short(1<<KEY0)) ebene--;
16
17
    sprintf(zeile1, "E:%02d ", ebene);
18
    sprintf(zeile2, "S:%04d ", adc_read(ebene,0));
19
    sprintf(zeile2+7, "T:%04d ", adc_read(ebene,1));
20
    display_update();
21
  }while(1);
22
}

von Joe F. (easylife)


Lesenswert?

Irgendwie habe ich gerade das Gefühl du bringst da einige Dinge 
durcheinander.
Weisst du wie Subsampling funktioniert? Du benötigst eine sehr genaue 
Zeitbasis und eine Sample&Hold Stufe, die in der Lage ist dein 50 KHz 
Signal abzutasten.
Ausserdem ist ein sehr steilbandiger Bandpassfilter nötig, um 
Aliasfrequenzen auszufiltern.
Einfach das Signal an deinen ADC ranzuhängen und mit 76.9 KHz abzutasten 
funktioniert bei 50 KHz natürlich nicht mehr.
Du kannst max. Frequenzen der halben Samplingfrequenz auflösen, und 
solltest alles darüber auch mit einem externen Tiefpass möglichst steil 
herausfiltern.
Je näher du an FS/2 kommst, desto schlechter wird natürlich die 
Signalform, im Grunde wird es ein Rechteck, und du hast lediglich noch 
die Amplitude als Information.

Eine Phasenmessung zweier 50 KHz Signale mit einem 76.9 KHz ADC ist so 
ziemlich unmöglich.
Ausserdem müssten auch beide ADCs (bzw. die Sample&Hold-Stufen) sehr 
präzise synchronisiert sein.

Warum machst du die Phasenmessung nicht analog?
Dazu beide Signal auf annähernd gleichen Pegel bringen, dann über 
Komparatoren in Rechtecke wandeln, auf ein XOR geben und danach 
Tiefpassfiltern.

Diesen Analogwert kannst du dann mit einem beliebig langsamen ADC mit 
hoher Auflösung messen.

: Bearbeitet durch User
von Possetitjel (Gast)


Lesenswert?

Joe F. schrieb:

> Irgendwie habe ich gerade das Gefühl du bringst da
> einige Dinge durcheinander.

Ähh... ebenso :)

> Ausserdem müssten auch beide ADCs (bzw. die Sample&Hold-
> Stufen) sehr präzise synchronisiert sein.

Nö.
Es genügt, wenn Sender und Empfänger, d.h. Generator und
ADC synchronisiert sind. Das ist hier deshalb der Fall,
weil sich beide innerhalb desselben Mikrocontrollers
befinden.

Wenn ein Timer-Kanal die 50 kHz erzeugt und DERSELBE
Timer-Kanal per Capture/Compare die AD-Wandlung startet,
dann wüsste ich nicht, warum das nicht funktionieren
sollte.

> Warum machst du die Phasenmessung nicht analog?

Weil das ein Haufen analoges Gefrickel ist.
Ich weiss, dass das möglich ist, denn eins unserer Messgeräte
hat das so ähnlich gemacht. Aber ganau deshalb weiss ich auch,
dass das analoges Gefrickel ist.

von Joe F. (easylife)


Lesenswert?

So, hat ein wenig gedauert, bis ich mir deinen anderen Thread 
durchgelesen habe, und glaube verstanden zu haben was zu da vor hast.

Mal ein paar grundsätzliche Überlegungen:

Dein Signal hat 50KHz, das entspricht einer Periodendauer von 20us.
Wenn es dir gelänge, den Samplezeitpunkt deines ADC auf einen 
Prozessortakt genau in Relation zum erzeugten Signal zu starten, ist das 
eine zeitliche Auflösung für dein Subsampling von 1/16 MHz = 62,5ns.
Das entspricht 1,125° deines 50 KHz Signals.
Bei dem von dir zu untersuchenden Bereiches von 0°-25° hast du also eine 
maximal mögliche Auflösung von ca. 23 Phasen-Stufen (1,125° 
Genauigkeit). Ich gehe davon aus, dass du das berücksichtigt hast, und 
du keine höhere Genauigkeit erreichen möchtest.

Um dein Signal mit 16 MHz abzutasten, benötigst du eine Sample&Hold time 
von max. 62,5ns.
Dein ADC wird mit 1 MHz getaktet, und die interne Sample&Hold Stufe 
braucht mind. 1 ADC-Taktzyklus (ich glaube irgendwo gelesen zu haben, 
dass es sogar 2 Zyklen sind).
D.h. die Sample&Hold Stufe deines ADC ist momentan für mindestens 1/1MHz 
= 1us "offen".
Bezogen auf dein 50 KHz Signal entspräche dies 18° des Signals, die von 
der Sample&Hold Stufe bereits gemittelt werden.
Wenn es 2 ADC Zyklen sein sollten, wären es 36°.
Ich glaube dies allein macht deine Messung schon sehr unbrauchbar.

Ob dein Code korrekt arbeitet, kann ich nicht beurteilen, ich finde ihn 
ziemlich schwer zu lesen.
So wie ich es verstehe, samplest du einen Wert, und mittelst ihn mit den 
vorherigen Werten (wobei wohl irgendwie noch der nachträglich ermittelte 
genaue Samplezeitpunkt die Array-Adresse bestimmt...?).
Deine "Mittelung"
1
adc_messwerte[temp+19][0] = (adc_messwerte[temp+9][0]+(ADCH))/2;

reduziert allerdings sofort schon mal die Auflösung auf 1/2, und 
ausserdem wird nicht linear gemittelt, sondern frühere Messwerte 
verlieren mit jedem neuen Messwert zunehmend mehr an Bedeutung (uns zwar 
nicht linear)?
Ich verstehe auch nicht, warum einmal temp+19 und einmal temp+9 genommen 
wird...

Ich würde mal versuchen, die Mittelung wegzulassen, und die Daten 
hintereinander weg in ein Array zu schreiben solange es der Speicher her 
gibt, und dies dann hinterher extern auszuwerten.
Dann kannst du besser beurteilen, ob die Werte Sinn machen.

Zusätzlich wäre es vielleicht mal einen Versuch wert, das Timing der ADC 
Startpunkte zu überprüfen.
Anstatt den ADC zu starten, könntest du mal einen weiteren Output 
high/low togglen lassen, und guckst dir am Oszi an, ob die Startpunkte 
einigermaßen gleichmäßig im 62,5ns Abstand über dein zu messendes Signal 
verteilt sind.

Mit diesem Start-Signal könnte man dann auch eine geeignet schnelle, 
externe Sample&Hold Stufe triggern.

Nachtrag:
Klar ist eine analoge Auswertung etwas "einstellbedürftig", allerdings 
hätte sie in deinem Fall wirklich Vorteile.
Da du ja weisst, dass sich dein zu messendes Signal nicht mehr als 180° 
verschiebt, kannst du auf die XOR Auswertung komplett verzichten.
Alles was du tun müsstest ist das Eingangssignal und das zu 
vergleichende Signal auf eine möglichst gleiche Amplitude zu verstärken, 
und das zu messende Signal zu invertieren.
Durch Mischen, aktives Gleichrichten und Tiefpassfiltern erhältst du 
direkt eine analoge Spannung, die der Phasenlage entspricht.
Mit einem 8-bit ADC könntest du damit theoretisch eine Auflösung von ca. 
0,7° erreichen, da du aber nicht mehr so schnell samplen musst, könntest 
du auch auf 10-bit gehen, und hättest eine Auflösung von ca. 0,18° - 
oder noch genauer, indem du den Messbereich auf <180° einschränkst.

: Bearbeitet durch User
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.