Forum: Mikrocontroller und Digitale Elektronik C 'Zeitsteuerung', µS - 24h. Bitte mal ansehen


von Koko Lores (Gast)


Lesenswert?

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:
1
// Include files
2
  #include <avr/io.h>       // Device-specific IO definitions
3
  #include <stdint.h>        // Standard Integer Types
4
  #include <stdlib.h>        // General utilities
5
  #include <avr/sleep.h>      // Power Management and Sleep Modes
6
  #include <avr/interrupt.h>    // Interrupts
7
8
9
// CPU Frequency
10
  // Internal Oscillators
11
  //  128KHz
12
  //  4.8MHz
13
  //  9.6MHz
14
  //Can be divided by 8 with CKSEL fuse or 1,2,4..256 System Clock Prescaler Register
15
  
16
  // F_CPU defined in Makefile
17
18
// System Clock Prescaler
19
  //   CLKPS3 CLKPS2 CLKPS1 CLKPS0 Clock Division Factor
20
  // T0
21
    #define SCP_2                        1 << CLKPS0
22
    #define SCP_4                 1 << CLKPS1
23
    #define SCP_8                 1 << CLKPS1 | 1 << CLKPS0 
24
    #define SCP_16          1 << CLKPS2
25
    #define SCP_32          1 << CLKPS2 |         1 << CLKPS0
26
    #define SCP_64          1 << CLKPS2 | 1 << CLKPS1
27
    #define SCP_128          1 << CLKPS2 | 1 << CLKPS1 | 1 << CLKPS0
28
    #define SCP_256  1 << CLKPS3  
29
30
31
  
32
// Timer/Counter Clock Select from Prescaler
33
  // T0
34
    #define T0_CLK_1              1 << CS00
35
    #define T0_CLK_8        1 << CS01
36
    #define T0_CLK_64        1 << CS01 | 1 << CS00
37
    #define T0_CLK_256  1 << CS02
38
  
39
  // Results: 
40
  // 1: 4,800,000 Hz - 0.2 µs
41
  // 8: 600,000 Hz - 1.6 µs  
42
  // 64: 75,000 Hz - 13.3 µs  
43
  // 256: 18,750 Hz - 53.3 µs
44
45
// Hardware defines
46
  // Servo
47
  #define SERVO PB4
48
  #define SERVO_POS_A 60 //closed
49
  #define SERVO_POS_B 2 //open
50
51
52
// GLOBAL VARIABLES
53
54
uint8_t volatile servo = SERVO_POS_B;
55
uint8_t counts = 0;
56
uint8_t signals=0;
57
58
uint8_t clock=0;
59
60
61
// FUNCTIONS & PROCEDURES
62
63
void slow(void)
64
{
65
  // Prescales the System Clock
66
  CLKPR = 1 << CLKPCE;
67
  CLKPR = SCP_256;
68
  
69
  // set Timer/Counter
70
  TCCR0B = T0_CLK_256;
71
  // Interrupt
72
  TIMSK0 |= 1 << TOIE0;
73
74
}
75
76
77
void fast(void)
78
{
79
  // Prescales the System Clock
80
  CLKPR = 1 << CLKPCE;
81
  CLKPR = 0;
82
  
83
  // set Timer/Counter
84
  TCCR0B = T0_CLK_256;
85
  // Interrupt
86
  TIMSK0 |= 1 << OCIE0A;
87
}
88
89
90
91
// INTERRUPT SERVICE ROUTINES
92
93
ISR(TIM0_COMPA_vect)
94
{ 
95
96
  OCR0A=TCNT0+4;
97
  
98
  if (TCCR0B == T0_CLK_256)
99
  {
100
    PORTB |= ( 1<<SERVO );
101
    TCCR0B  = T0_CLK_64;
102
    OCR0A=TCNT0+45;      // about 60µs
103
  }
104
  else
105
  {
106
    TCCR0B  = T0_CLK_8;
107
    counts++;
108
    
109
    if (servo == counts)
110
    {
111
        PORTB &= ~( 1<<SERVO );
112
      
113
      counts = 0;
114
      
115
      // next time in 13ms
116
      TCCR0B = T0_CLK_256;
117
      OCR0A = TCNT0+255;
118
      
119
      
120
      
121
      if(signals == 76) // about 1s
122
      {
123
        signals=0;
124
        slow();
125
        if(servo == SERVO_POS_B)
126
        {
127
          servo = SERVO_POS_A;
128
        }
129
        else
130
        {
131
          servo = SERVO_POS_B;
132
        }
133
        
134
      }
135
      else
136
      {
137
        signals++;
138
      }
139
    }
140
  }
141
}
142
143
144
ISR(TIM0_OVF_vect)
145
{ 
146
  clock++;
147
  if(clock == 4) // 14s
148
  {
149
    clock=0;
150
    fast();
151
  }
152
}
153
154
155
156
157
158
159
int main( void )
160
{
161
162
// Variables
163
164
165
166
167
168
// PORT SETUP
169
  // writing to PORTx before setting the DDRx is important to guarantee intended power-up pin states
170
  // inputs are active-low (pulled to GND when switches are closed)
171
  // enable internal pull-up resistors for inputs, outputs 'low'
172
  PORTB = 0;
173
  
174
  // define outputs in data direction register
175
  DDRB = 1<<SERVO;
176
177
// TIMER  
178
  fast();
179
180
// SLEEP MODE
181
  set_sleep_mode(SLEEP_MODE_IDLE);
182
  
183
// INTERRUPTS
184
  sei();
185
  
186
  
187
// // // // // // // // // // // // // //
188
  
189
  
190
  
191
  for (;;)  // ever
192
  {
193
    sleep_mode();
194
  }
195
}

