PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Servocontroller mit Mega16



Vinter
26.05.2006, 21:49
Salut,

momentan bin ich dabei, fuer ein Hexapod-Projekt einen Controller fuer 16 Servos mit einem Mega16 zu entwickeln. Programm steht soweit, C-Anteil funktioniert auch bereits; nur der Assemblerteil macht Schwierigkeiten. Hier erst einmal der Kerncode, vorzustellen in einem .S-File mit Funktionsdefinitionen und Registersicherung, abgelegt in einer Timer-ISR:



; clear masks - we do not want to write anything yet
clr amask
clr bmask
clr cmask

ser counter ; count down from 255

sec ; c is to be rotated into cmask
clt ; t is final break condition - cleared

ldi ZH,4 ; RAM pointer is at 1024, address of first servo value

; duration of loop: 32512 cycles - 1.6128 ms of variable pulse width
cycle:

clr ZL ; point Z to first servo

; loop to set lower 8 servos
reg1: rol cmask ; rotate mask to indicate position of current servo in PORTA
brcs end1 ; after 9 rotations: all servos checked, exit loop
ld servo,z+ ; copy value of current servo from RAM and move Z to next value
cp servo,counter
brne reg1 ; if servo is not to be set at current cycle: loop
or amask,cmask ; else add current servo to output mask as high
rjmp reg1 ; and loop

end1:
out _SFR_IO_ADDR(PORTA),amask ; output mask set in loop gets transferred to PORTA

; loop to set upper 8 servos - equivalent to lower 8
reg2: rol cmask
brcs end2
ld servo,z+
cp servo,counter
brne reg2
or bmask,cmask
rjmp reg2

end2:
out _SFR_IO_ADDR(PORTD),bmask ; output mask set in loop to PORTD

brts end ; if final exit condition met: jump to end
dec counter ; count comparision value for servos down
brne cycle ; if not zero: next cycle
set ; if zero: set exit condition
rjmp cycle ; and repeat one last time

end:


Hoffe, das ist so einigermassen verstaendlich. Kerngedanke ist, dass ein Zaehler von 255 runtergeht und 16 sequentiell im SRAM abgelegte Servopositionen mit ihm verglichen werden; stimmt ein Wert ueberein, wird der Servo aktiviert. Je hoeher der Wert ist, desto frueher wird also der Servo zugeschaltet, wobei alle zur gleichen Zeit wieder abschalten (nach der ISR). Naeheres im Codekommentar.

Es geht mir nun darum, den erzeugten Puls zwischen 0 und 1,6128 ms zu regeln (errechnet aus Zyklenzahl); momentan gibts aber 2 Probleme:
1. Liegt ein anderer Wert als 255 an der Registeraddresse, so wird sozusagen eine Reihe unterbrochen, die
2. kumulativ fuer jeden hintereinander auf 255 liegenden Servo eine exponentiell mit der Position steigende Zeit addiert.

Die Pulslaenge kann also nur durch o. g. Anzahl reguliert werden, nicht durch den Wert im SRAM; zudem ist sie fuer alle Servos gleich.

Ich habe keine Ahnung, wo der Fehler liegen koennte - kann mir da vielleicht jemand weiterhelfen?

Gruss und Dank,
David

PS: Falls jemand eine Optimierungsmoeglichkeit entdeckt - ich bin aufgrund der zeitkritischen Aufgabe um jeden Zyklus dankbar! Je weniger Zeit _noetig_ ist, desto besser laesst sich die Sache auf den Servo anpassen (nop)!

[Edit: Kleineren Fehler in der Schleife beseitigt, aendert nichts, Makulatur...]

.Johannes.
09.06.2006, 11:21
Hallo!
Ich habe mir auch mal einen Servocontroller gebaut (für 8 Servos)
Ich lasse alle 20ms einen Interrupt auslösen,
dann gebe ich ein HI-Signal auf alle Servos,
dann warte ich 1ms, da die Pulslänge zwischen 1 und 2ms liegt (1,5=Mitte)
die Pulslängen verändere ich auch in einer schleife, so wie du es beschrieben hast,
ist der Zähler also bei einem bestimmten Wert angelangt, dann wird das Signal wieder auf LO gezogen.
Die komplette schleife muss so konstruiert sein, dass sie dann genau 1ms
dauert, ggf. nop's einfügen und erst mal mit einem Servo probieren.
Es kann passieren, dass du nicht alle servos mit einer schleife ansteuern kannst. mach einfach 2 hintereinander.
Gruß. Johannes

Vinter
10.06.2006, 11:15
Genau auf die gleiche Art mache ich das ja auch, nur dass es nicht funktioniert ;)

.Johannes.
10.06.2006, 17:34
Hier meine Interrupt Routine..

sbi PORTB,1 ;Pulse einschalten
sbi PORTB,2
sbi PORTB,3
sbi PORTB,4
sbi PORTB,0
sbi PORTD,7
sbi PORTD,6
sbi PORTD,5


ldi r23,16
wms1:
ldi r22,00
wms2: ;etwa 1ms warten
dec r22
brne wms2
dec r23
brne wms1

ldi r22,$00
PLoop: ;Diese Schleife sollte auch wieder 1ms dauern

lds r21,0x0060 ;Position lesen
cp r22,r21 ;Vergleichen
brne n1
cbi PORTB,1 ;Ja, dann entsprechendes Portbit setzen
n1:
lds r21,0x0061
cp r22,r21
brne n2
cbi PORTB,2
n2:
lds r21,0x0062
cp r22,r21
brne n3
cbi PORTB,3
n3:
lds r21,0x0063
cp r22,r21
brne n4
cbi PORTB,4
n4:


lds r21,0x0064
cp r22,r21
brne n5
cbi PORTB,0
n5:
lds r21,0x0065
cp r22,r21
brne n6
cbi PORTD,7
n6:
lds r21,0x0066
cp r22,r21
brne n7
cbi PORTD,6
n7:
lds r21,0x0067
cp r22,r21
brne n8
cbi PORTD,5
n8:
nop
nop
nop
nop


inc r22
brne PLoop

nop

reti ;Rücksprung aus Interrupt


Am besten du Verwendest den Simulator im AVR Studio und prüfst, ob die
Zeiten auch stimmen, diese Routine ist für 12Mhz geschrieben...