Bewässerungsautomätchen:
Beitrag "Blumen gießen"
Ich hab's bis jetzt soweit, daß der Servo periodisch hin- und herfährt.
Leider ist der Ansatz etwas verwurschtelt, das Ergebnis ist
unregelmäßig.
Außerdem vermute ich, daß eine solche Steuerung wesentlich
übersichtlicher programmiert werden kann - ich kann's leider noch nicht.
Deswegen freue ich mich über Verbesserungsvorschläge!
Eigentlich wär ja die Steuerung als Reihe von Funktionen gut zu
gebrauchen..
servo(position)
nichtstun(3 minuten)
servo(position)
nichtstun(24 stunden)
aber wie, mit nur einem timer?
Man muß in jedem Fall zwischen einem gut anpaßbaren Programm und der
sparsamen, exakt zugeschnittenen Lösung abwägen.
Der Servo braucht ja eigentlich nur 2 Signallängen, aber es ist
natürlich praktisch, wenn man die Zeiten gut anpassen kann. Aber mit
welcher Auflösung? Volle 8 Bit werden vermutlich sowieso nicht zu
erreichen sein.
Desweiteren weiß ich nicht, wie man verhindert, daß das Umschalten
zwischen den Timer-Interrupts, Prescalern, etc. in allgemeinem Chaos
endet.
Wahrscheinlich läßt sich auch die ISR entschlacken, aber ich sehe nicht
so ganz wie.
Naja, hier mal der Code:
Wenn man den Systemtakt halbiert, hat man beim Timer mit Prescaler 256
eine Auflösung von 106.6µs pro bit. Das würde ausreichen, um das
Servosignal direkt per PWM zu erzeugen, aber dann hat man bestenfalls 10
Positionen / pro Bit 18° Auslenkung.
Periode ist dann 27.3ms.
Aber spart das soviel Code? Wichtiger ist eigentlich die übersichtliche
Struktur des Programms.
> Desweiteren weiß ich nicht, wie man verhindert, daß das Umschalten> zwischen den Timer-Interrupts, Prescalern, etc. in allgemeinem Chaos> endet.
Indem man das einfach nicht macht.
Einige dich mit dir selbst wie du den Timer einstellst und
belasse es dabei.
Der Timer gibt dir eine Zeitbasis und von der leitest du in
der ISR alles weitere ab.
Wenn dein Timer zb alle 10µs einen Interrupt auslöst,
dann ist es leicht in der ISR daraus eine Uhr zu bauen,
indem die 10µs aufsummiert werden. Wenn du zusätzlich
ein periodisches Signal alle 1 ms brauchst, dann baust du
noch einen zusätzlichen Zähler mit ein, der bei jedem
ISR Aufruf um 1 weiterzählt, bis 100 erreicht sind. Hat
der Zähler 100 erreicht, dann sind seit Beginn der Zählerei
auf diesem Zähler 1 ms vergangen.
Welchen µC benutzt du eigentlich, dass du nur einen Timer
zur Verfügung hast?
Danke erstmal für Deine Antwort. Ich benutze einen tiny13 (
http://atmel.com/dyn/products/product_card.asp?part_id=3175 )
Wenn ich aber so eine große Zeitspanne abdecken will, also 24 Stunden in
10µS -Schritten, brauche ich aber 'viele' Variablen.
Außerdem, wenn das Timing in Software gemacht wird, kann ich um den
Faktor >256 weniger oft einen Sleep-Mode verwenden (Batteriebetrieb).
Aber vielleicht ist der Mittelweg der richtige: Für die 24h Wartezeit
heruntertakten, und sonst eine einheitliche Zeitbasis?
Hast Du vielleicht auch einen Vorschlag, wie die Ablaufsteuerung, die ja
eigentlich jetzt nur in der ISR stattfindet, übersichtlich nach main()
verlagert werden könnte?
Koko Lores wrote:
> Danke erstmal für Deine Antwort. Ich benutze einen tiny13 (> http://atmel.com/dyn/products/product_card.asp?part_id=3175 )>> Wenn ich aber so eine große Zeitspanne abdecken will, also 24 Stunden in> 10µS -Schritten, brauche ich aber 'viele' Variablen.
Halb so wild.
Eine Variable zählt bis 100 -> ms
1000 ms ergeben 1 Sekunde
60 Sekunden sind 1 Minute
60 Minuten sind 1 Stunde
24 Stunden sind 1 Tag
Macht insgesammt, (Moment muss zählen) 5 Variablen.
> Außerdem, wenn das Timing in Software gemacht wird, kann ich um den> Faktor >256 weniger oft einen Sleep-Mode verwenden (Batteriebetrieb).
Da musst du dich entscheiden, was denn dein Basistakt sein
soll. Ich hab 10µs genommen um mal irgendeine Zahl ins Spiel
zu bringen. Die Frage ist: welches muss dein kleinster
Basistakt sein. Ich denke mal, den wird dir das Servo vorgeben
und da hängt es wieder davon ab, wieviele Servostellungen du
erreichen willst. Mit 10µs kannst du ca. 100 Servopositionen
erreichen. Wenn dir 50 auch reichen dann peile mal 20µs an.
Welchen Wert du dann tatsächlich nimmst, hängt auch davon
ab mit welchem Takt du den µC betreibst und welchen Vorteiler
du nimmst.
Du hast eine Wunschvorstellung der kleinsten Zeiteinheit.
Dann probierst du mal, mit welcher Vorteilereinstellung
du diesem Wunsch nahe kommst. Daraus ergbt sich dann das
Ist-Basistiming. Und mit dem rechnest du dann weiter.
> Batteriebetrieb
Die meiste Zeit wird in der ISR nicht viel passieren.
Der µC legt sich also gleich wieder schlafen, nachdem
er die Zeit-Buchhaltung erledigt hat.
Ausserdem: Welche Batterie willst du nehmen? Wenn dein
Servo einen Schlauch abquetscht, dann hat es eine 2000mAH
in ein paar Stunden ausgelutscht. (Das ist das was mir persönlich
an dieser Lösung nicht gefällt: Das Servo muss ständig arbeiten)
>> Aber vielleicht ist der Mittelweg der richtige: Für die 24h Wartezeit> heruntertakten, und sonst eine einheitliche Zeitbasis?>> Hast Du vielleicht auch einen Vorschlag, wie die Ablaufsteuerung, die ja> eigentlich jetzt nur in der ISR stattfindet, übersichtlich nach main()> verlagert werden könnte?
Lass sie doch in der ISR.
Wenn dein Basistakt nicht allzu schnell ist, dann hast du massenhaft
Zeit in der ISR. Ob die jetzt in der ISR ist oder in main(): Es
ist immer die gleiche Ablaufsteuerung und damit gleich übersichtlich.
Vielen Dank, jetzt bin ich wieder motiviert, es weiter zu probieren!
Der Servo betätigt ein Ventil, zum Abquetschen habe ich keinen passenden
Schlauch gefunden. Aber auch das Abquetschen mit Exzenter braucht nur
beim Verstellen Energie.
Grob gerechnet:
Servo
1s * 350mA = 350mAs = 0.1mAh
µC
5m * 3mA = 900mAs = 0.25mAh
24h * 0.7mA = 16.8mAh
-> 1 Tag ~ 20mAh, eher weniger
also mehr als 100 Tage gießen, dem 3-Monats-Urlaub steht nichts mehr im
Wege.. Ich hoffe, man kann für den Stand-By noch kleinere Werte
annehmen, das ist ja erstaunlich viel!
Koko Lores wrote:
> Vielen Dank, jetzt bin ich wieder motiviert, es weiter zu probieren!>> Der Servo betätigt ein Ventil, zum Abquetschen habe ich keinen passenden> Schlauch gefunden.
Ah richtig. Du hast ja ein Ventil umgebaut.
> Servo> 1s * 350mA = 350mAs = 0.1mAh>> µC> 5m * 3mA = 900mAs = 0.25mAh> 24h * 0.7mA = 16.8mAh>>> -> 1 Tag ~ 20mAh, eher weniger>> also mehr als 100 Tage gießen, dem 3-Monats-Urlaub steht nichts mehr im> Wege.. Ich hoffe, man kann für den Stand-By noch kleinere Werte> annehmen, das ist ja erstaunlich viel!
Mal blöd gefragt: Warum willst du das Teil nicht an
den Netzstrom hängen? So häufig haben wir in ME nun auch
wieder keine Ausfälle. Noch eine kleine Batterie zum Buffern
der Uhr falls doch und gut ists.
Habe draußen keine Steckdose, und hätte sonst auch keine Lust auf den
Kabelsalat - man könnte natürlich auch gut Solarzellen benutzen,
anstelle der Batterien!
Aber so ist es sehr günstig / einfach (+ schnell nachzubauen). 3
Batterien, Stückchen Lochrasterplatine, Tiny13, Servo, Ventil, Schlauch.
Noch einen Kondensator, Widerstand, Transistor.
Wenn man nicht an der Hardware/Software lernen muß wie ich gerade, ist
das in zwei Stunden aufgebaut und am Laufen. Eine nette, kleine,
sinnvolle Bastelei. Finde ich.
Jetzt hab ich die Schn erstmal wieder voll. Das darf doch nicht wahr
sein! Erst sieht alles vielversprechend aus, und jetzt stimmt das Timing
anscheinend vorne und hinten nicht:
1
// Hardware defines
2
// Servo
3
#define SERVOPORT PORTB
4
#define SERVOPIN PB4
5
#define SERVO_POS_A 97 // closed
6
#define SERVO_POS_B 30 // open
7
#define SERVO_PERIOD 20 // ms
8
#define SERVO_TIME 2000 // ms (16 bit)
9
10
11
12
// GLOBAL VARIABLES
13
14
volatilestruct{
15
unsignedservo:1;
16
unsignedpulse:1;
17
unsignedmove:1;
18
}flag;
19
20
volatileuint8_ts_pulse_len=SERVO_POS_B;
21
volatileuint8_ts_pulse_cur=0;
22
23
volatileuint16_tnow_ms;
24
volatileuint16_tms_0B;
25
volatileuint16_tnow_ms_0B;
26
27
volatileuint16_tmillisecond=0;// 1000
28
volatileuint8_tsecond=0;// 60
29
volatileuint8_tminute=0;// 60
30
volatileuint8_thour=0;// 24
31
32
33
34
// INTERRUPT SERVICE ROUTINES
35
36
ISR(TIM0_COMPA_vect)
37
{
38
39
OCR0A=TCNT0+1;//13.3µs
40
41
42
// servo pulse
43
if(flag.pulse==1)
44
{
45
46
if(s_pulse_cur==0)
47
{
48
SERVOPORT|=(1<<SERVOPIN);// high
49
s_pulse_cur++;
50
51
// timer for signal period
52
if((millisecond+SERVO_PERIOD)>=1000)
53
{
54
now_ms=millisecond+SERVO_PERIOD-1000;
55
}
56
else
57
{
58
now_ms=millisecond+SERVO_PERIOD;
59
}
60
}
61
elseif(s_pulse_cur==s_pulse_len)
62
{
63
SERVOPORT&=~(1<<SERVOPIN);// low
64
s_pulse_cur=0;
65
flag.pulse=0;
66
}
67
else
68
{
69
s_pulse_cur++;
70
}
71
}
72
73
}// ISR
74
75
76
ISR(TIM0_COMPB_vect)
77
{
78
79
OCR0B=TCNT0+75;// 1ms
80
81
ms_0B++;
82
83
millisecond++;
84
if(millisecond==1000)// 1s
85
{
86
millisecond=0;
87
second++;
88
if(second==60)// 1m
89
{
90
second=0;
91
minute++;
92
if(minute==60)// 1h
93
{
94
minute=0;
95
hour++;
96
if(hour==24)// 1d
97
{
98
hour=0;
99
}
100
}
101
}
102
}
103
104
105
// servo signal generation
106
if(flag.servo&&millisecond==now_ms)// no good, takes up to 1 second before 'true' when beginning signal
107
{
108
// enable pulse generation
109
flag.pulse=1;
110
}
111
112
113
// activate servo once
114
if(flag.move)
115
{
116
flag.servo=1;
117
flag.move=0;
118
119
// timer for servo signal
120
now_ms_0B=ms_0B+SERVO_TIME;
121
}
122
elseif(ms_0B==now_ms_0B)
123
{
124
flag.servo=0;
125
}
126
127
128
129
}// ISR
130
131
132
133
// MAIN
134
135
intmain(void)
136
{
137
138
// Variables
139
140
141
// PORT SETUP
142
// writing to PORTx before setting the DDRx is important to guarantee
143
// intended power-up pin states
144
// inputs are active-low (pulled to GND when switches are closed)
145
// enable internal pull-up resistors for inputs, outputs 'low'
146
PORTB=0;
147
148
// define outputs in data direction register
149
DDRB=1<<SERVOPIN;
150
151
// TIMER
152
/*/ Prescales the System Clock
153
CLKPR = 1<<CLKPCE;
154
CLKPR = 0; */
155
156
// set Timer/Counter
157
TCCR0B=T0_CLK_64;// 13.3µs - 3.4133ms
158
// Interrupts
159
TIMSK0|=1<<OCIE0A|1<<OCIE0B;
160
161
// SLEEP MODE
162
set_sleep_mode(SLEEP_MODE_IDLE);
163
164
// INTERRUPTS
165
sei();
166
167
flag.move=1;
168
// // // // // // // // // // // // // //
169
170
171
for(;;)// ever
172
{
173
sleep_mode();
174
}
175
}
Der Puls ist jetzt 750µs lang, dabei sollte er nur etwa 30*13.33µs = 400
µs lang sein. Die ganze Pulsfolge sollte 2 Sekunden lang sein, und liegt
jetzt bei 900ms. Irgendwas läuft extrem schief.
Und ich habe das Gefühl, daß ich irgendwie auf dem Holzweg bin, und das
alles viel eleganter geht.
Codegröße schon 582 bytes..:-(
Dabei erzeugt es lediglich eine Pulsfolge.. traurig.
Das liegt vermutlich auch an den vielen volatile Variablen, oder?
Ich hoffe auf weitere glückbringende Hinweise!
Habe leider immer noch keine Fortschritte gemacht. Daher mal konkrete
Fragen:
Muß ich die Variablen überhaupt alle volatile deklarieren, wenn sie in
verschiedenen Interrupts benutzt werden?
Wenn ich eine lokale variable in einer ISR benötige, die ihren Wert bis
zum nächsten Aufruf behält, wie wird sowas deklariert? static?
Und könnten die Timing-Probleme durch die 16 bit variablen verursacht
werden? Andererseits wird damit ja nur im Interrupt gerechnet..
Oder braucht die ISR schon zuviel Zeit? Alle 75 Timer-Takte werden beide
Interrupts 'gleichzeitig' ausgelöst, bringt das was durcheinander?
> Muß ich die Variablen überhaupt alle volatile deklarieren, wenn sie in> verschiedenen Interrupts benutzt werden?
Du mußt sie dann als volatile deklarieren, wenn sie von einer ISR und
dem Hauptprogramm zugegriffen werden.
> Wenn ich eine lokale variable in einer ISR benötige, die ihren Wert bis> zum nächsten Aufruf behält, wie wird sowas deklariert? static?
Das ist eine Möglichkeit. Man nutzt si normalerweise, wenn die Variable
ausschließlich der ISR (oder auch einer normalen Funktion) bekann sein
soll.
Eine andere Möglichkeit ist, eine globale Variable zu nehmen.
>Du mußt sie dann als volatile deklarieren, wenn sie von einer ISR *und*>dem Hauptprogramm zugegriffen werden.
= mindestens einer ISR und dem Hauptprogramm?
Aber was ist bei zwei ISR ohne Hauptprogramm?
Könntest Du so gut sein, und Dich auch der anderen Probleme kurz
annehmen? Ich wäre Dir ausserordentlich dankbar - so ein kleines
Progrämmchen, und ich bekomme es nicht hin, weil ich die Probleme nicht
sehe.
> = mindestens einer ISR und dem Hauptprogramm?> Aber was ist bei zwei ISR ohne Hauptprogramm?
Da eine ISR die andere nicht unterbrechen kann auf so einem kleinen
Ding, müssen sie nicht volatile sein.
Zu Deinen anderen Problemen: Ich versuchs gerade, mir ist aber nicht
klar, was der Servo für ein Signal bekommt. Wenn Du das nochmal kurz
beschreiben könntest, fällt es mir leichter, den inneren Schweinehund zu
überwinden...
Zudem kenn ich den AVR nicht gut.
Ha, toll.
Es soll ungefähr das Folgende ausgegeben werden.
Servosignal:
__
| |
| |______________________.....
High 0.6-1.3ms dann ca. 20ms Low
Die Servostellung ist durch die Dauer des Impulses bestimmt,
normalerweise 1-2ms, mit 1.5ms = Mittelstellung. Die Zeiten, die ich
versuche stehen unter dem letzten Code.
Danke!
Also handelt es sich um PWM.
Sehe ich das richtig, daß Dein µC PWM hardwaremäßig kann? Dann solltest
Du das auch so manchen.
Du brauchst dann nur noch eine ISR, die mit dem Ablauf eines vollen
Zyklus (also high- + low-Output zusammen) aufgerufen wird.
Die zählt dann Deine Softwareuhr hoch und prüft, ob eine Aktion
auszuführen ist. Wenn der Servo verstellt werden muß, wird nur das
Timerregister verändert, das die Länge des Pulses bestimmt.
Ich hatte auch über Hardware-PWM nachgedacht, aber ich glaube, daß es
nicht besser ist: Der tiny13 hat nämlich nur den einen Timer/Counter,
und wenn ich den auf PWM einstelle, habe ich eine schlechtere Auflösung,
da ja die 20ms mit 8 Bit aufgelöst werden müssen. Und derselbe Prescaler
muss dann meine Zeitbasis schaffen - das geht nicht auf. Für die
benötigten 20 Perioden lohnt sich das eigentlich auch nicht, es gibt ja
sonst nichts zu berechnen.
Oder übersehe ich etwas?
Es gilt herauszufinden, warum das Timing so schief läuft. Gibt es
irgendwo eine Anleitung zum Thema 'Debuggen' mit Debugger? Wenn ich
irgendwo sehen könnte, wie lange die ISR braucht, könnte ich zumindest
feststellen, ob sie zu lange braucht.
Oder soll ich mal einen Pin für die Dauer der ISR einschalten? Könnte
ich messen..
Die maximale Auflösung der PWM wird durch die Auflösung des Timers
gegeben. Für Deinen Servo dürfte es keine große Rolle spielen, ob die
Zykluslänge 20 µs, oder 50 ist - solange die Frequenz viel höher ist,
als der Tiefpaß am Eingang des Servos - sehe ich das richtig?
Du kannst also den Zyklus des Timers so einstellen, wie es am besten
paßt.
Der Software-Timer für Deine Ereignisplanung ist jedenfalls das kleinste
Problem: Du mußt ja nicht in hh:mm:ss:msn rechnen - es müßte doch
reichen, infach einen Sekundenzähler hochzuzählen, der bei 86400 wieder
auf 0 gesetzt wird.
Den Sekundentakt bastelst Du Dir durch abzählen der Zyklen Deiner PWM.
Nachdem ich etwas gerechnet habe: Die 8-Bit Auflösung müßte für Deinen
Servo eigentlich ausreichen: Das entspricht knapp 0,4% pro Schritt.
Ich habe sowas ähnliches vor einiger Zeit mit einem MSP430F2011 gemacht
- nachdem ich mit Software-PWM auf dem Bauch gelandet war. Das hat auf
Anhieb wunderbar funktioniert.
Ich verstehe Dich nicht..oder Du mich nicht..oder beides?
Das Servosignal ist ein Impuls mit (in meinem Fall) 500-2500µs =
0.5-2.5ms. Diese Impulslänge ist entscheidend, wie der Servo das intern
mit dem Ist-Stand vergleicht, weiß ich nicht. Der Impuls wird dann etwa
alle 20ms wiederholt (unkritisch).
Mit PWM muss der Timer also eine Periode von etwa 20ms abdecken. Ein
Wert eines 8-Bit Timers entspricht dabei einer Dauer von 78µs (wenn man
den Takt einstellen kann - ansonsten kommt man nicht auf die volle
Auflösung). D.h. ich habe für die 2ms On-Zeit etwa 25 Schritte.
Meinstest Du das?
Könnte man probieren, spart vielleicht auch ein bißchen Platz im Flash -
aber dann weiß ich immer noch nicht, warum der obige Code Mist baut.
> Das Servosignal ist ein Impuls mit (in meinem Fall) 500-2500µs => 0.5-2.5ms. Diese Impulslänge ist entscheidend, wie der Servo das intern> mit dem Ist-Stand vergleicht, weiß ich nicht. Der Impuls wird dann etwa> alle 20ms wiederholt (unkritisch).
OK, das war mir als Modellbaubanausen nicht klar.
Trotzdem sollte sich die Sache mit 8-Bit-PWM erledigen lassen:
- Zykluszeit 2,5 ms
- Auflösung besser als 0,4%
- Wenn die Zykluszeit abgelaufen ist, wird der PWM-Output abgeschaltet,
der Timer läuft so weiter, wie er eingestellt ist.
- 7 Zyklen ohne PWM-Ausgabe
- 1 Zyklus mit PWM-Ausgabe
usw.
Das Ganze geht mit einer ISR.
Das bei Deinem Programm ist ein Design-Problem - mein Job ist u.a.
ordentlicher Softwareentwurf und mein innerer Schweinehund ist in der
Freizeit nicht dazu zu bewegen, sich mit schrägem Design
auseinanderzusetzen. Also nicht böse sein.
Man sagt zwar so leichtsinnig, dem Inschinjör sei nicht zu zu schwör,
aber das stimmt nicht. Aus zerknitterten Komonenten kann er keine
Hochglanzlösung machen... und wenn er könnte, fehlt der Ehrgeiz.
Wie sollte man in einem Forum böse sein? Entweder man freut sich über
'gute' Beiträge, oder es sind keine Beiträge da.
Was mich jetzt aber interessiert, ist, ob Du den Versuch, die
Signalerzeugung in Software zu erledigen als Design-Problem ansiehst,
oder etwas anderes. Wenn es noch was anderes ist, würde ich natürlich
gerne wissen, um was es da geht, damit ich es ordentlich entwerfen kann.
Oder aber - wie würdest Du die Aufgabe lösen, wenn kein Hardware-PWM
möglich wäre?
Daß das Gewusel mit den Flags etc. nicht elegant ist, fürchte ich ja
auch, aber mir ist leider keine bessere Idee gekommen..
Und bei den kleinen Tinys aufpassen: Hardwarestack! Maximal 3 Ebenen,
dann wird von vorne überschrieben. Das produziert oft Mist, für mich ein
Grund auf den Tinys Assembler zu proggen, passt mehr 'rein und man
verheddert den Stack nicht so schnell. ;-)
Das sind 66,5 Takte in 13,3 µs - schätze, das wird eng... Da ja auch
noch eine zweite ISR vorhanden ist, würde eine Softwarelösung - die man
vermutlich wirklich am besten in ASM realisieren sollte - nicht sehr
präzise.
Machs mit Hardware-PWM - das ist einfacher, genauer und belastet das
Rechenknechtchen kaum.
Der Design-Aspekt der Sache:
Erste Frage bevor eine Zeile programmiert wird: Ist das Problem per
Software auf der gegebenen Hardware lösbar?
>Erste Frage bevor eine Zeile programmiert wird:
Erst mal auf dem Konzeptblock die Programmstruktur und den geplanten
Ablauf festhalten, dadurch schließen sich oft schon nicht machbare
Sachen aus. Außerdem ist die Struktur hinterher wiederzuerkennen und
durchschaubarer.
3/4 der Programmierarbeit findet auf dem Papier statt, das Umsetzen in
Code ist dann das kleinste Problem.
Ich sehe es mittlerweile ein (danke), und habe Uhus Vorschlag von weiter
oben mal in die Tat umgesetzt. Jetzt gibt es aber wieder ein neues
Problem:
Die Pulsweite ist nicht konstant, teilweise gibt es Phantompulse
Der Pulsabstand stimmt meistens, aber es gibt Lücken.
Ich bin mir allerdings auch nicht 100% sicher, ob ich den richtigen PWM-
Mode ausgewählt habe.
Fast PWM Mode:
Clear OC0B on Compare Match, set OC0B at TOP
Mode WGM2 WGM1 WGM0 Mode TOP Update OCRx TOV
3 0 1 1 Fast PWM 0xFF TOP MAX
1
// set Timer/Counter / PWM
2
TCCR0B=T0_CLK_64;// 13.3µs - 3.4133ms
3
// Interrupts
4
TIMSK0|=1<<OCIE0A;
5
6
7
ISR(TIM0_COMPA_vect)
8
{
9
10
OCR0A=TCNT0+188;//2506µs
11
12
// servo pulse
13
14
15
if(servo.cycle==7)
16
{
17
// enable PWM
18
TCCR0A=(1<<COM0B1)|(1<<WGM01)|(1<<WGM00);// Mode 3: Fast PWM
Äh, liegt das vielleicht daran, daß der Interrupt nicht mit der PWM
synchron ist, und diese vor Erreichen des Zielwertes ausschaltet?
Kann das mit den PWM Einstellungen behoben werden?
> Die Pulsweite ist nicht konstant, teilweise gibt es Phantompulse> Der Pulsabstand stimmt meistens, aber es gibt Lücken.
Läuft da noch die zweite ISR mit?
Danke für den Mode-Vorschlag, kann ich leider erst morgen ausprobieren.
Die zweite ISR ist nicht mehr da, geht auch gar nicht, weil es nur zwei
Compare-Units gibt.. Ach so, overflow ginge ja auch noch.
Also, wie synchronisieren? Vielleicht fällt mir morgen was ein..
So ist es auch nicht synchron. Hätte mich irgendwie auch gewundert.
_________________________
___________________________| PWM COMPB |________________
__________________________________________
_______| 2500µs INT COMPA |________________
Die überschneiden sich irgendwie. Wobei der PWM Wert mit 100 erstmal
wesentlich kürzer ist, als der Int. Aber wahrscheinlich auch nur das
erste, und dann alle N-mal.
Dann muß ich vielleicht auf die 2.5ms Taktung verzichten, und den
Overflow Interrupt benutzen ? Der ist dann auf jeden Fall so lang wie
der PWM-Zyklus.. Probier ich mal aus.
Und wie komme ich dann auf eine 'gerade' Zeiteinheit? Den anderen
Timerinterrupt benutzen?
Oder wie meintest Du, daß ich mit einem Interrupt auskomme?
Danke schonmal, tolles Wetter!
Das klappt im Prinzip, aber ich habe ca. 1.8ms nach dem Impuls einen
Spike, vermutlich schaltet PWM ein, und wird danach vom overflow-int
wieder abgeschaltet. passt ja von der zeit. Irgendwelche Ideen?
Für den Tiny und PWM kann ich Dir keine Tipps geben - hab mich mit dem
Teil noch nie länger als 5 min. am Stück befaßt...
Zum Interrupt: Mehr als einen Sekundentakt brauchst Du zum blumengießen
nicht. Wenn Deine PWM 2.5 ms Zykluszeit hat, dann kannst Du diesen Takt
benutzen, um den Sekundentakt abzuleiten: Du zählst einfach alle 400
Interrupts den Sekundenzähler um 1 hoch; wenn der 86400 erreicht, setzt
Du ihn wieder auf 0.
Das ist ja klar, aber wenn Du mit Zykluszeit den Timeroverflow meinst -
mit der Prescaler komme ich auf 3.4ms. Auch wenn die Lösung mit ziemlich
exakt 2.5ms durch einen Interrupt einstellbar wäre, es klappt ja nicht.
Und wenn ich den Overflow-Int benutze, bekomme ich die o.g. Spikes.
So, dank hannes jetzt doch wieder Servo-PWM in Software mit dem Besten
aus beiden vorherigen Versuchen.
Ich hatte in dem Versuch
Beitrag "Re: C 'Zeitsteuerung', µS - 24h. Bitte mal ansehen"
praktische jeden Timer-Schritt zusätzlich in einer Variablen mitgezählt,
das ist natürlich total bescheuert...
Und dank Uhus Plan ist es sehr übersichtlich.
Das Signal steht also wieder, fehlt 'nur' noch die Steuerung.
Danke für die Hilfe!
Spikes:
Hast Du die High-Phase der PWM am Anfang, oder am Ende des PWM-Zyklus?
Wenn sie am Anfang liegt, ist wohl mit Spikes zu rechnen, weil die PWM
mit dem Overflow die Ausgabe wieder auf High schaltet und die ISR ein
wenig Zeit braucht, bis die PWM abgeschaltet ist. Was man dann sieht,
ist die Interrupt-Latenzzeit + die Zeit, die die ISR braucht, bis der
Befehl zum Sperren der PWM ausgeführt ist.
Ich hatte beim MSP430F2011 auch ein Störsignal - ich habe es
wegbekommen, indem ich einen PWM-Modus ausgewählt habe, der die
High-Phase nicht an den Anfang des Zyklus, sondern ans Ende legt. (Dort
funktioniert das so: Der Timer hat zwei Register, das erste wird auf die
Zykluszeit eingestellt - in Deinem Fall 2.5 ms - das andere bestimmt den
Zeitpunkt innerhalb des Zyklus, an den das Ausgabesignal umschaltet,
also <= 2.5 ms. Der Interrupt wird vom ersten Register erzeugt. Das
Timerregister wird beim Erreichen des Zyklusendes zurückgesetzt.)
Ich mußte dann zwar den Registerwert entsprechend umrechnen, aber das
Signal war sauber.
Zum Sekundentakt:
Für die PWM brauchst Du doch den Interrupt, um die Pause zu erzeugen -
sehe ich das richtig?
Wenn das so ist, dann nimm den 2.5 ms Takt. Es macht keinen Sinn, nur
wegen ein paar µs mehr den krummen Wert des Prescalers als Zeittakt zu
benutzen - mit der Hardware-PWM hat der Hobel eh Zeit genug...
Allerdings ist hier jeder 8. Aufruf != 2.5ms.
Also entweder ignorieren oder kompensieren, z.B. die Differenz zum
Maximalwert 188 beim nächsten Schritt dranhängen..
Hallo und danke für die Rückmeldung!
Meines Wissens brauche ich den Interrupt in jedem Fall, um die Pause zu
erreichen, weil es beim AVR - glaube ich - nicht zwei Register für die
PWM gibt, wie Du beschrieben hast. D.h. die Timer/Counter/PWM-Auflösung
sind 8 Bit mit dem vom Prescaler erzeugten Takt. Basta.
Da komme ich halt nicht auf einen praktischen Wert (für die Sekunde),
sondern muß die 2.5ms mit einem Interrupt erzeugen, im Code der Wert 188
(mal 13.33µs).
Einen anderen PWM Mode werde ich bei Gelegenheit mal ausprobieren, aber
für die ganzen Modi und Registerbeschreibungen habe ich vorerst keine
Geduld mehr, sondern möchte erst noch ein Stück weiterkommen. Der
Mehr-Code gegenüber PWM ist jetzt ja nicht mehr so schwerwiegend.
Ich probier' mich erstmal wieder an der eigentlichen Steuerung..
Ich würde (beim Tiny13 in ASM) keine Hardware-PWM für ein Servo
benutzen.
Den Portpin für den Impuls würde ich in der Timer-OVF-ISR setzen
(sbi) wenn:
- die Impulspause (68 ISRs) abgelaufen ist
- der Transistor für die Servo-Stromversorgung eingeschaltet ist
Den Portpin für den Impuls würde ich in jeder Timer OCR-ISR löschen,
wenn er bereits aus ist, dann bleibt er halt aus (cbi).
Die Aufgaben würde ich also so verteilen:
- OCR-ISR:
- Impuls ausschalten (egal ob er an war)
- OVF-ISR:
- Synchronisations-Flag für Mainloop setzen
- Impulspausenzähler runterzählen, bei 0:
- Impulspausenzähler auf Startwert setzen,
- Impuls-Pin setzen, falls Servo-Stromversorgung aktiv ist
- Mainloop:
- Synchronisations-Flag prüfen, falls nicht gesetzt, dann sleep
- Synchronisations-Flag löschen (Job wird ja gemacht)
- Uhr um 393µs hochzählen
- Servo-Timeout herunterzählen und ggf Servo-Strom ausschalten
- Uhrzeit mit Schaltzeiten vergleichen, bei Treffer:
- Servostellung in OCR eintragen,
- Servo-Timeout (1/2s?) setzen,
- Servo-Strom einschalten
- SLEEP im Mode IDLE aktivieren
- Main:
- Stackpointer initialisieren (in ASM)
- Ports initialisieren
- Variablen initialisieren
- Timer mit Vorteiler 64 einschalten,
- OVF-Int und OCR-Int freigeben
- Sleep im Mode Idle vorbereiten
- Interrupts freigeben
...
Hallo Hannes, danke für Deinen Vorschlag!
Momentan habe ich alles in einer OCR-ISR, und Hardware-PWM benutze ich
ja nicht.
Bis auf eine kleine Abweichung von der Zeit klappt es so sehr gut. Der
Fehler sollte aber leicht zu finden sein.
Aber ich werd' noch mal sehen, ob ich nicht auch was in die main
verlagern sollte.
Zu Hannes' Beitrag:
Beitrag "Re: PNP als Schalter: widersprüchlich. Aber konkret gefragt" ff.
Aber wie kommt man vom OVF alle 3.41248ms auf eine Sekunde?
Mit dem OCR alle 188 Schritte komme ich auf ziemlich genau 2.5ms..
Leider geht mir gerade irgendwie etwas Zeit verloren, wenn der Servo
sich bewegt - und das, obwohl ich die Abweichung durch den Servo-Impuls
kompensiere. Da wird er sich irgendwo verstecken...
Zur Kompensation des Taktes muß ein weiterer Zyklus durchlaufen werden,
sonst kommt's nicht hin. Damit bilden dann zwei Durchläufe einen
regulären - wenn davon eine Zeitbasis abgeleitet wird, aufpassen, und
den zusätzlichen Durchlauf nicht mitzählen..
Damit wächst der Code allerdings wieder.. und es könnte sein, daß die
Zeit nicht ganz reicht. Wenn das Servosignal erzeugt wird (für 2
Sekunden) ist das Timing etwas länger. Bezogen auf die Sekunde, mit der
probehalber PB0 getoggelt wird, einige ms.
1
ISR(TIM0_COMPA_vect)
2
{
3
4
if(servo.on)
5
{
6
if(servo.cycle==7)//
7
{
8
SERVOPORT|=(1<<SERVOPIN);// high
9
servo.cycle++;
10
OCR0A=TCNT0+pulse_width;// pulse-width
11
}
12
elseif(servo.cycle==8)
13
{
14
SERVOPORT&=~(1<<SERVOPIN);// low
15
servo.cycle=9;
16
OCR0A=TCNT0+CYCLE-pulse_width;// complete the 2506µs period
Koko Lores wrote:
> Zu Hannes' Beitrag:> Beitrag "Re: PNP als Schalter: widersprüchlich. Aber konkret gefragt" ff.>> Aber wie kommt man vom OVF alle 3.41248ms auf eine Sekunde?
Mit den 0,393ms (weiter oben) habe ich wohl Blödsinn in den
Taschenrechner eingetippt, sorry... Der OVF erfolgt natürlich alle
3,41248ms.
Ein nachgeschalteter Software-Zähler erreicht die volle Sekunde nach 293
Überlauf-Interrupts, braucht also zwei Bytes.
> Mit dem OCR alle 188 Schritte komme ich auf ziemlich genau 2.5ms..
Dann müsstest Du den Zählumfang des Timers begrenzen. Bei meinem
Vorschlag ging ich von einem freilaufenden Timer aus.
Aus dem Hut weiß ich jetzt nicht, ob der Tiny13 im CTC-Mode (also Timer
bei Compare löschen) noch beide Compare-Interrupts unterstützt. Wenn ja,
dann kann man statt des OVF den einen Compare bei 2,5ms mit CTC
zuschlagen lassen, den anderen Compare bei 1,0..2,0ms zum Beenden des
Impulses. Auch hier brauchst Du einen nachgeschalteten Zähler in
Software, der alle 400 Schritte eine Sekunde erreicht (also auch zwei
Bytes). Ob das wirklich genauer ist, ist fraglich, denn der interne
RC-Oszillator ist weder genau noch temperaturstabil, für eine ernsthafte
"Uhr" also von vornherein ungeeignet.
> Leider geht mir gerade irgendwie etwas Zeit verloren, wenn der Servo> sich bewegtDas kann nach meinem Konzept (extrem kurze ISRs, Synchronisierung der
Jobs in der Mainloop mittels Jobflags, Sleep-Mode Idle, wenn alle Jobs der
Mainloop fertig sind) eigentlich nicht passieren. Denn der Zeitzählung ist es
egal, ob ein Impuls abgesetzt wurde oder nicht. Und da ein Impuls immer kürzer ist
als eine Timer-Runde (Überlauf), gibt es keinerlei Kollisionen.
> - und das, obwohl ich die Abweichung durch den Servo-Impuls> kompensiere. Da wird er sich irgendwo verstecken...
Da gibt es eigentlich nichts zu kompensieren, es sei denn, Du arbeitest
irgendwo in einer ISR mit Warteschleifen und verpennst dadurch
Interrupts.
...
Hannes hat sein Ding in Assembler geschrieben - damit sind die Chancen
deutlich besser, die Software-PWM hinzubekommen, als mit C.
Zur Zeitkorrektur: Die könntest Du eigentlich auch im Hauptprogramm
machen, wenn Du weißt, wie groß der Fehler je Sekunde ist.
Dann wäre die ISR etwas entlastet.
Uhu Uhuhu wrote:
> Hannes hat sein Ding in Assembler geschrieben
Ja, richtig, ich denke bei Programmentwürfen auch in ASM, der AVR kann
auch nur Maschinencode, also ASM...
> - damit sind die Chancen> deutlich besser, die Software-PWM hinzubekommen, als mit C.
Wer C wirklich virtuos versteht kann das AUCH IN C. Allersdings kann der
auch soviel ASM, dass er das Ergebnis seiner Arbeit nachvollziehen und
ggf. optimieren kann.
>> Zur Zeitkorrektur: Die könntest Du eigentlich auch im Hauptprogramm> machen, wenn Du weißt, wie groß der Fehler je Sekunde ist.>> Dann wäre die ISR etwas entlastet.
Die ISR gehört unabhängig von der Sprache so kurz wie möglich. Wobei ich
auch schon Programme schrieb, bei denen die gesamte Arbeit in der
Timer-ISR erledigt wurde. Allerdings kann man in ASM schnell mal
durchrechnen (durchzählen) wieviel Takte der Code benötigt.
...
Danke wiederum für Eure Antworten, das macht ja geradezu Spaß, so viele
Möglichkeiten der Umsetzung kennenzulernen.
Zur Korrektur - Da ich mit einem COMP Interrupt sowohl Signallänge als
auch Pausenlänge erzeuge, habe ich für die 'Zeitbasis' eine abweichende
Taktzeit, die muß man korrigieren.
Aber Du hast natürlich recht - den nachgeschalteten Zähler brauche ich
in jedem Fall, und da auch mit dem OVF recht genau 1s abzuleiten ist,
werde ich das als nächstes versuchen.
Ich weiß nicht mehr, warum ich mich dagegen entschieden hatte.
Dann ist alles synchron, es muß nichts ausgeglichen werden, und der Code
wird wieder kleiner. Klingt gut!
Koko Lores wrote:
> Danke wiederum für Eure Antworten, das macht ja geradezu Spaß, so viele> Möglichkeiten der Umsetzung kennenzulernen.>> Zur Korrektur - Da ich mit einem COMP Interrupt sowohl Signallänge als> auch Pausenlänge erzeuge, habe ich für die 'Zeitbasis' eine abweichende> Taktzeit, die muß man korrigieren.
Nunja, das ist Geiz am falschen Ende. Du hast zwar nur einen Timer, der
kann aber mehrere verschiedene Interrupts erzeugen. Es ist also
sinnfrei, alles in einem Interrupt machen zu wollen.
Ein zweiter Compare-Interrupt bringt vermutlich nicht mehr Genauigkeit
als der OVF-Interrupt, denn auch der Timer-Zählumfang mit 188 ergibt
nicht exakt 2,5ms, da bräuchte man den Zählumfang von 187,5. Da
verursacht der Nachteiler von 293 statt 292,96875 bei Timer-Zählumfang
von 256 schon weniger Abweichung.
>> Aber Du hast natürlich recht - den nachgeschalteten Zähler brauche ich> in jedem Fall, und da auch mit dem OVF recht genau 1s abzuleiten ist,> werde ich das als nächstes versuchen.> Ich weiß nicht mehr, warum ich mich dagegen entschieden hatte.
Ich vermute, Du drückst Dich (wie viele C-Anfänger) vor Interrupts bzw.
dem extrem hardwarenahen Programmieren.
>> Dann ist alles synchron, es muß nichts ausgeglichen werden, und der Code> wird wieder kleiner. Klingt gut!
Klingt nicht nur gut, ist auch gut.
Der Überlauf-Int "taktet" die Mainloop über ein Jobflag,
der Compare-Int schaltet nur den Impuls aus (egal ob er diesmal an war),
den Rest erledigt die Mainloop:
- Zeit hochzählen und Schaltuhrfunktion ausüben
- Servostrom einschalten, wenn Schaltzeit erreicht ist
- Servoposition einstellen
- Servo-Timeout verwalten (Strom wieder aus)
- Impulspause verwalten
- Impuls einschalten wenn Servospannung anliegt und Impulspause abläuft
Ich räume ein, dass dieser hardwarenahe Programmierstil vermutlich in
ASM überschaubarer realisiert werden kann als in C.
Im Prinzip fragt ja jeder Teil des Programms ja nur die Zustände einiger
Variablen (Flag, Portzustand, Zählvariablen) ab und reagiert darauf
sowie verändert die entsprechenden Variablen (hochzählen, runterzählen,
auf Startwert setzen).
...