Home

   Alignment

    >  2-Star-Alignment

    >  Parallaktisches Alignment

    >  Scheiner-Methode

   Schrittmotor-Steuerung

    >  Controller Elektronik

    >  ASCOM Treiber Download

    >  Verwendung an Astro3-Montierung

   >  Controller Code Download

   Optik

   Raytracing

Gemeinschaft Bad Cannstatt

   > Jazz-Abend

Karl-Heinz Schweikert   

Home   

Stand: 14. Mai 2009

 

Programm-Code

Ich stelle hier den kommentierten Sourcecode und die fertigen Binärdateien zur Verfügung. Mit einem geeigneten Programmer können damit Atmel 2313 Chips (AT90S2313 oder Nachfolgetyp ATiny2313) von jedem selbst programmiert werden. 

 

Das Kleingedruckte muss leider auch hier erwähnt werden:

Die Software ist von mir in aufwendigen und umfangreichen Tests geprüft worden, trotzdem kann eine Fehlerfreiheit grundsätzlich nicht garantiert werden (ist halt Software...). Der Controller ist von mir nicht für den kommerziellen Bereich gedacht, sondern für erfahrene Bastler im Rahmen eines privaten Hobbys. Er darf unter keinen Umständen für sicherheitskritische Anwendungen eingesetzt werden. Ich schließe jede Haftung für irgendwelche Schäden aus, die Verantwortung für den Einsatz dieses Controllers liegt allein beim Anwender. Bei Anwendung im Astronomiebereich bin ich auch nicht schuld an längeren Schlechtwetterperioden und anderen Unannehmlichkeiten kurz nach Fertigstellung der neuen Steuerung! 

Alle Rechte bei K.-H. Schweikert, Stuttgart.

 

Download:  StepMCtrl.zip (25kB)

Die zip-Datei enthält:

StepMCrtl_V1.1.asm  1)

 der eigentliche Assemblercode, erstellt mit dem Atmel AVR-Studio, aber auch mit einem Editor lesbar

StepMCtrl_V1.1.inc, 2313def.inc   

 Include-Dateien mit verschiedenen Definitionen (Ports, Register, usw)

StepMCtrl.hex, StepMCtrl.eep

 die daraus erzeugten Binärdateien (Programm und EEPROM)

sp12.bat Demo Batchfile zum Programmieren des Controllers mit dem bekannten  SP12-Programmer

1) Der Sourcecode ist so formatiert, dass er z.B. in Word mit den Einstellungen "Courier New 6pt, Tab=1 cm, 2 Spalten" platzsparend ausgedruckt werden kann.

 

AVR-Studio 4

Atmel hat mit dem neuen Assembler von AVR Studio 4 ein deutlich restriktiveres Verhalten eingeführt. So sind jetzt Neudefinitionen von bereits definierten Größen nicht mehr erlaubt. Auch eine Formulierung wie "ld ZH, Z+" führt zu Compilerfehlern bzw. Warnungen (je nach Optionseinstellung), obwohl es auf den 2313-Targets keinen Grund dafür gibt. 

Um die Probleme zu beseitigen, habe ich Register neu benannt oder vertauscht. Damit können sowohl der Code für AT90S2313 als auch der des Nachfolgetyps ATtiny2313 fehlerfrei kompiliert werden. Die Funktion ist unverändert geblieben. 

Die erzeugten hex- und eep-Files sind für beide Tagets (..S90.. oder ..tiny..) identisch, das Target muss nur für den AVR Studio4-Simulator richtig eingestellt werden. 

Bei Brennen muss aber auf die unterschiedliche Behandlung der Fusebits geachtet werden. Ein SP12 Beipiel ist im neuen zip-File enthalten.

Hier die angepassten Dateien zum Download (33kB): StepMCtrl_2.zip

Ich habe damit noch keine größeren Tests gemacht. Bitte kurze Info, falls Erfahrungen vorliegen. 

Die mit dem alten Code erzeugten eep und hex Files sind aber nach wie vor verwendbar (auch für ..tiny), nur der neue Assembler macht Zicken! 

 

 

TestTool