von Koko Lores (Gast)


Lesenswert?

Mist, das sollte doch in gcc!?

von Koko Lores (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

> 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?

von Koko Lores (Gast)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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!

von Karl H. (kbuchegg)


Lesenswert?

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.


von Koko Lores (Gast)


Lesenswert?

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
volatile struct {
15
   unsigned servo:1;
16
   unsigned pulse:1;
17
   unsigned move:1;
18
} flag;
19
20
volatile uint8_t s_pulse_len = SERVO_POS_B;
21
volatile uint8_t s_pulse_cur = 0;
22
23
volatile uint16_t now_ms;
24
volatile uint16_t ms_0B;
25
volatile uint16_t now_ms_0B;
26
27
volatile uint16_t millisecond = 0;  // 1000
28
volatile uint8_t second = 0;    //   60
29
volatile uint8_t minute = 0;    //   60
30
volatile uint8_t hour = 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
    else if (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
  else if (ms_0B == now_ms_0B)
123
  {
124
    flag.servo = 0;
125
  }
126
  
127
  
128
  
129
} // ISR
130
131
132
133
// MAIN
134
135
int main( 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!

von Koko Lores (Gast)


Lesenswert?

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?

von Uhu U. (uhu)


Lesenswert?

> 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.

von Koko Lores (Gast)


Lesenswert?

>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.

von Uhu U. (uhu)


Lesenswert?

> = 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.

von Koko Lores (Gast)


Lesenswert?

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!

von Uhu U. (uhu)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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..

von Uhu U. (uhu)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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.

von Uhu U. (uhu)


Lesenswert?

> 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.

von Koko Lores (Gast)


Lesenswert?

Danke, wenn ich es anders nicht hinbekomme versuch ich's mal so! Aber 
erstmal gucken, was schiefläuft...

von Uhu U. (uhu)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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..

von Uhu U. (uhu)


Lesenswert?

Mit welchem Takt läuft denn der µC? Schafft er die 1. ISR überhaupt in 
13,3 µs?

von Power (Gast)


Lesenswert?

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. ;-)

von Uhu U. (uhu)


Lesenswert?

Das kann bei Kokos derzeitigem Programm nicht passieren.

Jedenfalls ist das ein Grund, warum mir die MSP430F20xx deutlich besser 
gefallen...

von Koko Lores (Gast)


Lesenswert?

Er läuft mit 4.8MHz - ob er die ISR schafft, weiß ich nicht, vielleicht 
ja nicht, wenn's nicht so läuft, wie's soll..

von Uhu U. (uhu)


Lesenswert?

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?

von Power (Gast)


Lesenswert?

>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.

von Koko Lores (Gast)


Lesenswert?

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
19
    OCR0B=100;
20
    
21
    servo.cycle++;
22
  }
23
  else if (servo.cycle == 8)
24
  {
25
    // disable pwm
26
    TCCR0A = 0;
27
  
28
    servo.cycle=0;
29
  }
30
  else
31
  {
32
    servo.cycle++;
33
  }
34
35
} // ISR

  

von Koko Lores (Gast)


Lesenswert?

