PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 50Hz Millivolt mit Atmel messen?



stfan1409
08.02.2014, 16:57
Hallo Forum,
ich habe eine 0815 Stromzange zum messen von Wechselstrom. Sie wird an ein Multimeter angeschlossen, welches einen proportionalen Spannungswert zum gemessenen Strom anzeigt.
Nun möchte ich diese Werte mit einem µC mitloggen bzw. µC -> RS232 -> PC.

In einer Zeitung habe einen Artikel gefunden, wie man mit einem ATiny13 eine 50Hz Wechselspannung von 2000mVeff misst.

Ich habe aber 3 Spannungen (3 Zangen wegen 3 Phasen).
Nun wär das einfachste einen Mega8 zu nehmen und 3 Analogeingänge zum messen. Den Printbefehl im Programm würde ich so ändern, dass die ersten beiden Werte ohne "CR" gesendet werden und bei letzten Wert wird ein "CR" angehängt, damit der PC erkennt, dass der Datensatz abgeschlossen ist.

Frage:
Messe ich dann die richtigen Werte oder schmeiße ich dann etwas im Programm durcheinander - sprich kommt es auf das Timing an?



'Hier der Original CODE von ELEKTOR 10/2012
'Millivoltmeter 1 mVeff ... 2000 mVeff

$regfile = "attiny13.dat"
$crystal = 1200000
$hwstack = 8
$swstack = 4
$framesize = 4


Dim U1 As Integer
Dim U2 As Integer
Dim U3 As Long
Dim N As Integer

Config Adc = Single , Prescaler = Auto

Start Adc

Open "comb.1:9600,8,n,1,INVERTED" For Output As #1

Do
U2 = 0
For N = 1 To 64
U1 = Getadc(3)
U2 = U2 + U1
Next N

Shift U2 , Right , 3 ' /8
U3 = 0 ' Nullpunkt
For N = 1 To 2780
U1 = Getadc(3)
Shift U1 , Left , 3 ' *8

U1 = U1 - U2
U1 = Abs(U1)
U3 = U3 + U1
Next N ' * 2780

Shift U3 , Right , 12 ' / 4096
Print #1 , U3
Loop
End


Hier noch die Beschreibung zur Funktion:



Für die Untersuchung der hier vorgestellten Verstärker kann man ein
NF-Millivoltmeter gut gebrauchen. Eigentlich kann der A/D-Wandler
im ATtiny13 nur Gleichspannungen messen. Aber durch das Hochlegen
der mittleren Spannung auf 2,5 V und schnelle Messungen
können auch Wechselspannungen untersucht werden. Das Ergebnis
wird seriell an den PC gesendet und kann mit einem Terminalprogramm
angeschaut werden. Das Prinzip des Messprogramms ist einfach. Zuerst wird die Mittenspannung
durch eine Mittelwertbildung bestimmt. Dann folgt eine
schnelle Serie vieler Einzelmessungen, bei der die Mittenspannung
subtrahiert und dann jeweils der Absolutwert gebildet wird. Eigentlich
liegt die kleinste messbare Spannungsstufe des A/D-Wandlers
bei 5 mV. Durch die Mittelung über viele Einzelmessungen kommt
man dennoch bis herunter auf 1 mV. In der eigentlichen Mess-Schleife
werden 2780 Messungen durchgeführt, verarbeitet und aufsummiert.
Die Messpunkte liegen übrigens völlig asynchron zum Messsignal.
Aber die große Anzahl der Messungen und der Zufall führen
zum Ergebnis, solange das Eingangssignal etwa zwischen 50 Hz und
50 kHz bleibt.
Wie kommt es aber zu dieser ominösen 2780? Diese Zahl geht
von einer Referenzspannung von genau 5 V aus und berücksichtigt
schon den Unterschied zwischen arithmetischer Mittelung und echter
Effektivmessung, sodass das Ergebnis im Fall eines Sinussignals
tatsächliche Effektivwerte in mVeff zeigt. Um genaue Effektivwerte
zu ermitteln, müsste man eigentlich das Quadrat der Spannung aufsummieren
und dann später aus dem Mittelwert die Wurzel ziehen.
Damit wäre der ATtiny13 aber überfordert. Deshalb wird hier der
arithmetische Mittelwert der Absolutspannung gebildet. Dieser ist um rund 10 % zu niedrig, genauer gesagt um die Wurzel aus 2 geteilt
durch pi/2, also 0,9003. Ein A/D-Schritt beträgt 5000 mV / 1023 =
4,8876 mV. Außerdem wird der Messwert einmal mit 8 multipliziert
und dann durch 4096 geteilt, also effektiv durch 512 geteilt. Damit
am Ende alles korrekt in mV angezeigt wird, muss man also mit
512 * 4,8876 / 0,9003 = 2780 multiplizieren; und das geht am einfachsten,
wenn man 2780 Messwerte aufsummiert.
Das Ergebnis ist erstaunlich genau. Auch Werte wie 1 oder 2 mVeff
werden stabil angezeigt!