Zum Testen der Controller-Funktionen oder der RS232 Kommunikation habe ich ein kleines Programm geschrieben, das Befehle mit Hilfe von frei definierbaren Kommandozeilen zum Controller schicken kann. Der Inhalt der 8 Kommandozeilen kann in einer Datei gespeichert und von dort wieder geladen werden, so dass eine kleine Funktionsbibliothek erstellt werden kann. (Beschreibung der Befehle siehe hier.)

Die gesendeten (T=transmit) und empfangenen (R=receive) Daten werden in einem Monitorfenster in Hexcode dargestellt:

 

Programm, Sourcecode (VBasic6.0, Runtime Installation erforderlich) und ein Demo-Datenfile können hier heruntergeladen werden:

Download: TestTool SmcTest.zip (13kB)

 

 

Programm-Details: Erzeugung der Sinus-Funktion

mit Pulsweitenmodulation bei 64 Microsteps je Vollschritt-Zyklus (=4 Vollschritte):

Amplitude = sin(Pos & 63);   Pos=aktuelle Position (in Microsteps)

Es genügt, nur ein Viertel einer vollen Sinuswelle (Index 0...16) darzustellen, der Rest kann durch Spiegelung und Polaritätsumkehr erzeugt werden.

Abb.: PWM-Prinzip

 

 

Beschreibung der Ansteueralgorithmen in Pseudocode

Das Controllerprogramm selbst ist in hochoptimiertem Assemblercode erstellt und zum Verständnis der zugrundeliegenden Algorithmen ungeeignet.

Stattdessen präsentiere ich hier eine Darstellung in einer übersichtlichen 'Pseudo-Hochsprache' (Basic mit Elementen von C).

 

Steuerungs-Prinzip:

Es werden mehrere verschachtelte Interrupt-Tasks verwendet, die über Timer0 getriggert werden. Hierdurch sind exakt taktsynchrone Bitausgaben an den Ports gewährleistet.

 

Interrupt 1 (IRQ1) wird direkt von TIMER0 getriggert und steuert über variable Zeitabstände dt die pulsweiten-modulierte (PWM) Signalausgabe (siehe Abb. 'PWM-Prinzip'). Die Bitmuster, die zu den vorgegebenen Zeitpunkten an den PWM-Ausgänge ausgegeben werden, sind in einer Tabelle (='BitTab1') abgelegt.

Während BitTab1 in IRQ1 verwendet wird, aktualisiert IRQ3 im Hintergrund eine Kopie dieser Tabelle (=BitTab2) mit Daten, die von Main (s.u.) bereitgestellt wurden.

 

Interrupt 2 (IRQ2, 19.2kHz) wird über IRQ getriggert und erzeugt u.a. die Multiplex- Polaritätssignale. Außerdem wird hier die von IRQ3 inzwischen berechnete BitTab2 nach BitTab1 umkopiert (incl. Polarity). Daneben erledigt IRQ2 noch das Senden/Empfangen von serieller Schnittstelle und Userbus.

 

Interrupt 3 (IRQ3, 2.4kHz) wertet die seriellen Kommandos aus, berechnet die PWM-Tabelle BitTab2 aus den aktuellen Positionen der 4 Schrittmotoren und zählt die interne Sternzeituhr hoch. Hierbei wird ein spezieller Algorithmus eingesetzt, der auch gebrochenzahlige Teilerverhältnisse ermöglicht (im vorliegenden Fall 430/42401). Diese Zahlenkombination approximiert die Sternzeit mit einer (rechnerischen) Abweichung von nur 0.045 Bogensekunden/Tag.

 

Das Hauptprogramm (Main, 300Hz) läuft im Hintergrund. IRQ3 triggert einen neuen Berechnungszyklus, in dem die Positionen der 4 Motoren neu berechnet werden unter Berücksichtigung von Start- und Bremsrampen, Getriebespielausgleich (Backlash) und Periodenfehlerkorrektur (PEC). Hier findet auch die Synchronisation der Motoren statt mit Ermittlung der Master/Slave-Zuteilung und Interpolation der Slave-Positionen. Neu von RS232 eingelesene Target-Werte (in 'Binärgrad' 0x200000=360°) werden in Microstep umgerechnet, entsprechend werden über RS232 auszugebende Größen vorher wieder von internen 'Microstep' in 'Binärgrad' transformiert.

 

