Forum: Mikrocontroller und Digitale Elektronik Probleme mit Servo- Ansteuerung


von Uwe (Gast)


Lesenswert?

Hallo,

auch wenn es schon zig Beiträge über Servos gibt, hab ich nichts zu 
meinem Problem gefunden.

Ich benutze einen AtMega8 mit internem 8MHz Takt. Ich habe das Programm 
aus dem Wikibeitrag "Modellbauservo Ansteuerung" übernommen und leicht 
angepasst zwecks der anderen Timings aufgrund des schnelleren 
Prozessortaktes und des OCR1- Pins.

Das Servo soll zwischen "Neutral", "Linker Anschlag" und "Rechter 
Anschlag" sich hin und her bewegen. Das klappt auch ein paar Durchgänge 
(ca. 5), danach steht dass Servo jedoch für ca. 7s und läuft dann erst 
weiter.

Anbei mein Code:
1
#define F_CPU 8000000UL
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
ISR( TIMER1_COMPA_vect )                // Interruptbehandlungsroutine
8
{
9
  OCR1A = 625-OCR1A;      // Das Servosignal wird aus der Differenz von
10
  // Periodenlänge (625*0,032ms=20ms) und letztem
11
  // Vergleichswert (OCR1A) gebildet
12
}
13
14
15
int main (void)
16
{
17
  DDRB = 0xFF;
18
  PORTB = (1<<PB1);          // PB1 als Output
19
  
20
  TCCR1A = (1<<COM1A0);                 // Togglen bei Compare Match
21
  TCCR1B = (1<<WGM12) |  (1<<CS12);      // CTC-Mode; Prescaler 256
22
  TIMSK  = (1<<OCIE1A);                 // Timer-Compare Interrupt an
23
  
24
  OCR1A = 578;                         // Neutralposition ((625-578)*0.032ms)=1,5ms)
25
  
26
  sei();                                // Interrupts global an
27
  
28
  while( 1 ) 
29
  {
30
    sei();
31
    OCR1A = 578;
32
    _delay_ms(1000);
33
    OCR1A = 594;
34
    _delay_ms(1000);
35
    OCR1A = 578;
36
    _delay_ms(1000);
37
    OCR1A = 563;
38
    _delay_ms(1000);
39
  }  
40
  
41
  return 0;
42
}

Warum ist das so? Wo ist mein Fehler?

Es wäre super wenn jemand ne Idee hätte.

Danke und Gruß Uwe

von stefanus (Gast)


Lesenswert?

Der Timer läuft maximal in diesem Intervall über:

1 / (8 Mhz  256  256) = 8,2ms

Das Servo braucht aber einen Zyklius von ca 20mS. Also läuft der Timer 
viel zu schnell.

Weiterhin verstehe ich den Interrupthandler nicht. Aber das ist erstmal 
wurscht, denn du versucht da einen Wert >255 in ein 8 Bit Register zu 
laden. Das kann nicht gehen.

von spess53 (Gast)


Lesenswert?

Hi

>Aber das ist erstmal
>wurscht, denn du versucht da einen Wert >255 in ein 8 Bit Register zu
>laden. Das kann nicht gehen.

Timer1 ist ein 16Bit-Timer.

MfG Spess

von MWS (Gast)


Lesenswert?

Zitat:
It is important to notice that accessing 16-bit registers are atomic 
operations. If an interrupt occurs between the two instructions 
accessing the 16-bit register, and the interrupt code updates the 
temporary register by accessing the same or any other of the 16-bit 
Timer Registers, then the result of the access outside the interrupt 
will be corrupted.
Therefore, when both the main code and the interrupt code update the 
temporary register, the main code must disable the interrupts during the 
16-bit access.

von stefanus (Gast)


Lesenswert?

Ach so, ok. Also sollten wir doch mal einen Blick auf die 
Interruptroutine werfen.

Ein Takt dauert 256*(1/8000000)=0,032ms, passt zu den obigen 
Kommentaren.

1)
Der erste Output Compare tritt nach 578 Takten auf. Dann geht der 
Ausgang auf High, nehme ich mal an. 578*0,032ms = 18,496ms

2)
Der Interrupthandler stellt das register auf 625-578 (also 47) um. Dann 
geht der Ausgang wieder auf Low. Mal nachrechnen, wie lange das dauert:

Um vom momentanen Zählwert 578 zur 47 zu kommen, muss er einmal 
überlaufen.

65536-578+47 = 65005 Takte.
65005 * 0,032ms = 2080,16 ms

Also zwei Sekunden !?! Mach da mal eine LED dran, das muss ja leicht 
sichtbar sein.

Spielen wir das mal weiter durch.

3)
Der Interrupthandler setzte das Register nun auf 625-47 (also 578) um. 
Dann geht der Ausgang wieder auf High (start des zweiten Impulses). Das 
dauert (578-47) * 0,032ms = 16,99ms

Danach wiederholt sich der Vorgang bei 2).