Vielleicht interessiert sich ja noch jemand dafür wie man die günstigen Stromzangen mitloggen kann ;-)

- - - Aktualisiert - - -

Hier noch der geänderte CODE



$regfile = "m8def.dat"
$crystal = 8000000

$hwstack = 8
$swstack = 4
$framesize = 4

$baud = 19200

Dim U1 As Integer
Dim U2 As Integer
Dim U3 As Long
Dim N As Integer


Config Adc = Single , Prescaler = Auto
Start Adc



Do

'Strommessung 1
U2 = 0

For N = 1 To 64
U1 = Getadc(0)
U2 = U2 + U1
Next N

Shift U2 , Right , 3 ' /8
U3 = 0 ' Nullpunkt

For N = 1 To 2780
U1 = Getadc(0)
Shift U1 , Left , 3 ' *8
U1 = U1 - U2
U1 = Abs(u1)
U3 = U3 + U1
Next N ' * 2780

Shift U3 , Right , 12 ' / 4096

Print U3; 'ohne CR senden
'_________________________________________________ ______________________________



'Strommessung 2
U2 = 0

For N = 1 To 64
U1 = Getadc(1)
U2 = U2 + U1
Next N

Shift U2 , Right , 3 ' /8
U3 = 0 ' Nullpunkt

For N = 1 To 2780
U1 = Getadc(1)
Shift U1 , Left , 3 ' *8
U1 = U1 - U2
U1 = Abs(u1)
U3 = U3 + U1
Next N ' * 2780

Shift U3 , Right , 12 ' / 4096

Print U3; 'ohne CR senden
'_________________________________________________ ______________________________



'Strommessung 3
U2 = 0

For N = 1 To 64
U1 = Getadc(2)
U2 = U2 + U1
Next N

Shift U2 , Right , 3 ' /8
U3 = 0 ' Nullpunkt

For N = 1 To 2780
U1 = Getadc(2)
Shift U1 , Left , 3 ' *8
U1 = U1 - U2
U1 = Abs(u1)
U3 = U3 + U1
Next N ' * 2780

Shift U3 , Right , 12 ' / 4096

Print U3 'MIT CR senden
'_________________________________________________ ______________________________


Loop


End

Besserwessi
08.02.2014, 18:58
Im Prinzip könnte die Messung so schon funktionieren - so groß ist der Schritt von 1 Kanal auf 3 Kanäle ja auch nicht. Wie gut das mit den 2780 Punkten für die Messung hinkommt müsste man noch kontrollieren - so ganz einfach ist das aber nicht, weil die Zeit von den Einstellungen des AD-Wandler (per Auto eingestellt) und der Laufzeit abhängt. Das mitteln von 64 Punkten für den DC Offset könnte zu wenig sein - auch hier müsste die Zeit ein Vielfaches der Periode (20 ms) sein, damit es einen richtigen Wert ergibt, sonst gibt es einen Fehler durch das Signal.