Berechnung der Start/Stop Rampen

Hierfür gibt es prinzipiell mehrere Verfahren. Am einfachsten ist es, aus Start- und Zielposition die Anfahr- und Bremsrampen zu berechnen, vorausgesetzt, die Zielposition wird vor Erreichen nicht mehr verändert. Für einen interaktiven Betrieb, bei dem ein Benutzereingriff (z.B. über Vorwärts-/Rückwärtstasten oder gar Digitaldrehgeber) jederzeit möglich sein soll, ist dieses Verfahren ungeeignet.

Stattdessen wird hier bei jedem Berechnungszyklus (in 'Main' mit 300Hz) aufgrund der aktuellen Geschwindigkeits-, Positions- und Zieldaten neu ermittelt, ob ein Motor beschleunigt, abgebremst oder die aktuelle Geschwindigkeit beibehalten werden soll. Diese Berechnung wird zunächst nur für Motoren durchgeführt, die das Attribut 'Master' zugeteilt bekommen haben. Die Soll-Positionen der vom Master abhängigen 'Slaves' werden aus diesen Daten interpoliert. Die tatsächlichen Ist-Positionen werden dann über einen regelungstechnischen Algorithmus aus den Soll-Größen so bestimmt, dass Anfahr- und Bremsrampen korrekt eingehalten werden. Im Fall von abrupten Bewegungsvorgaben kann es durchaus notwendig werden, die Master/Slave-Zuordnung der einzelnen Motoren während der Fahrt neu zu bestimmen. Durch den beschriebenen Algorithmus werden dabei zu jedem Zeitpunkt die korrekten Rampenvorgaben eingehalten

 

 

'=== Microstep Controller, KHS 10/2004 =================================
' 'Pseudocode'
 
' --- used prefixes ----------------------------------------------------
'i,j,k - count indices
'f - flag (true/false)
' --- used Variables --------------------------------------------------
'Sync = nr. of synchronized/interpolated Motors (0...3)
'Range= nr. of microsteps for 360° (motor+gear, per motor)
'Pos  = current position (per motor)
'Tgt  = target position (per motor)
'Rem  = remaining distance (per motor)
'Master(1-4), Slave1/2 = remaining distance, storage for master and slaves
'Step  = current step size (per motor), can be changed by +/-1 only (->ramp)
'StepS = interpolated step size (per motor), may change by more than +/-1
'Backlash = backlash of whole gear (in microsteps, per motor)
'PecRange = nr. of microsteps for 1 revolution of worm (for motor 1+2)
'PecAmp   = amplitude of PEC correction (in microsteps) (for motor 1+2)
'PecPhase = offset angle of PEC sin() function (for motor 1+2)
'PecVal   = calculated PEC correction value for a Pos value (for motor 1+2)
'Mode  = operating mode of the controller:
'        - SngChip: 0 -Outport data for 2 motors (4xPWM, 4xDir)
'                   1 -Outport data for 4 motors (8xPWM, 8xDir multiplexed)


