Ich setzt mich mal ran, wenn die Sonne vom Himmel verschwunden ist.Zitat:
Zitat von hrei
Bis Dahin, schönen Samstag :D
da Hanni.
Druckbare Version
Ich setzt mich mal ran, wenn die Sonne vom Himmel verschwunden ist.Zitat:
Zitat von hrei
Bis Dahin, schönen Samstag :D
da Hanni.
na jetzt bin ich aber gespannt. ich werd erstmal probieren, den atmega mit externem takt laufen zu lassen. sind für mich gottseidank nur 2 jumper (hardwareseitig). nur dann kommt wieder diese ätzende fusebitterei...
Guten Morgen.
Wenn man von dem Ursprunglichem Problem ausgeht (der Post von Goblin am 0.04.2006 um 08:19 Uhr) bieten sich generell 2 Lösungswege an. (es ging darum, wieso er seine zwei Servos nur mit relativ wenigen Schritten ansteuern kann.
Die erste Möglichkeit wäre eine reine Software Realisierung:
Diese dürfte in seinem Fall nur eine marginale bis gar keine Verbesserung bringen.
Dieses dürfte vor allem dann zutreffen, wenn man innerhalb des gewissen 20 ms Fensters als Vorgabe für die Pulsfolgefrequenz bleiben möchte.
Eine weiter Möglichkeit ist die reine Hardware Realisierung:
In diesem Fall ist es sogar die einzige, die bis zu 4 Servos mit nur 1 MHz ausreichend genau ansteuern kann. (bis zu 6 bei den ATmega x8 Mikrocontrollern).
Das hier verwendete Prinzip ist die Nutzung der integrierten Hardware PWM. Da es sich in diesem Fall nur um 2 Servos handelt bietet sich der Timer 1 mit seinen 2 PWM Kanälen direkt an. Desweiteren kann dieser den Servos bis zu 1001 verschiedene Stellungen Vorgeben (ich hab keine Ahnung ob und inwiefern diese Auflösung noch Sinn macht).
Bei 4 Servos, der damit verbundenen Nutzung aller Timer sowie einer gleichen Auflösung können es immernoch 125 Schrittte sein (welche wohl sinnvoller sein dürfte).
Nachfolgend ein Codebeispiel für 2 Servos mit Timer 1 ... leider in Assembler .. da ich mich mit Bascom noch nicht wirklich beschäftigt habe.
Wie man sieht, ist der Code doch etwas umfangreicher, was wohl auch auf die Testroutinen für die 2 Servos zurückzuführen ist.Code:; #
; # Testroutine für 2 Servos
; #
; # Controller: ATmega 32
; # Takt: 1 MHz
; #
; # Methode: - PWM über 16 Bit Timer (Timer 1)
; # - Timermode: 14 (siehe Datenblatt Seite 109)
; #
; # Pinbelegung: PD4 - Ausgang - Servo 2
; # PD5 - Ausgang - Servo 1
; #
; # Servoregelbereich: 1001 Einzelschritte (kA ob das nen Servo unterstützt ...)
; # im Detail 999 - 1999 im nachfolgendem Programm (1ms - 2 ms)
; #
;
; Definitionsfile
.include "m32def.inc"
;
; Registeraliase
.undef XL ; hier unnötig und anderweitig genutzt
.undef XH
.def temp = r16
.def servo_1_l = r24 ; Stellung Servo 1
.def servo_1_h = r25
.def servo_2_l = r26 ; Stellung Servo 2
.def servo_2_h = r27
.cseg
.org 0x00
;
; ISR Vektoren
jmp isr_reset ; Reset
.org 0x2A
;
; HW Initialisierung
isr_reset:
ldi temp, high(RAMEND) ; Stackpointer
out SPH, temp
ldi temp, low(RAMEND)
out SPL, temp
ldi temp, 0xFF ; alle ungenutzten Ports -> Eingang + Pullup
out PORTA, temp
out PORTB, temp
out PORTC, temp
ldi temp, (1<<DDD4) | (1<<DDD5) ; Port D4 & D5 -> Ausgang, Rest -> Eingang + PU
out DDRD, temp
ldi temp, (1<<PD0) | (1<<PD1) | (1<<PD2) | (1<<PD3) | (1<<PD6) | (1<<PD7)
out PORTD, temp
ldi temp, high(1499) ; Timer 1 Setup
out OCR1AH, temp
ldi temp, low(1499)
out OCR1AL, temp
ldi temp, high(1499)
out OCR1BH, temp
ldi temp, low(1499)
out OCR1BL, temp
ldi temp, high(19999)
out ICR1H, temp
ldi temp, low(19999)
out ICR1L, temp
ldi temp, (1<<COM1A1) | (1<<COM1B1) | (1<<WGM11)
out TCCR1A, temp
ldi temp, (1<<WGM13) | (1<<WGM12) | (1<<CS10)
out TCCR1B, temp
;
; Main Loop
loop:
ldi servo_1_h, high(1999) ; Servo 1 - ganz nach rechts
ldi servo_1_l, low(1999)
ldi servo_2_h, high(999) ; Servo 2 - ganz nach links
ldi servo_2_l, low(999)
rcall set_servos ; Werte übergeben
rcall warten_1s
ldi servo_1_h, high(1499) ; Servo 1 - Mittelstellung
ldi servo_1_l, low(1499)
ldi servo_2_h, high(1499) ; Servo 2 - Mittelstellung
ldi servo_2_l, low(1499)
rcall set_servos ; Werte übergeben
rcall warten_1s
ldi servo_1_h, high(999) ; Servo 1 - ganz nach links
ldi servo_1_l, low(999)
ldi servo_2_h, high(1999) ; Servo 2 - ganz nach rechts
ldi servo_2_l, low(1999)
rcall set_servos ; Werte übergeben
rcall warten_1s
ldi temp, high(1499)
loop_a: ; Servos sanft in Mittelstellung fahren - dauert ca. 25 Sekunden
sbiw servo_2_l, 1
adiw servo_1_l, 1
rcall set_servos
rcall warten_50ms
cpi servo_1_l, low(1499)
cpc servo_1_h, temp
brne loop_a
rcall warten_1s
rcall warten_1s
rcall warten_1s
jmp loop
set_servos:
out OCR1AH, servo_1_h
out OCR1AL, servo_1_l
out OCR1BH, servo_2_h
out OCR1BL, servo_2_l
ret
warten_50ms: ; 50 ms Warteschleife ...
ldi YL, 0xD2
ldi YH, 0x30
sbiw YL, 1
brne PC-1
ret
warten_1s: ; 1 s Warteschleife
ldi YL, 0x3E
ldi YH, 0x0D
ldi ZL, 0x03
subi YL, 1
sbci YH, 0
sbci ZL, 0
brne PC-3
nop
ret
Nun zum zweitem Problem,
es geht darum, 16 Servos (oder auch mehr) mit 4 MHz mit einer ausreichenden Schrittweite anzusteuern.
Eine Lösung mit HardwarePWM scheidet schon aufgrund der hohen Anzahl der benötigten direkt PWM fähigen Ausgänge aus.
In diesem Beispiel gehe ich einfach mal davon aus, das an allen Pins der Ports A und B je ein Servo angeschlossen ist, der angesteuert werden soll.
Aufgrund der 'Tatsachen, daß hrei mit der in Basecom impletierten Bibliothek, bei nur einem Servo ?, eine Interuptlast von ca 150% haben dürfte* bin ich einen etwas anderen Weg gegangen.
Ich habe die 20 ms der verfügbaren Zeit in 20 Slots mit je einer Millisekunde aufgeteilt.
Hier einmal der prinzipielle Zeitliche Ablauf:
1. ms: nix großartiges tun
2. ms: die ersten 8 Servowerte laden und den jeweiligen PORT komplett auf High setzten, anschließen nix großartiges tun.
3. ms: die entsprechenden Pulswerte ausgeben, bei Slotende - den jeweiligen PORT komplett auf Low setzen
4. ms: die zweiten 8 Servowerte laden und den jeweiligen PORT komplett auf High setzten, anschließen nix großartiges tun.
5. ms: die entsprechenden Pulswerte ausgeben, bei Slotende - den jeweiligen PORT komplett auf Low setzen
5. - 20. ms: nix großartiges tun
Anmerkung: theoretisch könnte man das ganze noch weiter Aufsplitten, also z.B. 8x2 Zeitfenster zu je 2 Servos, ob das allerdings Sinn macht weiß ich im Moment nicht (ich bin gerade zu faul das zu coden).
Die Interuptlast bewegt sich so um die 80% - 90%, was auch nicht wirklich ein guter Wert ist.
Zeitkritsche Anwendungen, wie z.B. eine Software USART oder längere Interuptroutinen sind bei diesem Code auch nicht mehr wirklich empfehlenswert.
Ach ja, und hier der Code (wieder in Assembler):
Wie man sieht gehe ich in diesem Beispiel aus Gründen der Geschwindigkeit in der ISR Routine recht verschwenderisch mit den vorhanden Resourcen (sprich den Registern) um. auch ist das ganze nicht wirklich nach Codegröße optimiert.Code:; #
; # Testroutine für 16 Servos
; #
; # Controller: ATmega 32
; # Takt: 4 MHz
; #
; # Methode: - Software PWM mittels 8 Bit Timer (Timer 0)
; # - Timermode: 2 (siehe Datenblatt Seite 80)
; #
; # Pinbelegung: PA0 - Ausgang - Servo 1
; # PA1 - Ausgang - Servo 2
; # PA2 - Ausgang - Servo 3
; # PA3 - Ausgang - Servo 4
; # PA4 - Ausgang - Servo 5
; # PA5 - Ausgang - Servo 6
; # PA6 - Ausgang - Servo 7
; # PA7 - Ausgang - Servo 8
; #
; # PB0 - Ausgang - Servo 9
; # PB1 - Ausgang - Servo 10
; # PB2 - Ausgang - Servo 11
; # PB3 - Ausgang - Servo 12
; # PB4 - Ausgang - Servo 13
; # PB5 - Ausgang - Servo 14
; # PB6 - Ausgang - Servo 15
; # PB7 - Ausgang - Servo 16
; #
; # Servoregelbereich: 0 - 85 = 85 Schritte (die scheinbare Ungereimtheit liegt am Software PWM Algo ...)
; # Pulsfolgefrequenz: 20399.75 µs
; #
;
; Definitionsfile
.include "m32def.inc"
;
; Registeraliase
.def sregsave = r5
.def null = r6
.def voll = r7
.def servo_1 = r8
.def servo_2 = r9
.def servo_3 = r10
.def servo_4 = r11
.def servo_5 = r12
.def servo_6 = r13
.def servo_7 = r14
.def servo_8 = r15
.def status = r16 ; Statusregister
; 7 =
; 6 =
; 5 =
; 4 =
; 3 =
; 2 =
; 1 = Preload Servo 9 - 16 done
; 0 = Preload Servo 1 - 8 done
.def temp = r17
.def pulswidth = r18
.def cycle = r19
.def output = r20
;
; Speicher reservieren
.dseg
servo_data: .byte 16
.cseg
.org 0x00
;
; ISR Vektoren
jmp isr_reset ; Reset
.org 0x02
reti ; Int 0
.org 0x04
reti ; Int 1
.org 0x06
reti ; int 2
.org 0x08
reti ; T2 Compare
.org 0x0A
reti ; T2 Overflow
.org 0x0C
reti ; T1 Capture
.org 0x0E
reti ; T1 Compare A
.org 0x10
reti ; T1 Compare B
.org 0x12
reti ; T1 Overflow
.org 0x14
jmp isr_t0_co ; T0 Compare
.org 0x16
reti ; T0 Overflow
.org 0x18
reti ; SPI Transfer Complete
.org 0x1A
reti ; USART - RX Complete
.org 0x1C
reti ; USART - UDR Empty
.org 0x1E
reti ; USART - RX Complete
.org 0x20
reti ; ADC Complete
.org 0x22
reti ; EEPROM Ready
.org 0x24
reti ; Analog Comperator
.org 0x26
reti ; TWI
.org 0x28
reti ; Store Program Memory Ready
.org 0x2A
; HW Initialisierung
isr_reset:
ldi temp, high(RAMEND) ; Stackpointer
out SPH, temp
ldi temp, low(RAMEND)
out SPL, temp
ldi temp, 0xFF ; alle ungenutzten Ports -> Eingang + Pullup
out PORTC, temp
out PORTD, temp
out DDRA, temp ; Port A & B -> Ausgang
out DDRB, temp
ldi temp, 47 ; Timer 0 konfigurieren
out OCR0, temp
ldi temp, (1<<WGM01) | (1<<CS00)
out TCCR0, temp
in temp, TIMSK
ori temp, (1<<OCIE0)
out TIMSK, temp
;
; Speicher und Register initialisieren
ldi temp, 42
ldi ZH, high(servo_data)
ldi ZL, low(servo_data)
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z+, temp
st Z, temp
clr pulswidth
clr cycle
clr status
clr null
ldi temp, 0xFF
mov voll, temp
out TCNT0, null
clr temp
sei
;
; Mainloop
loop:
ldi temp, 85
sts servo_data, null ; Servo 1 nach links
sts servo_data+1, temp ; Servo 2 nach rechts
sts servo_data+2, null ; Servo 3 nach links
sts servo_data+3, temp ; Servo 4 nach rechts
sts servo_data+4, null ; Servo 5 nach links
sts servo_data+5, temp ; Servo 6 nach rechts
sts servo_data+6, null ; Servo 7 nach links
sts servo_data+7, temp ; Servo 8 nach rechts
sts servo_data+8, null ; Servo 9 nach links
sts servo_data+9, temp ; Servo 10 nach rechts
sts servo_data+10, null ; Servo 11 nach links
sts servo_data+11, temp ; Servo 12 nach rechts
sts servo_data+12, null ; Servo 13 nach links
sts servo_data+12, temp ; Servo 14 nach rechts
sts servo_data+14, null ; Servo 15 nach links
sts servo_data+15, temp ; Servo 16 nach rechts
rcall warten ; ne Weile Warten
sts servo_data, temp ; und nun genau andersrum ....
sts servo_data+1, null
sts servo_data+2, temp
sts servo_data+3, null
sts servo_data+4, temp
sts servo_data+5, null
sts servo_data+6, temp
sts servo_data+7, null
sts servo_data+8, temp
sts servo_data+9, null
sts servo_data+10, temp
sts servo_data+11, null
sts servo_data+12, temp
sts servo_data+13, null
sts servo_data+14, temp
sts servo_data+15, null
rcall warten ; ne Weile Warten
ldi temp, 42 ; und nun alle Servos auf Mittelstellung
sts servo_data, temp
sts servo_data+1, temp
sts servo_data+2, temp
sts servo_data+3, temp
sts servo_data+4, temp
sts servo_data+5, temp
sts servo_data+6, temp
sts servo_data+7, temp
sts servo_data+8, temp
sts servo_data+9, temp
sts servo_data+10, temp
sts servo_data+11, temp
sts servo_data+12, temp
sts servo_data+13, temp
sts servo_data+14, temp
sts servo_data+15, temp
rcall warten ; ne Weile Warten
jmp loop
;
; Subroutinen
warten: ; Warteschleife
ldi XL, 0x3E ; geschätzte Wartezeit aufgrund der
ldi XH, 0x0D ; hohen Interuptlast ca. 5-6 Sekunden
ldi YL, 0x03 ;
subi XL, 1
sbci XH, 0
sbci YL, 0
brne PC-3
nop
ret
;
; ISR Routinen
isr_t0_co:
in sregsave, SREG
ldi ZH, high(isr_t0_oc_table)
ldi ZL, low(isr_t0_oc_table)
add ZL, cycle
adc ZH, null
ijmp
isr_t0_oc_table:
rjmp isr_t0_oc_0
rjmp isr_t0_oc_1
rjmp isr_t0_oc_2
rjmp isr_t0_oc_3
rjmp isr_t0_oc_4
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_0
rjmp isr_t0_oc_19
isr_t0_oc_0:
andi status, 0b11111100
inc pulswidth
cpi pulswidth, 85
brne PC+3
clr pulswidth
inc cycle
out SREG, sregsave
reti
isr_t0_oc_1:
sbrc status, 0
rjmp isr_t0_oc_1_done
lds servo_1, servo_data
lds servo_2, servo_data+1
lds servo_3, servo_data+2
lds servo_4, servo_data+3
lds servo_5, servo_data+4
lds servo_6, servo_data+5
lds servo_7, servo_data+6
lds servo_8, servo_data+7
ori status, 0b00000001
isr_t0_oc_1_done:
out PORTA, voll
inc pulswidth
cpi pulswidth, 85
brne PC+3
clr pulswidth
inc cycle
out SREG, sregsave
reti
isr_t0_oc_2:
cp pulswidth, servo_1
ror output
cp pulswidth, servo_2
ror output
cp pulswidth, servo_3
ror output
cp pulswidth, servo_4
ror output
cp pulswidth, servo_5
ror output
cp pulswidth, servo_6
ror output
cp pulswidth, servo_7
ror output
cp pulswidth, servo_8
ror output
inc pulswidth
cpi pulswidth, 85
brne PC+4
clr pulswidth
inc cycle
clr output
out PORTA, output
out SREG, sregsave
reti
isr_t0_oc_3:
sbrc status, 0 ; Preload schon erledigt ?
rjmp isr_t0_oc_3_done ; - Ja
lds servo_1, servo_data+8
lds servo_2, servo_data+9
lds servo_3, servo_data+10
lds servo_4, servo_data+11
lds servo_5, servo_data+12
lds servo_6, servo_data+13
lds servo_7, servo_data+14
lds servo_8, servo_data+15
ori status, 0b00000010
isr_t0_oc_3_done:
out PORTB, voll
inc pulswidth
cpi pulswidth, 85
brne PC+3
clr pulswidth
inc cycle
out SREG, sregsave
reti
isr_t0_oc_4:
cp pulswidth, servo_1
ror output
cp pulswidth, servo_2
ror output
cp pulswidth, servo_3
ror output
cp pulswidth, servo_4
ror output
cp pulswidth, servo_5
ror output
cp pulswidth, servo_6
ror output
cp pulswidth, servo_7
ror output
cp pulswidth, servo_8
ror output
inc pulswidth
cpi pulswidth, 85
brne PC+4
clr pulswidth
inc cycle
clr output
out PORTB, output
out SREG, sregsave
reti
isr_t0_oc_19:
inc pulswidth
cpi pulswidth, 85
brne PC+6
clr pulswidth
inc cycle
cpi cycle, 20
brne PC+2
clr cycle
out SREG, sregsave
reti
Allerdings bietet der Code mit kleineren Änderungen auch die Möglichkeit 32 Servos anzusprechen ... nur, wo sollen dann die Daten für diese herkommen ?
Eines ist vielleicht noch recht interessant: Ein ATmega 8 würde den selben Code ein klein wenig schneller abarbeiten (1-2 Takte je Interupt .. bin mir da nicht so ganz sicher).
@ hrei:
Ich denke, ich habe meine Lektion gelernt.
Fazit:
- es gibt immer mehrere mögliche Wege die zum Ziel führen.
- selbst scheinbar viel Zeit kann bei genauerer Betrachtung sehr knapp werden... So hat man bei einem MHz und einer Auflösug von 100 Schritten auf einmal nur noch 10µs Zeit um die entsprechenden Werte einzustellen. 10 µs entsprechen nunmal aber exakt 10 Takten ... und die braucht der ATmega 32 schon um nur in die ISR und wieder raus zu springen ... ohne irgendetwas zu tun. (3 für den Sprung zum ISR Vektor, 3 für den Sprung zur ISR Routine und 4 für den Rücksprung zum Programm)
- 16 Servos bei 1 MHz gehen mit einer vernünftigen Auflösung in einem vernünftigem Zeitraster nicht wirklich. (siehe weitere ausführungen zu Punkt 2)
- ich denke, bei genauerer Betrachtung ist auch mein Code wesentlich verbesserungsbedürftig. Insbesondere die doch recht hohe Interuptlast stört mich schon etwas ....
hrei, wenn es dir nichts ausmacht würde ich dich bitten, diese beiden Programme einmal mit einem Oskar und / oder Servos zu überprüfen und mir mitzuteilen, ob meine Programme wirklich funkionieren. Laut Simulation im AVR studio tun sie es, ich hätte allerdings gerne Gewissheit.
Für die, die diese Codes dennoch recht interessant finden, ist das ganze gezippt nocheinmal im Anhang zu finden.
Und nun noch nen schönen und vor allem sonnigen Sontag,
da Hanni.
------------------------------------------------------------------
* Interuplast: meine Auslegung: Zeit die die ISR von der theoretisch maximal möglichen Zeit** belegt.
** maximal mögliche Zeit: Zeit zwischen 2 Interupts des gleichen Types
Hallo Hanni,
zunächst mal meinen uneingeschränkten Respekt für Deine Bemüngen, die Theorie in die Praxis umzusetzten. Endlich mal jemand, der es nicht beim Labern lässt.
Für jetzt und hier nur ganz kurz dazu (es gibt zu viel gutes Wetter und anderes außerhalb des Forums):
Wenn man es praktisch betreibt, fällt auf, daß Bascom hier gar nicht so übel abschneidet, was die Effizienz anbelangt. Es ist ja immer im Auge zu behlten, daß von einer Hochsprache im allgemeinen und von Bascom im besonderen die weitestgehende Portabilität verlangt wird. Also vom Tiny 2313 bis zum Mega128 soll derselbe Befehl/dieselbe Konfiguration benutzt werden können.
Das führt zum benutzen eines Timers, mit trotzdem freier Zuordnung der Portpins. Unter diesen Umständen muss man eben auf jeden Fall einen höheren Takt als 1 Mhz benutzen, um zu halbwegs ordentlichen Auflösungen und Ausführungsgeschwindigkeiten zu kommen.
Ist ja auch kein Problem, Goblin zum Beispiel braucht ja dafür keinen externen Quarz, er muss nur den internen Oszillator auf 8 Mhz "umfusen".
Deine Routinen werde ich mir bei etwas mehr Zeit genauer ansehen und messen (sie haben es verdient) und schaun, inwieweit davon nicht das ein oder andere für Sonderfälle als Lib ausgestaltet werden kann.
Viele Grüße
Henrik
Danke für die Blumen :DZitat:
Zitat von hrei
Für ein derartiges Projekt bin ich sicherlich zu haben, auch wenn meine Zeitplanung zum Teil doch recht eng gesteckt ist.
Grüße.
da Hanni.
Hmm .. was ist dabei rausgekommen ?
(also beim Anshauen ?)
Grüße,
da Hanni.