Man könnte allerdings noch ein paar Dinge besser machen: Wenn der ADC im Auto-triger Modus läuft, wäre die Wandlung etwas schneller und man hätte eine bessere Kontrolle über die Zahl der Punkte um eine vielfaches von 20 ms zu erreichen.

Die "Gleichrichtung per Absolutbetrag ist schon recht gut umgesetzt, ist aber empfindlich auf Fehler beim Offset. Hier könnte man ggf. auch einfach die letzte Messung für den Mittelwert nutzen und so einen besseren Wert erhalten. Mit etwas aufwand ließe sich auch eine kleine Abweichung (etwa um 1-2 LSB) noch nachträglich korrigieren, wenn man mitzählt wie oft die kritischen Werte nahe 0 auftreten. Damit der Code noch in den Tiny 13 passt könnte man ggf. die Bestimmung der Amplitude in ein Unterprogramm auslagern, das dann 3 mal aufgerufen wird.

Im Prinzip wäre es besser die Gleichrichtung nicht per ABS() zu machen, sondern den Effektivwert in Software zu berechnen. Allerdings befürchte ich, dass der Tiny13 dafür zu langsam ist, und ggf. auch der Platz im Flash knapp wird - das wäre ggf. eine Herausforderung für ASM Programmierer. Mit einem Mega48 geht es, aber der ist bei der Multiplikation deutlich schneller.

wkrug
09.02.2014, 08:30
Um sich die ganze Rumrechnerei und das Abtasten zu ersparen, könnte man auch einen Effektivwert zu DC Wandler wie den AD636 benutzen.
Der gibt an seinem Ausgang ein zum Effektivwert proportionales Ausgangssignal raus und logarithmiert das Ganze sogar noch.
Eventuell könnte man auch nach einem preisgünstigeren Chip suchen.

stfan1409
09.02.2014, 14:01
Hallo,
vielen Dank für die Antworten.

Ein "True RMS-to-DC Converter" wäre toll. Aber für mich zu teuer(benötige 9 St. also 3 x 3 Phasen).
Den µC benötige ich ja auch noch wegen der Seriellenschnittstelle zum PC.


Ich denke, ich werde die Lösung mit dem MEGA8 mal aufbauen und messen(denn das ganze in einen Tiny13 zu quetschen macht wohl keinen Sinn -> Bascom sagt: Code ohne Optimierung = 127%).


@ Besserwessi

Wenn der ADC im Auto-triger Modus läuft, wäre die Wandlung etwas schneller

Da muss ich mich erst mal mit beschäftigen - ich weiß (noch) nicht wie ich das in meinem Programm umsetze.

Besserwessi
09.02.2014, 15:11
Wenn man einen Mega8 oder ähnlich (z.B. Mega48) nutzt, kann der µC auch den Effektivwert in Software berechnen. Das hat gegenüber der Lösung mit analogem RMS-DC Converter und dann auf den 10 Bit AD auch einige Vorteile - einzig die Bandbreite ist halt beschränkt. Der Unterschied zur jetzigen Lösung ist nicht so groß - es werden halt die ADC-Werte einmal direkt aufsummiert und dazu die Quadrate der Abweichungen vom ungefähren Mittelwert (z.B.512). Daraus kann man dann den RMS-wert berechnen.

Den oben gezeigten Code könnt man wohl auch noch einiges kürzen, um von den 127% auf unter 100% zu kommen. Es gäbe ja auch noch Tinys mit 2 K Speicher.

stfan1409
09.02.2014, 15:56
@Besserwessi

Der Unterschied zur jetzigen Lösung ist nicht so groß

naja ich wollte das Programm 1:1 vom Tiny13 übernehmen und mal 3 in den MEGA8 schreiben - so wie ich es am Ende meines ersten Betrages geschrieben hatte.
Das sollte doch gehen. Eventuell danach mit den Verbesserungen, die du in deinem ersten Betrag geschrieben hast.