'=== Main Loop (300Hz) ==========================================
do loop {
  ' --- calc new position and step values --------------------
  fWrEnable=0	               ' temporary disable any Tgt change in IRQ3
  if (fMove(any)) set fNewTgt  ' -> Tgt is changed within every loop!

  for iMot=3 to 0 {
    ' ---- Calc. new positions----
    Pos(iMot)+=Dir(iMot)*Step(iMot)
    ' --- normalize (ensure that 0<=Pos<Range) ---
    if (Pos(iMot)>=Range(iMot)) Pos(iMot)-=Range(iMot)
    if (Pos(iMot)<0)            Pos(iMot)+=Range(iMot)

    ' --- Move (change Tgt) ---
    if (fMove(iMot)) { Tgt(iMot)+/-=Rate }

    if (Unit(Tgt)='°') { TransformTgtToSteps() } ' new Tgt[°] received

    ' --- track Motor1 ---
    if (fTrack & iMot==0) 
      {TransformTimeToSteps() ' [°] to [Steps]
       Tgt(0)+=TrackingRate
    }

    ' --- Calc remaining distance -------------------------
    Rem=Tgt(iMot)-Pos(iMot)
    ' --- normalize (-Range/2...+Range/2) ---
    if (Rem> Range/2) Rem-=Range
    if (Rem<-Range/2) Rem+=Range
    if (Rem!=0) fDir=sgn(Rem)   ' retain previous fDir_p for Backlash calc.
    ' --- compensate Backlash ---
    Rem=abs(Rem): if (fDir!=fDir_p) Rem+=Backlash

    ' --- Evaluate Master/Slaves ----------------------
    if !(fNewTgt==0 or iMot>=Sync or Sync<=1) {
      set fSlave(iMot)
      switch {
        case iMot==2: Slave2=Rem: continue
        case iMot==1: Slave1=Rem: continue
        case iMot==0: {
          ' --- find master, i.e. motor with largest remaining distance ---
          iMaster=0: State=b00  ' track State to decide if slaves must be swapped
          if (Rem<Slave1) swap(Rem,Slave1): iMaster=1: State|=b10
          if (Rem<Slave2 && Sync==2) swap(Rem,Slave2): iMaster=2: State|=b01
          if (State==b01) swap(Slave1,Slave2)    'now motor#(slave1) < motor#(slave2)
          clr fSlave(iMaster)
          Rem_M=Rem              ' master remaining distance

          ' --- calc. ratio ---
          for each Slave {
            Ratio(Slave)=Slave/Rem_M    
          }
        }
      }
    }
    ' --- Calculate new step values (Master) ---------------
    if (Master) { 
      if (Step==0) fDirS=fDir  ' change step direction at speed 0 only
      Min=Step*(Step+1)/2      ' min. distance for stop ramp
      if (Step>0 && (fDirS!=fDir || Rem_M<Min)) {
        ' slow down if dist. to Target<Min or if moving in wrong direction
        Step--		       ' unsigned
      } else {
        ' accelerate if distance to Target is long enough and Speed<max.Speed
        Min+=Step              ' Min=(Step+1)*(Step+2)/2
        if (Rem_M>=Min && {StepMax=16*n} Step<StepMax) 
          Step++               ' unsigned
      }
      Rem_M-=<fDir>Step  ' correct master remaining distance (signed, sign depends on fDir)
    }
  }

  '--- Calculate new step values (Slaves) ------------------
  ' calc. interpolated values, then adapt the actual step value by a control feedback loop 
  iSlave=0                ' slave index (0..1)
  for iMot=0 to Sync-1 {
    if (fSlave(iMot)) {
      --- CalcSlave ---
      Rem=Ratio*Rem_M                      ' interpolate slave remaining distance
      Delta=norm(Tgt-PosDir*Rem_p(iSlave)) ' diff. between interpolated and actual
      --- calc new SlaveStep ---
      StepS=Dir*abs(Rem_p(iSlave)-Rem)     ' target step from interpolated values
      Q=(StepS-Step)*p+Delta               ' calc. Q-function (p=32, empiric factor)
      if (Q<0) Step--    ;signed  
      if (Q>0) Step++    ;signed  
      --- calc new SlavePos ---
      Pos+=Step 
      ' --- normalize ---
      if (Pos>=Range) Pos-=Range
      if (Pos<0)      Pos+=Range
      iSlave++
      Rem_p(iSlave)=Rem
    }
  }

  ' ---calc PEC ----------------------------------------------------------
  ' approx. sin(x) by 1-(1-x)^2 (quadr.1+3) or 1-x^2 (quadr.2+4)
  ' sgn='+' (for Quadr.1+2) or '-' (for Quadr.3+4)

  for iMot=0 to 1 {
    x=(Pos(iMot)-PecPhase(iMot)) % PecRange(iMot)
    if (quadr.1 or 3) x=(1.0-x)
    y=1-x^2
    if (quadr.3 or 4) y=-y
    PecVal(iMot)=y*PecAmp(iMot)
  }

  ' --- reset flags ---
  fWrEnable=1, fNewTgt=0, fNewStep=0    ' enable Tgt change in IRQ

  while (!fNewStep) { ' do idle loop until next fNewStep trigger
  ' fNewStep flag set in 2.4kHz task
    '--- Idle loop: write RS232 data ---
    if (fTrxWr) {    ' any request for send?
      get iType 'Motor 0...3 or Time
      if (iType!=Time) {
        CorrectBacklash()	    ' remove backlash from sent values
        ConvertToDegree()           ' Pos[unit=°]=360°*Pos[unit=Steps]/Range
      }
      CopyToTrxBuf(), clr fTrxWr
    }
  }
}


