Stromwandler mit ADC messen

    This site uses cookies. By continuing to browse this site, you are agreeing to our Cookie Policy.

    • Stromwandler mit ADC messen

      Hallo Bascom Freunde,

      Ich möchte AC-Strom per Stromwandler messen und den RMS Wert dann berechnen.
      Die Wechselspannung vom Wandler gelangt per Spannungsteiler zum ADC des Arduino.
      Stromwandler.png



      Der ADC soll ja mit einer Taktrate zwischen 50 und 200kHz betrieben werden also
      der Arduino läuft mit 16MHz geteilt durch 128 (Vorteiler) ergibt einen ADC Takt von 125kHz.
      Im Datenblatt steht das eine Wandlung 13 ADC Takte dauert das wären 0,104ms für eine Wandlung.
      Natürlich wollte ich meine Theoretischen Überlegungen mit einem kleinen Testprogramm überprüfen aber anstand der erwarteten 0,104ms komme ich auf (wert aus Variable T1 * 1/16Mhz) 0,226ms woher kommt dieser unterschied?
      Ist der Ansatz die Messungen mit dem ADC im Singel Modus durchzuführen ok oder muss ich mit dem free runn Modus arbeiten?

      Source Code

      1. Hallo Bascom Freunde,
      2. Ich möchte AC-Strom per Stromwandler messen und den RMS Wert dann berechnen.
      3. Die Wechselspannung vom Wandler gelangt per Spannungsteiler zum ADC des Arduino.
      4. Der ADC soll ja mit einer Taktrate zwischen 50 und 200kHz betrieben werden also
      5. der Arduino läuft mit 16MHz geteilt durch 128 (Vorteiler) ergibt einen ADC Takt von 125kHz.
      6. Im Datenblatt steht das eine Wandlung 13 ADC Takte dauert das wären 0,104ms für eine Wandlung.
      7. Natürlich wollte ich meine Theoretischen Überlegungen mit einem kleinen Testprogramm überprüfen aber anstand der erwarteten 0,104ms komme ich auf (wert aus Variable T1 * 1/16Mhz) 0,226ms woher kommt dieser unterschied?
      8. Ist der Ansatz die Messungen mit dem ADC im Singel Modus durchzuführen ok oder muss ich mit dem free runn Modus arbeiten?
      9. [tt]
      10. $regfile = "m328pdef.dat"
      11. $crystal = 16000000
      12. $hwstack = 64
      13. $swstack = 32
      14. $framesize = 32
      15. $baud = 9600
      16. '$sim
      17. Config Timer1 = Timer , Prescale = 1
      18. Enable Timer1
      19. Enable Interrupts
      20. Config Adc = Single , Prescaler = Auto , Reference = Avcc
      21. Start Adc
      22. Config Portb.5 = Output
      23. Led Alias Portb.5
      24. Declare Function Berechne_i(byval Adc_kanal As Byte) As Dword
      25. Dim T1 As Word
      26. Dim Wandler_nr As Byte
      27. Dim Ausgabe_i As Dword
      28. Dim Adc_wert As Dword
      29. Dim Offset As Dword
      30. Offset = 1024 / 2
      31. Wandler_nr = 0
      32. Do
      33. Wait 1
      34. Led = 1
      35. Ausgabe_i = Berechne_i(wandler_nr)
      36. Print "ADC-Wert " ; Ausgabe_i ; " Kanal " ; Wandler_nr
      37. Wait 1
      38. Led = 0
      39. Loop
      40. End
      41. Function Berechne_i(byval Adc_kanal As Byte) As Dword
      42. Timer1 = 0
      43. Adc_wert = Getadc(adc_kanal)
      44. T1 = Timer1
      45. Print T1
      46. Berechne_i = Adc_wert
      47. End Function
      Display All
    • Wenn ich mich recht erinnere wird bei Bascom mit dem GetADC() 2x gemessen.
      Der 1. Wert wird verworen, der 2. zurückgegeben.
      Somit kannst du schon mal mit 2x13 Takten rechnen.
      Sind dann in Summer schon mal 208µs.
      Hinzu kommt Laufzeit für Aufruf der Routine und Rücksprung.

      Deine Messung ist ja 226µs.
      Scheint so dass etwa 18µs Laufzeit dazu kommen. Das wären dann also noch 288 Prozessortakte.

      Du kannst aber mal versuchen, die Register zur AD-Wandlung selber zu beschreiben.
      Ist nicht so kompliziert. Das dürfte schneller gehen.

      Weiterhin willst du RMS messen. Das wäre also die Effektive Spannung.
      Dazu kann man über eine Periodendauer das Signal sampeln und danach auswerten.
      Im Prinzip muss man nur einen Mittelwert über eine Periodendauer ermitteln.
      Offset aber beachten, da dein 0-Wert bei VCC/2 liegt.
    • Mitch64 wrote:

      Wenn ich mich recht erinnere wird bei Bascom mit dem GetADC() 2x gemessen.
      Die Dummy-Messung wird nur nach dem Einschalten des AD-Wandlers empfohlen, also nach Start Adc.
      Im laufenden Betrieb braucht es dan nicht.
      Mit Bascom hat das auch nichts zu tun, das ist bei C und Assembler genauso (und bei Pascal)

      P.S.:
      Siehe Datenblatt Seite 208:
      A normal conversion takes 13 ADC clock cycles. The first conversion after the ADC is switched on (ADEN in ADCSRA is set)
      takes 25 ADC clock cycles in order to initialize the analog circuitry.
      ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
    • macht Bascom aber so (disassembly für m32):

      Source Code

      1. Tmp = Getadc(0)
      2. A4: 07 B1 IN R16, 0x07 ; 7
      3. A6: 00 7E ANDI R16, 0xE0 ; 224
      4. A8: 07 B9 OUT 0x07, R16 ; 7
      5. AA: 0E 94 66 00 CALL 0xCC ; 0xCC
      6. AE: A0 E6 LDI R26, 0x60 ; 96
      7. B0: B0 E0 LDI R27, 0x00 ; 0
      8. B2: 8D 93 ST X+, R24
      9. B4: 9C 93 ST X, R25
      10. End
      11. B6: F8 94 CLI
      12. B8: FF CF RJMP .-2 ; 0xB8
      13. BA: 31 97 SBIW R30, 0x01 ; 1
      14. BC: F1 F7 BRNE .-4 ; 0xBA
      15. BE: 08 95 RET
      16. C0: 68 94 SET
      17. C2: 62 F8 BLD R6, 2
      18. C4: 08 95 RET
      19. C6: E8 94 CLT
      20. C8: 62 F8 BLD R6, 2
      21. CA: 08 95 RET
      22. CC: 36 9A SBI 0x06, 6 ; 6
      23. CE: 36 99 SBIC 0x06, 6 ; 6
      24. D0: FE CF RJMP .-4 ; 0xCE
      25. D2: 36 9A SBI 0x06, 6 ; 6
      26. D4: 36 99 SBIC 0x06, 6 ; 6
      27. D6: FE CF RJMP .-4 ; 0xD4
      28. D8: 84 B1 IN R24, 0x04 ; 4
      29. DA: 95 B1 IN R25, 0x05 ; 5
      30. DC: 08 95 RET
      Display All
    • Was im Datenblatt steht, da gebe ich dir recht.

      Aber wie gewandelt wird steht in der Lib mcs.lib

      Hier der Auszug zu GetADC:

      Source Code

      1. [_GETADC]
      2. _GetADC:
      3. * Sbi ADCSR,6 ; start conversion
      4. _GetADC_1:
      5. * Sbic ADCSR,6 ; skip next instruction if ready
      6. Rjmp _GetADC_1 ; not ready
      7. ;--- remark the next 4 lines for single conversion
      8. * Sbi ADCSR,6 ; we need 2 conversions for a good result
      9. _GetADC_2:
      10. * Sbic ADCSR,6 ; skip next instruction if ready
      11. Rjmp _GetADC_2 ; not ready
      12. ;-------------------------------------------------
      13. * In r24,ADCL ; first read lower data register
      14. * In r25,ADCH ;then read upper data register
      15. Ret
      16. [END]
      Display All
      In Zeile 3 wird die 1. Konversation gestartet
      In Zeile 9 wird eine 2. Konversation gestartet

      Nach LIB wird also 2x gewandelt, also 2x 13 ADC-Takte.
    • Zu Glück gibt es ja verschiedene Möglichkeiten das Problem zu lösen.

      1. Man könnte eine eigene lib machen, die den geänderten Code aus der MCS enthält (4 Zeilen remarken). Und dann diese Lib in das Projekt einbinden. Damit hat man dann 13 ADC-Takte.
      2. die MCS-Lib ändern, würde ich aber nicht empfehlen, denn beim nächsten Update wird die vielleicht überschrieben.
      3. Die MCS-Lib in ein Unterverzeichnis von Bascom-Lib kopieren (z.B. MyLib) und dort ändern. Dann die neue Lib einbinden $Lib "MyLib\mcs.lib" (keine Einbindung mit $LIB verwendet mcs.lbx). Damit bleibt di geänderte Lib erhalten. Nachteil: Bei Update der Lib, kann man in dem Projekt nicht provitieren. Vorteil. Abdere Projekte bleiben von der Änderung unbeeinflusst.
      4. Man kann die Register selber beschreiben in Bascom (Wandlung starten) und das ADC-Flag pollen, ob die Wandlung beendet ist und den Wert direkt in die Zielvariable kopieren.
      Ich empfehle Variante 1, denn da kann man durch einbinden der eigenen Lib gezielt diese GetADC anpassen. Die mcs.lbx kann gefahrlos upgedatet werden und alles funktioniert. Man hat dann die Wahl, ob die Wandlung schneller gehen soll, dann muss man eben nur die eigene Lib einbinden.
    • ich Weiß jetzt nicht ob das eine verlässliche "Zeitmessung" wie ich das umgesetzt habe
      aber ich denke es kommt hin.

      Der Zählerstand von Timer1 den ich mir mit dem Print Befehl ausgeben lasse ist relativ konstant

      er schwankt zwischen 3618 und 3623

      Ich habe den Vorschlag von Mitch64 umgesetzt

      Mit getadc scheint eine Wandlung wirklich länger zu dauern

      Hier mal die Zählerstände vom Timer (die Werte wiederholen sich)

      1799

      1777

      1755

      1711

      Macht man das so mit den ADC Registern oder geht es besser

      Source Code

      1. $regfile = "m328pdef.dat"
      2. $crystal = 16000000
      3. $hwstack = 64
      4. $swstack = 32
      5. $framesize = 32
      6. $baud = 9600
      7. '$sim
      8. Config Timer1 = Timer , Prescale = 1
      9. Enable Timer1
      10. Enable Interrupts
      11. Config Adc = Single , Prescaler = Auto , Reference = Avcc 'Internal 'INTERNAL_1.1
      12. Start Adc
      13. Config Portb.5 = Output
      14. Led Alias Portb.5
      15. Declare Function Berechne_i(byval Adc_kanal As Byte) As Dword
      16. Dim X As Byte
      17. Dim T1 As Word
      18. Dim Wandler_nr As Byte
      19. Dim Ausgabe_i As Dword
      20. Dim Adc_wert As Dword
      21. Dim Adc_low_byte As Byte At Adc_wert Overlay
      22. Dim Adc_high_byte As Byte At Adc_wert + 1 Overlay
      23. Dim Offset As Dword
      24. Offset = 1024 / 2
      25. Wandler_nr = 0
      26. Do
      27. Wait 1
      28. Led = 1
      29. Ausgabe_i = Berechne_i(wandler_nr)
      30. Print "ADC-Wert " ; Ausgabe_i ; " Kanal " ; Wandler_nr
      31. Wait 1
      32. Led = 0
      33. Loop
      34. End
      35. Function Berechne_i(byval Adc_kanal As Byte) As Dword
      36. Timer1 = 0
      37. Set Adcsra.adsc ' ADC Wandlung Starten
      38. While Adcsra.adsc = 1 'Warte bis Wandlung fertig
      39. Adc_low_byte = Adcl 'Erst Low Byte Lesen
      40. Adc_high_byte = Adch
      41. Wend
      42. 'Adc_wert = Getadc(adc_kanal)
      43. T1 = Timer1
      44. Print T1
      45. Berechne_i = Adc_wert
      46. End Function
      Display All
    • Kleiner Nachtrag, wie man das mit einer eigenen Lib machen kann.

      Man kopiert folgenden Code in eine Textdatei. Ich habe sie myLib.lib genannt. Und speichert diese für künftige Anwendung im Bascom Lib-Verzeichnis.
      Ich bevorzuge aber lieber ein Unterverzeichnis für eigene Libs, da sich da schon einiges gesammelt hat.
      Ich habe also im Lib Ordner noch einen Ordner Namens Lib. Darin sind meine eigenen.

      Wo du es hin kopierst bleibt dir überlassen, aber im Code muss dann der Pfad angepasst werden.

      Hier zunächst die Lib:

      Source Code

      1. ; MyLib
      2. ; Enthaelt diverse Erweiterungen bzw. Verbesserungen
      3. ; GetADC
      4. [_GETADC]
      5. _GetADC:
      6. * Sbi ADCSR,6 ; start conversion
      7. _GetADC_1:
      8. * Sbic ADCSR,6 ; skip next instruction if ready
      9. Rjmp _GetADC_1 ; not ready
      10. * In r24,ADCL ; first read lower data register
      11. * In r25,ADCH ;then read upper data register
      12. Ret
      13. [END]
      Display All

      Die Lib wird dann einfach ins eigene Projekt eingebunden, sofern man die Lib nutzen will.
      Dieses kleine Programm zeigt die Einbindung und die Anwendung:

      BASCOM Source Code

      1. $regfile = "attiny44.dat"
      2. $crystal = 8000000 ' Intern RC-Clock
      3. $hwstack = 16
      4. $swstack = 16
      5. $framesize = 32
      6. Config ADC = Single , Prescaler = Auto , Reference = AVCC
      7. $Lib "Lib\MyLib.lib" ' Eingebunden wird GetADC schneller
      8. Dim adcValue as Word
      9. Do
      10. adcValue = GetADC(1)
      11. Loop
      Display All

      In Zeile 9 wird die Lib dann aus dem Unterverzeichnis Lib/Lib/(MyLib.lib) eingebunden.

      In der Hauptschleife sieht man, dass kein Unterschied zur bekannten Anwendung GetADC(kanal) zu sehen ist.
      Die Lib ersetzt damit die GetADC aus der mcs.lib, ohne diese zu ändern.

      Vorteile sind klar.
      - Updates der Libs von Seiten mcselec hat keinen Einfluss auf das Programm bezogen auf GetADC().
      - Will man das Ursprüngliche GetADC (langsamer) verwenden, remmt man einfach die Lib.
      - Da mcs.lib nicht geändert wurde, hat das keine Auswirkung auf andere Projekte.

      Alles ist Save!

      Die Lib kann man auch erweitern, falls man noch Ideen hat.
      Z.B. GetHighNibble(byte) oder GetLowNibble(byte)

      Man könnte die Lib auch Tools nennen, passt ja auch ganz gut.

      Einen kleinen Nachteil gibts allerdings.
      Verwendet man die Lib, muss man die beim verteilen vom Code auch mit geben, da sonst das langsame GetAdc verwendet wird.

      Viel Spaß damit.
      Files
      • MyLib.lib.txt

        (363 Byte, downloaded 10 times, last: )
      • DemoMyLib.bas

        (385 Byte, downloaded 7 times, last: )
    • @six1
      Genau so wenig wie in der originalen Routine (Auszug mcs.lib) siehe Post #5.
      Das Mux-Register wird aber gesetzt. Zumindest nach Simulator. Habe es eben nochmal mit Kanal 3 simuliert.

      Was da der Compiler intern macht weiß ich nicht. Aber in dem Fall muss offensichtlich der Kanal nicht in der Lib in das ADMUX-Register geschrieben werden.
      Das hatte ich ursprünglich auch erwartet. Aber der Wert steht da schon im Register.

      Ich habe nur die 4 Zeilen gelöscht, wie in der Originalen lib angegeben.
      Sonst ist der Code 1:1 das selbe.
    • Pluto25 wrote:

      Der Compiler entscheidet sich in so einem Fall immer die Externe zu verwenden?
      Ich denke, davon muss man ausgehen können. Sonst könnte man ja keine eigenen libs einbinden. Wenn ich den compiler schreiben müsste, dann würde ich die externen libs nacheinander durchgehen, bis ich ein passendes label finde und den code dazu verwenden. Das heißt, wenn noch weitere libs mit _Getadc eingebunden werden, dann würde der code von dem ersten gefundenen label in den libs verwendet. Ob das jetzt so gemacht wurde, weiß ich natürlich nicht.
      Raum für Notizen

      -----------------------------------------------------------------------------------------------------

      -----------------------------------------------------------------------------------------------------
    • @tschoeatsch
      Wie der Mechanismus (Reihenfolge Einbindung der Routinen) intern genau funktioniert habe ich auch noch nicht wirklich durchschaut.
      Normal ist es aber so, dass alle Routinen aus der mcs.lib durch eine ersetzt werden können. So habe ich das mal irgendwo in der Bascomhilfe gelesen.
      Aber das funktioniert leider nicht mit allen Routinen.

      Bei GetADC funktioniert es, bei SPIWrite und SPIRead nicht. Da weigert sich der Compiler vehement.
      Es gibt auch spezielle LCD-Routinen, die nicht ersetzt werden können.
      Ich glaube das waren die Befehle Upperline, Lowerline, Thirdline und Fourthline. Dafür gibts wohl keine
      Einspung-Labels in der mcs-Lib. Das übersetzt der Compiler wohl selber.

      Die Reihenfolge der Einbindung:
      Wenn man z.B. die GetADC schon ersetzt hat (mit eigener Lib) und würde noch eine weitere Lib schreiben, die auch diese GetADC ersetzt, dann würde das GetADC aus der 1. Lib genommen.

      Vielleicht ist es so, wie du sagst, dass der Compiler zuerst die Libs durchsucht, die im Programm mit Lib eingebunden werden. Den Rest sucht er in der MCS-Lib.
      Die 1. gefundene Routine würde er dann nehmen.

      Das würde auch erklären, wenn man 2 Libs einbindet, die jeweils den GetADC ersetzt, würde die 1. eingebundene Lib zum Zug kommen, die 2. ignoriert und auch die mcs ignoriert. Das Verhalten passt zu meinen Beobachtungen.

      Leider steht in der Bascomhilfe nichts bezüglich der Reihenfolge, wie die Libs bzw. deren Routinen eingebunden werden.
    • Ich erstatte hiermit Bericht :saint:


      Ich muss sagen es funktioniert sehr gut mit der eigenen Lib, die ermittelten Zeiten kommen im Schnitt (0,111ms) sehr nahe an den theoretisch ermittelten Wert für eine Wandlung heran.


      Ursprünglich wollte ich nur den Strommessen, die Netzspannung kann man ja nach dem gleichen Prinzip messen / berechnen also habe ich erstmal ein 9VAC Stecker Netzteil aus der Schublade gekramt und das Programm zur Spannungsmessung geschrieben.


      Das Programm scheint soweit erstmal grob zu funktionieren, momentan messe ich einfach drauf loss.

      Für genauere Ergebnisse wäre es besser die Messung im Nulldurchgang zu starten und für die Dauer von 2 Perioden zumessen und hier bräuchte ich Hilfe ich weißnicht wie ich das machen soll.


      Hier mal mein aktuelles Programm wenn ihr Verbesserungsvorschläge habt wie man die Berechnung besser effizienter macht bitte her damit

      Source Code

      1. $regfile = "m328pdef.dat"
      2. $crystal = 16000000
      3. $hwstack = 64
      4. $swstack = 32
      5. $framesize = 32
      6. $baud = 9600
      7. $lib "Lib\GetADC.lib"
      8. $sim
      9. Config Timer1 = Timer , Prescale = 256
      10. Enable Timer1
      11. Enable Interrupts
      12. Config Adc = Single , Prescaler = Auto , Reference = Avcc 'Internal 'INTERNAL_1.1
      13. Start Adc
      14. Config Portb.0 = Output
      15. Config Portb.5 = Output
      16. Led Alias Portb.5 'Arduino Onbord Led
      17. Const Sekunde = 62500
      18. Const Ref_spannung = 5
      19. Const 10bit = 1024
      20. Const Windungszahl = 300
      21. Const Buerdewiderstand = 47
      22. Const Netz_v = 230
      23. Dim Adc_wert As Word
      24. Dim Adc_faktor As Single
      25. Dim Ausgabe As Word
      26. Dim Irms As Single
      27. Dim Offset As Single
      28. Dim T_alt As Word
      29. Dim Trafo_faktor As Single
      30. Dim Vrms As Single
      31. Dim Wandler_nr As Byte
      32. Dim Wandler_faktor As Single
      33. 'Faktor für Umrechung des ADC-Wertes in Spannung
      34. ''(Referenzspannung / 1024 ) * 1.000.000
      35. ''5V / 1024 * 1.000.000 = 4882.8125µV
      36. Adc_faktor = Ref_spannung / 10bit
      37. Adc_faktor = Adc_faktor * 1000000
      38. Adc_faktor = Round(adc_faktor)
      39. Offset = 10bit / 2
      40. Trafo_faktor = 118
      41. Wandler_faktor = Windungszahl / Buerdewiderstand
      42. Wandler_nr = 0
      43. Do
      44. 'Wait 1
      45. Ausgabe = Timer1 - T_alt
      46. If Ausgabe >= Sekunde Then
      47. T_alt = Timer1
      48. Toggle Led
      49. Gosub Messe_vrms
      50. Print "Vrms= " ; Vrms ; " Kanal " ; Wandler_nr
      51. End If
      52. 'Wait 1
      53. 'Led = 0
      54. Loop
      55. End
      56. Messe_vrms:
      57. Dim V_nr As Single
      58. Dim V_offset As Single
      59. Dim V_summe As Single
      60. Dim V_quadrat As Single
      61. Dim V_merker As Single
      62. Dim V_ergebnis As Single
      63. Dim V_gesamt_faktor As Single
      64. Vrms = 0
      65. For V_nr = 0 To 100
      66. ' Timer1 = 0 '
      67. Adc_wert = Getadc(wandler_nr)
      68. ' Print Timer1
      69. V_offset = Adc_wert - Offset'Annalogwert minus DC Offset (2.5V)
      70. V_quadrat = V_offset * V_offset 'Rohdaten Quadrieren
      71. V_summe = V_summe + V_quadrat 'Aufaddieren für Mittelwert
      72. Next V_nr
      73. '
      74. ''Berechnung VRMS
      75. V_merker = V_summe / V_nr
      76. V_merker = Sqr(v_merker)
      77. V_gesamt_faktor = Trafo_faktor * Adc_faktor
      78. V_ergebnis = V_merker * V_gesamt_faktor
      79. V_ergebnis = V_ergebnis / 1000000
      80. Vrms = V_ergebnis
      81. V_summe = 0
      82. Return
      Display All
    • Vielleicht noch einen Tipp: ich würde, wenn ich eine schnelle Abfolge der Messungen haben will, die Messwerte in einem array zwischen speichern und danach die Berechnungen mit den Werten aus dem array machen. Also die Zeilen 81, 82 und 83 mit den Werten des arrays, das in der for V_nr ..next gefüllt wird.
      Raum für Notizen

      -----------------------------------------------------------------------------------------------------

      -----------------------------------------------------------------------------------------------------