Besserwessi
09.02.2014, 17:26
Zumindest für die Hardware macht es keinen Unterschied ob man die Auswertung mit dem Betrag oder per RMS Berechnung macht. Im Zweifelsfall kann man beides probieren. Die grobe Programm-strucktur bleibt auch gleich.

Was man auf alle fälle verbessern sollte, wäre es die Zahl der ADC Werte (und damit die Zeit die gemessen wird) an die Netzfrequenz anzupassen. Also dafür zu sorgen das es ein vielfaches von 20 ms ist. Das ist vor allem für den Gleichspannungswert wichtig. Denn kann man auch gut aus der letzten Messung übernehmen, muss also nicht noch einmal extra 64 Werte (wäre sowieso die falsche Zeit und zu kurz) einlesen. Dafür muss man dann zwar teilen, aber so lange dauert das auch nicht.

Mit dem ADC im kontinuierlichen Modus macht es auch nichts wenn man für die Werte etwas rechnen muss, die Rechenzeit geht nicht verloren, solange es schneller als der ADC ist. Mit dem Mega8 (bzw.48) klappt das mit der RMS-routine in C noch bei 2 MHz Takt - könnte also auch noch in Basic bei 8 MHz klappen. Die Berechnung über den Betrag ist auch noch einen Hauch schneller aber mit dem Mega8 auch nicht viel.

Wenn es hilft hätte ich zur RMS Methode Code in C - müsste man dann nur noch nach BASIC übersetzen.

stfan1409
25.07.2014, 15:01
Oh ist das lange her. Aber nun endlich - im Sommerurlaub - hatte ich mal Zeit zum löten ;-)

Als Hardware habe ich einen MEGA8 verbaut, der zur Zeit mit den internen 8 MHz läuft - könnte man ja noch ändern.
Zum testen gebe ich ich eine regelbare Wechselspannung auf den Eingang vom µC und lasse das Ergebniss auf dem PC anzeigen.
Der µC gibt alle 3 Werte einmal pro Sekunde aus. Das ist leider etwas wenig - vier mal pro Sekunde wäre besser.

@Besserwessi: kannst du mir ein Beispielcode geben wie der µC den Effektivwert softwaremäßig berechnet?

Hier mein (etwas langsames) Programm für 3 Eingänge:


'Millivoltmeter 1 mVeff ... 2000 mVeff
'ELEKTOR 2012/10 Seite 76

'VERSION:
'-V0.01: erster Test
'-V0.02: 3 Messungen zusammengefasst

'-------------------------------------------------------------------------------
'Konfiguration µC:
$regfile = "m8def.dat"
$crystal = 8000000

$hwstack = 32
$swstack = 32
$framesize = 32

$baud = 38400 '9600 oder 19200 oder 38400 geht mit 8MHz



'-------------------------------------------------------------------------------
'Ein- Ausgänge:
Ddrc = &B0000000 '1 Ausgang, 0 Eingang


'-------------------------------------------------------------------------------
' Variablen
'-------------------------------------------------------------------------------
Dim U10 As Integer
Dim U20 As Integer
Dim U30 As Long
Dim U11 As Integer
Dim U21 As Integer
Dim U31 As Long
Dim U12 As Integer
Dim U22 As Integer
Dim U32 As Long
Dim N As Integer


Config Adc = Single , Prescaler = Auto
Start Adc


'-------------------------------------------------------------------------------
'Programm:
'-------------------------------------------------------------------------------

Do

'Strommessung 1 - 3
U20 = 0
U21 = 0
U22 = 0

For N = 1 To 64
U10 = Getadc(0)
U20 = U20 + U10

U11 = Getadc(1)
U21 = U21 + U11

U12 = Getadc(2)
U22 = U22 + U12

Next N

Shift U20 , Right , 3 ' /8
U30 = 0 ' Nullpunkt

Shift U21 , Right , 3 ' /8
U31 = 0 ' Nullpunkt

