Emulator für Incrementalgeber

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

    • Emulator für Incrementalgeber

      Hallo allerseits

      Ich versuche gerade einen Emulator für einen Incrementalgeber zu bauen.
      Ich möchte also das Ausgangssignal eines Incrementalgebers generieren (Kanal A und B).
      Dabei sollen verschiedene Incrementalgeber (100, 500 und 1024 Pulse/Ump) verwendet werden können.

      Mit einem Controller soll per Hardware und Timer1 am OC1A das A-Signal und am OC1B-Pin das B-Signal ausgegeben werden.
      Mit einem Poti am ADC-Eingang ADC0 möchte ich die Frequenz einstellen können.
      Rechtsanschlag, also 5V am ADC-Eingang soll 6000 rpm Signal ausgeben.
      Das soll bis runter auf 200 rpm oder tiefer gehen.

      Soweit habe ich das auch am laufen.

      Hier mal den Code

      BASCOM Source Code: Emulator Incrementalgeben

      1. ' Incrementalgeber Emulator
      2. ' Das Tastverhältnis der Signale
      3. ' ist symetrisch mit 50%. Allerdings sind die Signale richtungsabhängig
      4. ' zueinander um ±90° phasenverschoben.
      5. ' ---- ----
      6. ' | | | |
      7. ' Kanal A ---- ---- ----
      8. '
      9. ' ---- ----
      10. ' | | | |
      11. ' Kanal B ---- ---- ----
      12. ' Die untere Frequenz (F out) ist theoretisch 0Hz. Die obere Frequenz hängt vom
      13. ' Incrementalgeber ab, der simuliert werden soll. Simuliert werden soll
      14. ' die Drehzahl n = 200 ... 6000 rpm mit den folgenden Incrementalgebern (umschaltbar).
      15. ' Incrementalgeber F out max.
      16. ' 100 Pulse/Ump 10000 Hz
      17. ' 500 Pulse/Ump 50000 Hz
      18. ' 1024 Pulse/Ump 100000 Hz
      19. $Regfile = "m168def.dat"
      20. $HWStack = 40
      21. $SWStack = 40
      22. $FrameSize = 40
      23. $Crystal = 14745600 ' Quarz
      24. Config SubMode = New
      25. Const PIN_AIN = 0 ' ADC0 Soll-Drehzahl
      26. ' mögliche Inkrementalgeber
      27. Const I_Geber_100 = 100 ' Geber mit 100 Pulse/Ump
      28. Const I_Geber_500 = 500 ' Geber mit 500 Pulse/Ump
      29. Const I_Geber_1024 = 1024 ' Geber mit 1024 Pulse/Ump
      30. Const I_Geber = I_GEBER_100 ' Geber setzen
      31. Dim F_Out as Long ' Ausgabefrequenz des Gebers
      32. Dim F_OutNew as Long
      33. Dim I_State as Byte ' Zustand der A-B-Signale
      34. ' ------------------------------------
      35. ' Routinen
      36. ' ------------------------------------
      37. ' Berechnung der Ausgabefrequenz des Gebers
      38. Function getFOut() as Long
      39. Local n as Integer ' Drehzahl
      40. Local f as Single ' Frequenz
      41. ' n-max * Adc 6000 * Adc
      42. ' frq = -------------- = -----------
      43. ' adc-max * 60 1000 * 60
      44. n = GetAdc(PIN_AIN) * 6 ' Soll-Drehzahl einlesen
      45. ' Drehzahlen unter 200 sollen Geberausgabe stoppen
      46. If n < 200 then
      47. F_Out = 0
      48. Exit Function
      49. End If
      50. ' Drehzahl (rpm) in Frequenz umrechnen
      51. f = n ' in Single wandeln
      52. f = f / 60 ' Umrechnung Drehzahl in Frq
      53. ' Berechnung Ausgabe-Frequenz mit gewähltem Inkremental-Geber
      54. f = f * I_GEBER
      55. GetFOut = f ' in Long umwandeln
      56. End Function
      57. ' Registerwerte Timer1 berechnen und setzen
      58. Sub setTimer()
      59. Local R_ICR as Long
      60. Local R_CM as Long
      61. Local tmp as Word
      62. If F_Out = 0 then
      63. Reset TCCR1B.0 ' Keine Drehzahl ausgeben (Timer1 Stop)
      64. Else
      65. ' Register berechnen / setzen
      66. R_ICR = _xtal \ F_Out ' ICR1-Registerwert berechnen
      67. R_ICR = R_ICR \ 2 ' Da Pin getoggelt wird, brauche ich doppelte Frequenz
      68. R_CM = R_ICR \ 2 ' Compare-Register berechnen
      69. tmp = LowW(R_ICR)
      70. ICR1 = tmp
      71. tmp = LowW(R_CM)
      72. Compare1A = tmp 'LowW(R_CM) ' Register setzen
      73. Set TCCR1B.0 ' Drehzahl ausgeben (Timer1 Start)
      74. End If
      75. End Sub
      76. ' ------------------------------------
      77. ' Initialisierung
      78. ' ------------------------------------
      79. Config ADC = Single , Prescaler = Auto , Reference = AVCC
      80. Config Portb.0 = Output
      81. Config PortB.1 = Output ' OC1A
      82. Config PortB.2 = Output ' OC1B
      83. ' Timer1 im Mode 12, CTC mit ICR1 als Top-Wert
      84. Compare1A = 368
      85. Compare1B = 1
      86. ICR1 = 737
      87. Config Timer1 = Timer , Prescale = 1 , COMPARE_A = Toggle , COMPARE_B = Toggle , Clear_Timer = 1
      88. Set TCCR1B.WGM13
      89. On OC1A ISR_OC1A NoSave
      90. Enable OC1A
      91. Enable Interrupts
      92. ' ------------------------------------
      93. ' Hauptschleife
      94. ' ------------------------------------
      95. Do
      96. F_Out = GetFOut() ' Ausgabe-Frequenz des Gebers berechnen
      97. Call SetTimer() ' Register für Frequenzausgabe setzen
      98. Waitms 100
      99. Loop
      100. ISR_OC1A:
      101. '!PUSH r23
      102. !PUSH r24
      103. !IN r24, SREG
      104. !PUSH r24
      105. !SBI Portb,0
      106. !NOP
      107. !CBI PortB,0
      108. '!SBIC PinB,1
      109. '!CBI PortB,0
      110. !POP r24
      111. !OUT SREG, r24
      112. !POP r24
      113. '!POP r23
      114. Return
      Display All
      Das generierte Ausgangssignal sieht nun so aus:
      DS1Z_QuickPrint2.png

      Wie man sieht, werden etwa 10kHz ausgegeben, weas bei einem Incrementalgeber mit 100 Pulse je Umdrehung einer Drehzahl von 6000 rpm entspricht.

      Um die Phasenverschiebung von 90° zu erzeugen, habe ich den Timer1 im Mode 12 (CTC-Mode mit ICR1-Registerwert als Top-Wert) konfiguriert.
      Somit wird die Timerfrequenz auf die doppelte der Ausgangsfrequenz eingestellt und die Ausgänge getoggelt.

      Nun das Problem:
      Wenn ich am Poti drehe und damit die Frequenz ändere, springt sporadisch die Phase der A-B Kanäle.
      Heißt, mal ist Kanal B 90° nacheilend, und mal Voreilend, aber dann stabil.

      Ich denke es hängt mit dem Zeitpunkt zusammen, an den das OC1A-Register und das ICR1-Register geändert wird.

      Bisher habe ich das noch nicht hinbekommen, dass die Phase stabil bleibt. Das ist wichtig, denn die bestimmt die Drehrichtung.

      Um das Problem in den Griff zu bekommen habe ich noch ein Interrupt von OC1A-generieren lassen, in dem ich einen Puls auf PortB.0 ausgeben lasse.
      (Siehe ISR_OC1A). Dort wollte ich die Signale zueinander synchronisieren. das klappt aber nicht.

      Hier das Signal des OC1A-Pulses in Bezug auf OC1A.

      DS1Z_QuickPrint3.png

      Hat jemand eine Idee, wie ich Signal B dem Signal A stabil nacheilen lasse?
    • Nachtrag.
      Das Problem wird vielleicht deutlicher, wenn man sich das folgende Oszillogramm und die zugehörige ISR ansieht.
      DS1Z_QuickPrint4.png

      Gelb ist Kanal A,
      Blau Kanal B (der sollte nacheilen),
      und violett ist der Peak, der mit OC1A, Also Kanal A zusammen läuft.

      Und hier die ISR (rest wie Post #1)

      BASCOM Source Code: ISR_OC1A

      1. ISR_OC1A:
      2. '!PUSH r23
      3. !PUSH r24
      4. !IN r24, SREG
      5. !PUSH r24
      6. !SBI Portb,0 ; Pos. Peak auf PortB.0 ausgeben
      7. !NOP
      8. !CBI PortB,0
      9. !SBIC PinB,1 ; Überspringe, wenn Kanal A = 0
      10. !CBI PortB,2 ; Kanal B = 0 setzen, wenn Kanal A = 1 (B soll nacheilen)
      11. !POP r24
      12. !OUT SREG, r24
      13. !POP r24
      14. '!POP r23
      15. Return
      Display All
      In der ISR wird Kanal A geprüft und dann wenn der 1 ist, soll Kanal B 0 gesetzt werden.
      Das funktioniert leider nicht wie gedacht.

      Wo ist der Fehler?
    • Pluto25 wrote:

      Die Änderung mit dem Timer synchronisieren. z.B. aufs Overlow-flag warten um dann erst die Werte ändern. (Jedoch vorher berechnen)
      Gleiches wie zuvor.

      BASCOM Source Code: Warten auf ICP-Interrupt

      1. ' ------------------------------------
      2. ' Hauptschleife
      3. ' ------------------------------------
      4. Do
      5. F_Out = GetFOut() ' Ausgabe-Frequenz des Gebers berechnen
      6. BitWait TIFR1.ICF1 , Set
      7. Set TIFR1.ICF1
      8. Call SetTimer() ' Register für Frequenzausgabe setzen
      9. Waitms 100
      10. Loop
      11. ' ------------------------------------
      12. ' OC1A-Interrupt
      13. ' ------------------------------------
      14. ISR_OC1A:
      15. '!PUSH r23
      16. !PUSH r24
      17. !IN r24, SREG
      18. !PUSH r24
      19. !SBI Portb,0 ; Pos. Peak auf PortB.0 ausgeben
      20. !NOP
      21. !CBI PortB,0
      22. '!SBIC PinB,1 ; Überspringe, wenn Kanal A = 0
      23. '!CBI PortB , 2 ; Kanal B = 0 setzen , wenn Kanal A = 1(B soll nacheilen)
      24. !POP r24
      25. !OUT SREG, r24
      26. !POP r24
      27. '!POP r23
      28. Return
      Display All
      Ein Overflow gibts in dem Mode nicht. Nur OC1A, OC1B und den ICP-Interrupt, was dem Overflow entspricht.

      Aber es klappt so auch nicht.
    • Die Fehlermeldung mit TOV1
      Error : 117 Line : 126 Unknown interrupt [TOV1] , in File : C:\Users\Michael\Documents\Projekte\Experimente\Temp\Drehzahl_Linearisierung\Geber\Main.bas
      Error : 85 Line : 127 Unknown interrupt source [TOV1] , in File : C:\Users\Michael\Documents\Projekte\Experimente\Temp\Drehzahl_Linearisierung\Geber\Main.bas

      Wenn dann OVF1
      Aber der wird nicht ausgelöst.
      Habe es eben nochmal versucht.
    • Nein, an der Hardware direkt probiert.

      On OVF1 ISR_OC1A NoSave
      Enable OVF1

      Es wird kein Nadelpuls ausgegeben!
      Aber so schon:

      On ICP1 ISR_OC1A NoSave
      Enable ICP1

      Ich glaube ich weiß jetzt, warum ich den Pegel am OC2B nicht ändern kann.
      Der Port wird wohl durch die Timer-Konfiguration mit der Toggle-Funktion überschrieben.

      Ein Lösung hab ich noch nicht.
    • Wenn Du zweigleisig fährst wäre es gut andere Pins zu nehmen. Dann kann man gleich beides Messen und gut entscheiden was schöner ist.
      Läßt ein Pin sich überhaupt beeinflussen / lesen solange er im OCR Modus ist? ( Nie versucht )

      Vielleicht noch ein Set OVF1 zu beginn ? Es sieht ja so aus als hätte das Datenblatt einen Fehler?
    • Ich habe die Variante mit anderen Pins schon durch.
      Das Signal zittert wie Espenlaub.

      Daher kommt nur die Generierung der AB-Signale per Timer in Frage.

      Und ja ich habe versucht die Pins zu beeinflussen in der ISR.

      Das geht aber nicht, weil die Pins mit der COM1xn Funktion überschrieben werden. Ich kann also in der ISR an den Pins OC1A und OC1B wackeln, aber das wird völlig ignoriert.

      Ich kann die Pins nur ändern, wenn COM1Axn auf 0 ist.

      Pluto25 wrote:

      Vielleicht noch ein Set OVF1 zu beginn ? Es sieht ja so aus als hätte das Datenblatt einen Fehler?

      Der Interrupt OVF1 wird nicht ausgelöst, siehe vorherige Posts.

      Wieso soll das Datenblatt einen Fehler haben? Welchen?
    • Mitch64 wrote:

      Ich kann die Pins nur ändern, wenn COM1Axn auf 0 ist.
      Das stimmt mit dem DB überein; ob man sie lesen kann? das Pin Register gesetzt wird? Vermutlich auch nicht? Das es schreibt das TOV1 gesetzt wird ist dann wohl falsch.
      Woher das Zittern? Wieviel Stress machst Du der Cpu das sie es nicht schafft zeitig in die Isr zu kommen?
      Da steht die (Timer) Berechnung doch wohl nicht drin? :D
    • Bei dem Test, wo ich die Pins per Hand in der ISR gesetzt habe, hatte ich ein Jittern.
      Grund ist nicht die Auslastung der MCU, sondern dass die Befehle unterschiedlich lange Ausführungszeit haben.

      Um per Hand die 4 Zustände auszugeben, muss bei Incrementalgeber mit 1024 Pulsen pro Umdrehung die ISR mit 400kHz aufgerufen werden.

      In der Hauptschleife lief nur die ADC-Abfrage, Berchnung der Ausgabefrequenz/Registerwerte und das setzen der Register.

      Das Jittern lag, wenn ich jetzt richtig gerechnet habe bei 10µs.
      Das hat sich deutlich bemerkbar gemacht.

      Mit dem Hinweis, dass man die Pinns nicht auslesen kann, wenn eines der COMxn-Bits gesetzt ist, könntest du recht haben.

      Bleibt als einziger Ausweg aus jetziger sicht, das Flag FOC1A bzw. FOC1B mal auszuprobieren.
    • Mitch64 wrote:

      dass die Befehle unterschiedlich lange Ausführungszeit haben.
      Aber doch keine 140 Takte vielleicht 2. Da muß was anderes dazwischen hauen. Verhindert die Long Berechnug Ints? Oder ein Getadc? Gibts eine ADC-Isr?
      400kHz? Dann bleiben der isr nur 35 Takte, da ist selbst Nosave eng.
      Meine Quick&Easy hat schon 146 Takte ;( - ich versuche nachzubessern

      The post was edited 1 time, last by Pluto25 ().

    • Pluto25 wrote:

      Aber doch keine 140 Takte vielleicht 2.
      Da konnte ich mir auch keinen Reim machen. Es war deutlich länger als ein Prozessortakt. Es war ungefär 1/4 von der Frequent (400kHz) die ist per ISR erzeugt habe.

      Pluto25 wrote:

      Da muß was anderes dazwischen hauen. Verhindert die Long Berechnug Ints?
      Ich wüßte nicht was. Am Long liegts nicht.

      Pluto25 wrote:

      Oder ein Getadc? Gibts eine ADC-Isr?
      GetADC verwendet keine ISR, außer Freerun oder wenn man selbst eine ADC-ISR anlegt. Beides war nicht der Fall.

      Spielt aber auch keine Rolle.

      Es müsste doch mit Hardware auch gehen.

      Pluto25 wrote:

      Meine Quick&Easy hat schon 146 Takte
      Meine ISR war vollständig in Assembler geschrieben und mir NoSave konfiguriert.
      Ich meine es waren unter 40 Takte

      Die Ausgabe war simpel.
      In Basic etwa so:

      Dim I_State as Byte ' Speichert den Zustand der AB-Signale

      Dann die ISR
      Die war mittels CTC-Timer gemacht und OC1A bestimmte die Frequenz (400kHz bei Incr-Geber mit 1024 Pulsen und Drehzahl = 6000 rpm).

      BASCOM Source Code

      1. Sub ISR_OC1A()
      2. If I_State=0 then
      3. Set Signal_A
      4. Reset Signal_B
      5. ElseIf I_State = 1 then
      6. Set Signal_A
      7. Set Signal_B
      8. ElseIf I_State = 2 then
      9. Reset Signal_A
      10. Set Signal_B
      11. ElseIf I_State = 3 then
      12. Reset Signal_A
      13. Reset Signal_B
      14. End If
      15. Incr I_State
      16. I_State = I_State And 3
      17. Return
      Display All
    • Unter 40 reicht nicht bei 35 die zur Verfügung stehen. (Die angegebene hat erheblich mehr)
      Das

      Source Code

      1. $asm
      2. PUSH r16
      3. IN r16,sreg
      4. PUSH r16
      5. push r17
      6. lds r16,{temp}
      7. inc r16
      8. sts {temp},r16
      9. mov r17, r16
      10. lsr r17
      11. eor r17,r16
      12. Bst R17 , 0
      13. in r17,portb
      14. bld r17,4 'Portb.4
      15. bst r16,1
      16. bld r17,3 'Portb.3
      17. Out Portb , R17
      18. pop r17
      19. pop r16
      20. Out Sreg , R16
      21. POP r16
      22. $end Asm
      Display All
      sind 28 ohne Aufruf. Etwas würde sich noch beschleunigen lassen wenn ein Register im Code nicht verwendet wird z.B. 15 oder 7-9. Aber das muß im Einzelfall mit kompletten Code geprüft werden.

      Hast Du mit den FOC was reißen können?
      Auch 14,xx Mhz beim Testobjekt?

      The post was edited 1 time, last by Pluto25 ().

    • Meine Routine sah so aus (extra nochmal programmiert).

      BASCOM Source Code: ISR

      1. ISR_OC1A:
      2. !PUSH r24
      3. !IN r24, SREG
      4. !PUSH r24
      5. !LDS r24,{I_State} ' Status laden
      6. ISR_State_0:
      7. !CPI r24, 0 ' State 0 ?
      8. !BRNE ISR_State_1
      9. !SBI PortB,1 ' Kanal A (OC1A)
      10. !CBI PortB,2 ' Kanal B (OC1B)
      11. !RJMP ISR_Exit
      12. ISR_State_1:
      13. !CPI r24,1 ' State 1 ?
      14. !BRNE ISR_State_2
      15. !SBI PortB,1
      16. !SBI PortB,2
      17. !RJMP ISR_Exit
      18. ISR_State_2:
      19. !CPI r24,2 ' State 2 ?
      20. !BRNE ISR_State_3
      21. !CBI PortB,1
      22. !SBI PortB,2
      23. !RJMP ISR_Exit
      24. ISR_State_3:
      25. !CPI r24,3 ' State 3 ?
      26. !BRNE ISR_Exit
      27. !CBI PortB,1
      28. !CBI PortB,2
      29. '!RJMP ISR_Exit
      30. ISR_Exit:
      31. !Inc r24 ' Status erhöhen
      32. !AndI r24,$03
      33. !STS {I_State},r24 ' Status sichern
      34. !POP r24
      35. !OUT SREG, r24
      36. !POP r24
      37. Return
      Display All
      Abhängig von I_State braucht die ISR mit Aufruf und Rücksprung max. 41 Takte. Max. 31 Takte ohne Hin und Rücksprung.
      Die 100kHz Ausgabesignal mit 400Kh ISR wäre dann nicht ganz erreicht.
      Aber auf das käme es jetzt nicht an.

      Ich habe herausgefunden, dass man die Pins doch abfragen kann, auch wenn mit COM1Bn gesetzt ist.
      Ich konnte 2 Pulse ausgeben, wenn das OC1A in der ICP-ISR gesetzt war, sonst nur 1 Puls. Das klappt also.

      Aber mit dem FOC1B hat es nicht hingehauen. Ich hatte dann nur noch auf Kanal B ein Signal, aber total falsche frequenz.
      Da bin ich noch nicht weiter.

      Vielleicht fällt mir über Nacht noch was ein.

      Wegen dem Jittern nochmal.
      Ich bin jetzt nicht mehr sicher, ob das bei maximaler Frequenz so war oder auch bei anderen Frequenzen.
      Sollte ich vielleicht nochmal ausprobieren.

      Ich habe den Eindruck, wenn das D-Flipflop mal gesetzt ist, kann man es nicht mehr löschen. Das welches per COM1xn den OC1x steuert.
      Vielleicht würde es gehen, wenn man die COM1xn löscht, Timer anhält und auf 0 setzt, dann die Register setzen und den Timer einstellen und zum schluss den Timer wieder laufen lassen.

      Ich muss mal drüber schlafen.

      Wichtig ist halt auch, dass man später per Pin oder so die Dreh-Richtung ändern kann. Also die Phasenverschiebung ändern kann. Deswegen sollte das stabil laufen, ohne mit dem Oszi kontrollieren zu müssen.

      Dann kommt die Auswertung dieses AB-Signals mit einem anderen Controller. Der soll dann anhand des AB-Signals ein Analoges Signal +- 10V generieren.
      Aber das kommt dann später. Erst mal brauch ich ein sauberes Signal, auf das ich mich verlassen kann.
    • Wie wärs mit WGM 4 (ocr1a=top)
      Nur Oc1B auf toggeln, Oc1a normale Pin funktion
      in der Oc1a_isr dann
      toggle Wunschpin darf aber muß nicht oc1a sein
      und falls neue Werte da sind
      Oc1b=neuer Wert/2
      oc1a=neuer Wert
      Das braucht nur halb so oft aufgerufen werden und kann neue Werte synchron eingeben
    • Pluto25 wrote:

      Wie wärs mit WGM 4 (ocr1a=top)
      Nur Oc1B auf toggeln, Oc1a normale Pin funktion
      in der Oc1a_isr dann
      toggle Wunschpin darf aber muß nicht oc1a sein
      und falls neue Werte da sind
      Oc1b=neuer Wert/2
      oc1a=neuer Wert
      Das braucht nur halb so oft aufgerufen werden und kann neue Werte synchron eingeben
      Da muss ich mal drüber nachdenken.

      Übrigens das jittern sind 140ns gewesen.
      Kleines Rechenbeispiel
      Bei Ausgabe-Frequenz 100kHz:
      1/100000 = 10µs

      10µs + 140ns = 10,14µs = 98619Hz

      6000 ump / 100000 * 98619 = 5917 Hz, Fehlt schon einiges. Und der Interrupt muss ja dann mit 200kHz laufen für 100kHz Ausgabe. Also sind es dann 2x 140µs, um die die Frequenz flattert.

      Bei deiner Methode wäre es tatsächlich 2 Interrupts per Periodendauer Ausgabefrequenz.
      Muss ich mal ausprobieren.