Dass heisst, der Ausgang ist immer für 2080,16ms High und 16,99ms Low - 
wenn ich mich nicht vertan habe. So kann es nicht funktionieren.

Gib uns mal einen Link auf den Artikel, den du meinst. Ich habe ihn 
nämlich nicht gefunden. Ich vermute mal, dass sie dort den Timer so 
getaktet hatten, dass er alle 20ms überläuft, was bei Dir jedoch ca 100 
mal länger dauert.

von stefanus (Gast)


Lesenswert?

@MWS: Darum kümmert sich der C Compiler bereits.

von MWS (Gast)


Lesenswert?

stefanus schrieb:
> 2)
> Der Interrupthandler stellt das register auf 625-578 (also 47) um. Dann
> geht der Ausgang wieder auf Low. Mal nachrechnen, wie lange das dauert:
>
> Um vom momentanen Zählwert 578 zur 47 zu kommen, muss er einmal
> überlaufen.
>
> 65536-578+47 = 65005 Takte.
> 65005 * 0,032ms = 2080,16 ms

Nö, der Mode ist CTC, der läuft nicht über, sondern TCNT1 wird auf 0 
gesetzt und zählt von dort auf 47.

stefanus schrieb:
> @MWS: Darum kümmert sich der C Compiler bereits.

Würde mich wundern, das erzeugt Overhead, wenn das TEMP-Register nicht 
im Interrupt genutzt wird.

von stefanus (Gast)


Lesenswert?

> Nö, der Mode ist CTC, der läuft nicht über,
> sondern TCNT1 wird auf 0 gesetzt und zählt von dort auf 47.

Dann müsste es ja funktionieren. Es sei denn, die Hauptschleife 
konkurriert tatsächlich mit den Interrupts. Bei den langen delays würde 
ich jedoch davon ausgehen, dass das nur sehr selten passiert. Was 
wiederum nicht zur Problembeschreibung passt.

Wie dem auch sei, probier mal den Ratschlag von MWS, Interupts in der 
Hauptschleife temporär zu sperren, wenn Du das Compare register änderst.

Die Fuses (vor allem CLKDIV8) hast Du geprüft?

von MWS (Gast)


Lesenswert?

stefanus schrieb:
> Bei den langen delays würde
> ich jedoch davon ausgehen, dass das nur sehr selten passiert.

Das dachte ich mir auch, jedoch sollte man immer die offensichtlichen 
Fehler zuerst beheben.

Allerdings, wenn der Zähler sich im Bereich des Servopulses bis z.B. 47 
befindet und genau dann einen neuen Wert in der Main zugewiesen bekommt, 
so zählt er weiter bis z.B. 563. Jetzt ist der Servokontrollpuls zu lang 
und schlechter noch: er toggelt von nun an falsch, da eine Flanke 
ausgelassen wurde. Das geht dann solange, bis sich der Fehler erneut 
ereignet und die Flanken wieder richtig kommen.
In dieser Form taugt der Code nichts.

von stefanus (Gast)


Lesenswert?

> schlechter noch: er toggelt von nun an falsch

Da hast Du einen gravierenden Fehler erkannt. Das könnte die Erklärung 
des Rätsels sein - sehr warscheinlich, denke ich mal.

@Uwe: Schau Dir mal meinen Code an 
(http://stefanfrings.de/servocontroller/index.html).

Ich lasse dort einen 8bit Timer im Fast-PWM Modus laufen, nutze jedoch 
nicht die physikalischen PWM Ausgänge, sondern setze meine Ausgänge in 
Interrupt-Handlern.

Der Timer läuft regelmäßig alle 4,1ms über. Der Interrupt Handler 
bedient dabei immer einen von 5 Ausgängen wechselweise. So ergibt sich 
ein Intervall von ungefähr 20ms pro Ausgang und ich habe 5 Ausgänge mit 
nur einem Timer abgedeckt.

Durch Nutzung des zweiten Compare Registers, komme ich auf weitere 5 
Ausgänge.

Im Hauptprogramm musst du nur die gewünschten Positionswerte in das 
Array schreiben. Die Interrupt Routine überträgt sie zyklisch in das 
Comapre-Register.

von Uwe (Gast)


Lesenswert?

Hallo,

erstmal vielen Dank für alle Kommentare. Ich habe gelernt, der Code ist 
Schrott, ich soll einen anderen machen....... ;-)