Ä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?

von Power (Gast)


Lesenswert?

Probiers mal mit dem CTC-Mode (Mode 2), WGM01 im TCCR0A gesetzt. Und den 
Output-Compare0A-INT benutzen (ist das der 'ISR(TIM0_COMPA_vect)'?).

von Koko Lores (Gast)


Lesenswert?

Nee, im CTC wird doch immer der Timer auf 0 gesetzt - dann kann ich den 
Output-Compare0A-INT doch nicht mehr benutzen! Oder versteh ich das 
falsch?

von Koko Lores (Gast)


Lesenswert?

Also der B-Match ist jetzt auf PWM eingestellt, mit A als Interrupt 
steuert er den Ablauf..

von Power (Gast)


Lesenswert?

Ups, stimmt! War noch beim Timer (Uhr). Der Mode 1 (WGM00) dürfte eher 
passen!

von Uhu U. (uhu)


Lesenswert?

> 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?

von Koko Lores (Gast)


Lesenswert?

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..

von Koko Lores (Gast)


Lesenswert?

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!

von Koko Lores (Gast)


Lesenswert?

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?

von Uhu U. (uhu)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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!

1
ISR(TIM0_COMPA_vect)                                 // 4.8MHz / 64
2
{ 
3
4
  if(servo.cycle == 7)
5
  {
6
    SERVOPORT |= ( 1<<SERVOPIN );        // high
7
    servo.cycle++;
8
    OCR0A = TCNT0 + s_pulse_len;         // pulse-width
9
  }
10
  else if (servo.cycle == 8)
11
  {
12
    SERVOPORT &= ~( 1<<SERVOPIN );       // low
13
    servo.cycle = 0;
14
    OCR0A = TCNT0 + 188;                 // 2506µs
15
  }
16
  else
17
  {
18
    OCR0A = TCNT0 + 188;                 // 2506µs
19
    servo.cycle++;
20
  }
21
22
} // ISR

von Uhu U. (uhu)


Lesenswert?

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...

von Koko Lores (Gast)


Lesenswert?

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..

von Koko Lores (Gast)


Lesenswert?

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..

von Hannes L. (hannes)


Lesenswert?

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

...

von Koko Lores (Gast)


Lesenswert?

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.

von Koko Lores (Gast)


Lesenswert?

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...

von Koko Lores (Gast)


Lesenswert?

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
    else if (servo.cycle == 8)
13
    {
14
      SERVOPORT &= ~( 1<<SERVOPIN );     // low
15
      servo.cycle = 9;
16
      OCR0A = TCNT0 + CYCLE - pulse_width;  // complete the 2506µs period
17
      tick++;
18
    }
19
    else if (servo.cycle == 9)
20
    {
21
      OCR0A = TCNT0 + CYCLE;  // complete the 2506µs period
22
      tick++;
23
      servo.cycle = 0;
24
    }
25
    else
26
    {
27
      servo.cycle++;
28
      OCR0A = TCNT0 + CYCLE;          // 2.5ms
29
      tick++;
30
    }
31
  }
32
  else
33
  {
34
    OCR0A = TCNT0 + CYCLE;          // 2.5ms
35
    tick++;
36
  }
37
  
38
  if(tick == 400)            // 1s
39
  {
40
    PORTB ^= (1<<PB0);
41
    tick = 0;
42
    second++;
43
    
44
    if(second == 1)
45
    {
46
      pulse_width = SERVO_POS_B;
47
      SERVO_ON()
48
      servo.cycle = 0;
49
      servo.on=1;
50
    }
51
    else if(second == 3)
52
    {
53
      servo.on=0;
54
      SERVO_OFF()
55
    }
56
    else if(second == 10)
57
    {
58
      pulse_width = SERVO_POS_A;
59
      SERVO_ON()
60
      servo.cycle = 0;
61
      servo.on=1;
62
    }
63
    else if(second == 12)
64
    {
65
      servo.on=0;
66
      SERVO_OFF()
67
    }
68
    else if(second == 20)
69
    {
70
      second = 0;
71
    }
72
  }
73
74
} // ISR

von Hannes L. (hannes)


Lesenswert?

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.

...

von Uhu U. (uhu)


Lesenswert?

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.

von Hannes L. (hannes)


Lesenswert?

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.

...

von Koko Lores (Gast)


Lesenswert?

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!

von Hannes L. (hannes)


Lesenswert?

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).

...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.