Shift U22 , Right , 3 ' /8
U32 = 0 ' Nullpunkt

For N = 1 To 2780
U10 = Getadc(0)
Shift U10 , Left , 3 ' *8
U10 = U10 - U20
U10 = Abs(u10)
U30 = U30 + U10

U11 = Getadc(1)
Shift U11 , Left , 3 ' *8
U11 = U11 - U21
U11 = Abs(u11)
U31 = U31 + U11

U12 = Getadc(2)
'Print "u12" ; U12
Shift U12 , Left , 3 ' *8
U12 = U12 - U22
'Print "u12-u22" ; U12
U12 = Abs(u12)
'Print "u12abs" ; U12
U32 = U32 + U12
'Print "u32" ; U32
Next N ' * 2780

Shift U30 , Right , 12 ' / 4096
Shift U31 , Right , 12 ' / 4096
Shift U32 , Right , 12 ' / 4096

Print U30 ; "\" ; U31 ; "\" ; U32 'Ausgabe
'_________________________________________________ ______________________________



Loop


End

Besserwessi
26.07.2014, 16:42
Im Anhang mal der Teil zur RMS Berechnung in C. Das ist allerdings keine komplettes Programm, sondern nur ein Ausschnitt für die Wechselspannungsmessung.

Die Idee ist es dem RMS wert wie in der Definition zu bestimmen. U_i sind die einzelnen Spannungswerte:

RMS = Wurzel ( 1/N * Summe [ (U_i-U_dc)²] )

Als kleiner Trick wird für U_dc erst einmal nur ein Schätzwert benutzt, und dann der richtige DC Wert aus den Werte für die Messung neu berechnet und dann nachträglich berücksichtigt.

Die ADC Messung erfolgt im Interrupt - so ist die Abtastrate von der Laufzeit unabhängig und die Zahl der Werte kann passend zur Periodenlänge berechnet werden. Die eigentliche Datenaufnahme erfolgt in der ISR - das Aufsummieren der ADC werte und der Quadrate, jeweils weniger des ersten Schätzwertes für den Nullpunkt.

Wenn man das für 3 Kanäle machen will, wird das leider nicht so trivial - ADC Interrupts und umschalten der Kanäle sind so eine Sache. Da kommt es auf die genaue Zeit an, wann man den Kanal umschaltet - wenn man zu spät dran ist, wird ggf. noch eine Messung mit dem alten Kanal gemacht. Das geht mit dem freilaufenden ADC wenn man aufpasst, ist aber halt nicht mehr einfach - vermutlich würde es darauf hinauslaufen erst den ADC auszulesen, dann die Werte zu verarbeiten, ggf. noch etwas warten und erst dann den Kanal umzuschalten, so dass die Umschaltung dann erst für den übernächsten ADC Wert gilt.

Zur Not ginge es auch mit der getadc() funktion - wobei da einiges an Zeit verloren geht und man die passende Zahl der Werte ausmessen (ggf. Simulator) müsste - bei sehr vielen Punkten ist die genaue Zahl aber nicht mehr so wichtig. Einfacher wäre es die 3 Spannungen nacheinander zu messen.

stfan1409
29.07.2014, 15:02
Hallo,
ich habe da so meine Schwirigkeiten das Programm in C umzuschreiben.
Hier mal mein Ergebnis....



'Millivoltmeter
'VERSION:
'-V0.01: erster Test


'-------------------------------------------------------------------------------
'Konfiguration µC:
$regfile = "m8def.dat"
$crystal = 8000000

$hwstack = 32
$swstack = 32
$framesize = 32

$baud = 38400 '9600 oder 19200 oder 38400 geht mit 8MHz


'-------------------------------------------------------------------------------
'Ein- Ausgänge:
Ddrc = &B0000000 '1 Ausgang, 0 Eingang