@Stefanus: Ich habe mir deinen Code angeschaut. Ich habe ihn an meine 
Verhältnisse angepasst: da ich nen AtMega 8 verwende habe ich den 8bit- 
Timer2 verwendet. Dann habe ich noch den Teil für Servo 6-9 gelöscht und 
alles was die I2C- Übertragung betrifft. Außerdem habe ich den PORTD für 
alle Servos verwendet.
Leider tut sich gar nichts. Ich habe als Wert die Neutralstellung 
gewählt, aber es kommt anscheinend nichts an. Frequenz habe ich auf 4 
MHz eingestellt.......
1
#include <stdio.h>
2
#include <stdint.h>
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
6
7
// Generates PWM signals for up to 5 servos.
8
9
//using internal 4Mhz RC oscillator
10
11
// 2  PD0 servo 0
12
// 3  PD1 servo 1
13
// 4  PD2 servo 2
14
// 5  PD3 servo 3
15
// 6  PD4 servo 4
16
17
// PWM Values for the servos
18
volatile uint8_t servo[10];
19
20
// Timer 0 Compare A Interrupt
21
ISR(TIMER2_COMP_vect) {
22
  // Sets servo 0-4 pins to low
23
  PORTD &= ~1;
24
  PORTD &= ~2;
25
  PORTD &= ~4;
26
  PORTD &= ~8;
27
  PORTD &= ~16;
28
}
29
30
// Timer 2 Overflow Interrupt
31
ISR(TIMER2_OVF_vect) {
32
  static uint8_t loop;
33
34
  // Set servo pins to high, round-robin, two pins per interrupt
35
  if (servo[loop] != 0) {
36
    switch (loop) {
37
      case 0: PORTD |= 1; break;
38
      case 1: PORTD |= 2; break;
39
      case 2: PORTD |= 4; break;
40
      case 3: PORTD |= 8; break;
41
      case 4: PORTD |= 16; break;
42
    }
43
  }
44
  
45
  // Set timer compare values for the next (not the current) loop
46
  if (++loop>4) {
47
    loop=0;
48
  }
49
  OCR2=servo[loop];
50
  }
51
52
53
int main() {
54
  // Configure pin direction and pull-ups
55
  DDRD  = 1|2|4|8|16;
56
57
  
58
  // Timer 2: fast PWM, prescaler 64, IRQ on overflow, compare match A and compare match B.
59
  TCCR2 = (1<<WGM20)|(1<<WGM21)| (1<<CS11);
60
  TIMSK  = (1<<TOIE2) | (1<<OCIE2);
61
62
  // Enable interrupts
63
  sei();
64
65
  
66
  while (1) {
67
    
68
    servo[1]=94;
69
70
  }
71
}

Danke mal wieder für die Hilfe im vorraus.

Gruß Uwe

von MWS (Gast)


Lesenswert?

CS11 ist ein Bitname von Timer1, das läuft damit schneller als Prescaler 
64 und servo[1] ist der zweite Servoausgang.
Und warum Prescale 64, sollte das nicht 128 sein?

von stefanus (Gast)


Lesenswert?

Prescaler 64 ist schon richtig. 1/(4000000 Mhz/64/256) = 4ms. Prescaler 
128 würde bei 8 Mhz Taktfrequenz richtig sein.

Es muss aber (1<<CS22) heissen, nicht (1<<CS11).

Nun ein bisschen Kritik an meinem eigenen Code :-)

Das kann man abkürzen:
1
  PORTD &= ~1;
2
  PORTD &= ~2;
3
  PORTD &= ~4;
4
  PORTD &= ~8;
5
  PORTD &= ~16;
1
  PORTD &= ~ (1|2|4|8|16)

Und das auch:
1
switch (loop) {
2
      case 0: PORTD |= 1; break;
3
      case 1: PORTD |= 2; break;
4
      case 2: PORTD |= 4; break;
5
      case 3: PORTD |= 8; break;
6
      case 4: PORTD |= 16; break;
7
    }
1
  PORTD |= (1<<loop);

Aber für die Funktion spielt es erstmal keine Rolle.

von stefanus (Gast)


Lesenswert?

>> servo[1]=94;

Damei steuerst Du den Zweiten Ausgang (Pin PD1) an, das war aber klar, 
oder?

von MWS (Gast)


Lesenswert?

stefanus schrieb:
> Prescaler 64 ist schon richtig. 1/(4000000 Mhz/64/256) = 4ms. Prescaler
> 128 würde bei 8 Mhz Taktfrequenz richtig sein.

Er nutzt 8 MHz Clock:

Uwe schrieb:
> #define F_CPU 8000000UL

> Damei steuerst Du den Zweiten Ausgang (Pin PD1) an

So schrub ich bereits:

MWS schrieb:
> servo[1] ist der zweite Servoausgang.

von MWS (Gast)


Lesenswert?

MWS schrieb:
> Er nutzt 8 MHz Clock:

Hmm, im Kommentar des zuletzt gezeigten Codes steht dagegen 4MHz, 
entweder ein übriggebliebenes Textfragment oder den Tatsachen 
entsprechend.
Maßgebend ist die Fuse, also hier noch der Hinweis:

Stimmt der Kommentar mit der Fuseeinstellung überein?

von Uwe (Gast)


Lesenswert?

Hallo,

endlich konnte ich mich wieder dem Projekt widmen.

Das mit dem Port war mir klar, aber wie ihr richtig geschrieben habt lag 
es an dem falschen CS11. Nachdem ich diesen Fehler korrigiert habe, 
funktioniert es einwandfrei.

Vielen Dank für die Unterstützung und den Code.

Gruß Uwe

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.