'=== IRQ1 (non equidist.) ===============================================
TimerTab=(dt= 3,7,12,17,21,26,30,34,37,41,43,45,47,49,50,50) ' sum =512 clocks
' On-time(Index1) =sum(dt(Index1)..dt(16)) =(0,50,100,149,196, 241,...,512) =sin(0...90°)

OutPortB = BitTab(Index1)
<generate latch pulses for PWM>
TIMER0   = T0-TimerTab(Index1)   ' T0=offset value for correct dt delay
Index1=(Index1+1) & 15
if (Index1=0) <trigger IRQ2>


'=== IRQ2 (19.2 kHz) ====================================================
' PWM-Frequenz: 9.8304MHz/512 clocks =19.2kHz
<generate latch pulses for Polarity, ParOut,..>
<Send/Receive RS232>
<Send/Receive Userbus>
<copy BitTab2 to BitTab>


'=== IRQ3 (2.4kHz) ======================================================
Read RS232, parse commands (Cmd + Data)
if (Cmd=<Move>) {set Move flags}
if (Cmd=<set new Target> && fWrEnable) {copy new values to Tgt}
if (Cmd=<get position> {set fTrxWr}  ' data evaluated in Main Idle loop
if (Cmd=<LCD>) { <send data to LCD (Userbus)> }
if (Cmd=<SerOut>) { <send data to Serial Out (Userbus)> }

' --- Set BitTab2 ---------------------------------------------
' Bytes in BitTab2 define the PortB out levels, 
' which are set at non-equidistant times within IRQ2

for i=0 to 15 { BitTab2(i)=0 }         ' clear BitTab2 Bytes
Polarity=0                             ' clear Polarity Byte
Mask=1	                               ' initialise Bitmask

n=1: if (Mode==SngChip) n=nOutB  ' nOutB defines the motor-nr @ OutB (1...3, def.=1)
for jMot=0 to 3 step n
  Phase=(Pos(jMot)+PECVal(jMot) & 63

  ' --- calc BitTab2 index (0..16) & Polarity from Phase (0..63) --------
  'Phase:  0, 1...15,16,17...31,32,33...47,48,49...63
  'Index: 16,15... 1, 0, 1...15,16,15... 1, 0, 1...15 (Phase  +0°)
  'Polar:  x, 0      ...      0, x, 1        ...    1 (x=don't care)
  'Index:  0, 1...15,16,15....1,0, 1...15,16,15.... 1 (Phase +90°)
  'Polar:  0..........x, 1 .............1, x, 0.....0

  ' --- calc index for Phase and Phase+90° ---
  for j=0 to 1 { 
    Index=Phase 
    if (Phase & 32) Polarity|=Mask      ' set bit in Polarity
    if (Phase & 16) Index=32-Index
    Index=(Index-16) & 31 
    if (Index<16) BitTab2(Index)|=Mask  ' set bit in BitTab2
    Phase+=16                           ' Phase+90° in 2nd. loop
    Mask<<1                             ' shift Bitmask
  }

' --- Finish BitTab2: set all bits lower than already set bit ---
' e.g 0000100000000000 -> 0000111111111111
Mask=BitTab(0)
for j=0 to 15 {
  BitTab(j)|=Mask
  Mask=BitTab(j)
  if (Mode=SngChip) copy Polarity0:3 to BitTab(j)4:7   ' single chip mode (2 motors)
}

' --- Count sidereal Time -----------------------------------------------
' inc Time on each TrackCnt underflow (24.339049Hz = 2400Hz*430/42401)  
TrackCnt-=430
if (TrackCnt<0) {
  TrackCnt+=42401
  Time++                  ' 1 sid.day=0x200000 = 0x200000/24.339049Hz = 86164.091 s
}
 

Home       Top