'-------------------------------------------------------------------------------
' Variablen
'-------------------------------------------------------------------------------
Dim Adc_samples As Integer '// number of adc samples to do
Dim Sumad As Long
Dim Sumadsq As Long '// data summed up
Dim Minu As Word
Dim Maxu As Word 'richtiges Format?!
Dim Wert As Double

Dim U As Integer
Dim N As Integer ' ; / / Use N As Local Nonvolatile Copy For Speed And Size Reasons !
Dim V As Long '// long is neede for v*v , ASM could use int !
Dim C As Word 'Kanalauswahl


Config Adc = Single , Prescaler = 64 , Reference = Off

'-------------------------------------------------------------------------------
'Programm:
'-------------------------------------------------------------------------------

Do




'ISR(SIG_ADC) // AD interrrut


U = Adcw 'Was ist ADCW??????????
If U > Maxu Then
Maxu = U
End If

If U < Minu Then
Minu = U
End If

Sumad + = U 'wofür das + ?????????????????????
V = U
V = V - 512
Sumadsq + = V * V 'wofür das + ????????????????????????
N = Adc_samples - 1
Adc_samples = N


If N = 0 Then

'// finished sampling: stop further interrupts
Adcsra & = 255 -(1 << Adie) '???????????
Adcsra | =(1 << Adif) '// clear pending int ?????????
End If


Getadc(c) ' // mit Kanal auswahl

Sumad = 0
Sumadsq = 0
Minu = 1024
Maxu = 0
Adc_samples = Acsamples '// passender Wert für ganze Perioden, maximal etwa 10000
Admux = C '| (1 << REFS0);// Kanal und VCC als Ref
ADCSRB = 0;
ADCSRA = (1<< ADEN) + (1<<ADSC)+ (1<<ADATE) + (1<< ADIF) + (1<<ADIE) + 5 ;
'// Autostart + AD clock rate Clock / 2^5
'// enable adc interrupt, clear pending Int,
'// add some delay ?
sei();
while (ADCSRA & (1<< ADIE)) ; // wait for sampling ready
cli();

If Minu < 2 And Maxu > 1020 Then

Overport | =(1 << Overbit) '?????????????

else

Overport & = ~(1 << Overbit) '???????????
End If



'void outac()

Wert =(1.0 / Acsamples) * Sumad -(512.0)
Wert =(1.0 / Acsamples) * Sumadsq - Wert * Wert
'// we may substract internal noise !, e.g. 0.4
'// if (wert <0.0) wert = 0; // Limit possible negative values

Wert = Sqrt(wert); '// RMS Spannung


Loop

Besserwessi
29.07.2014, 16:42
In C gibt es abgekürzte Schreibweise wie x += a . dies steht für x = x + a . So ähnlich auch für &= für die und Verknüpfung - hier zum Löschen von Bits genutzt und |= für die oder Verknüpfung zum setzen von Bits.
Als Beispiel löscht der Teil "Adcsra &= 255 -(1 << Adie)" gerade ein Bit. In BASCOM wäre das dann etwa (so genau kenne ich BASCOM nicht)
adcsra.adie = 0 oder ggf. auch ein Befehl zum ausschalten des ADC interrupts.


Der Teil mit "Overport" ist als Anzeige für einen Überlauf - den Teil kann man weglassen oder später implementieren.

ADCW steht für das Ergebnis der AD Wandlung, als 16 Bit Wert. Ich weiss jetzt nicht ob Bascom das untrestützt und wie es da heißt, ggf. einfach nur ADC.
Der Teil mit ISR(ADC) ist die Interrupt routine für den ADC. Der ADC läuft auch nicht als Single sondern mit Autotrigger. Wenn man es ohne ISR machen will, dann müsste da noch eine Schleife rum. Also etwa als FOR-Schleife (z.B. 5000 mal) drum - dann könnte man den ADC auch per getadc() auslesen statt über adcw.
Die "Schleife" (bzw. ISR) zum einlesen der Werte Summiert einmal die ADC werte auf (Variable Sumad) und dazu die Quadrate in der variable sumadsq.