Forum: Mikrocontroller und Digitale Elektronik Timer0 Attiny85


von Mark (Gast)


Lesenswert?

Hallo,
ich habe eine Frage zum Timer0 beim Attiny85.
Ich möchte um eine Periode von 20ms erzeugen und eine Pulsweite von 1ms 
bis 2ms. Damit möchte ich meinen Servomotor für meine Eisenbahn 
ansteuern.
Habe ich mir die Tutorials angeguckt. Vor allem die mit dem Timern
http://www.mikrocontroller.net/articles/AVR-Tutorial:_Timer

Aber ich bin mir nicht ganz sicher, ob ich es richtig verstanden habe 
und ob ich das so machen kann, wie ich es mir überlegt habe.

Folgendes würde ich in meinem Code machen

CPU-Frequenz = 4MHz
TCCR = CS01 -> Vorteiler = 8
TIMSK = TOIE0  -> Interrupt beim Overflow auslösen
sei() -> Interrupts freigeben

Damit würde ich eine Periode von
t=(2^8*8)/4000000 = 0.000512s (~0.5ms) bekommen


Bei einem Interrupt würde ich folgendes machen
1
ISR(TIM0_OVF_vect) {
2
  zähler_Periode = zähler_Periode + 1;
3
  if (zähler_Periode >= 40 ) {  // 20ms sind erreicht (40*0.5ms=20ms)
4
    Zähler_Pulsweite = Zähler_Pulsweite + 1;
5
    if(Zähler_Pulsweite <= 3){ //Pulsweite ist 1,5ms
6
      PB1 HIGH
7
    }
8
    else if(Zähler_Pulsweite < 3){
9
      PB1 LOW
10
      Zähler_Pulsweite = 0;
11
      Zähler_Periode = 3; // eigentlich wieder auf 0 setzen, da ich aber schon wieder 3 Schritte (Zähler_Pulsweite) weitergezählt habe, springe ich auf 3.
12
    }
13
  }
14
}
In diesem Beispiel würde der Servo jetzt eine Position halten.
Meine Frage wäre, ob ich das so richtig verstanden habe und es so 
umsetzen könnte? Mir geht es jetzt nicht um die richtige Schreibweise, 
sondern mehr um die Logik (die ich versucht habe so gut wie möglich zu 
beschreiben).

Bis dahin,
guten Abend und gute Nacht
Mark

: Bearbeitet durch User
von Thomas E. (thomase)


Lesenswert?

Ein bisschen eleganter geht das schon:

Prescaler = 1024
20ms = 1/50Hz
4Mhz/1024/50Hz = 78

Timer0, Mode7, Fast PWM Top = OCR0A.

OCR0A = 78 - 1;
Damit zählt der Timer 78 Takte = 20ms und geht wieder auf 0.

78 / 20ms * 1,5ms = 5,85 gerundet 6

OCR0B = 6;
Das ist dein Puls von ca. 1,5ms.
Bei TCNT0 = 0 geht PB1 auf H, bei 6 auf L.

1
TCCR0A |= (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
2
TCCR0B |= (1 << WGM02) | (1 << CS02) | (1 << CS00);
3
OCR0A = 77;
4
OCR0B = 6;
5
DDRB |= (1 << 1);

Damit hast du deinen Impuls von ~1,5ms mit 20ms Periode an PB1.
Macht der Timer alles ganz alleine in Hardware.
Zum Abschalten einfach den Pin auf Eingang schalten: DDRB &= ~(1 << 1);
Den OVF-Interrupt kannst du noch als 50Hz-Ticker verwenden, falls du das 
brauchst.

mfg.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Thomas hat ja schon geschrieben, wie man das ganze dem Timer ganz 
alleine aufhalsen könnte


Aber um auf das Ursprungs-Programm zurückzukommen.
Deine ISR ist falsch. Du hast dich da mit zu vielen Variablen selbst ins 
Knie geschossen.

Worum gehts denn.
Du brauchst einen Zeitraum von 20 Millisekunden, von denen in den ersten 
1.5ms ein Pin auf High sein soll und die restliche Zeit soll er auf Low 
sein.

Durch deine (korrekte) Zeitberechnung, weisst du, dass die 20ms 40 
Aufrufen der ISR entsprechen. Die ersten 3 Aufrufe der ISR soll der Pin 
auf High gehalten werden, die restlichen 37 auf Low


d.h. du baust dir da jetzt erst mal einen Zähler zusammen, der ständig 
immer nur von 0 bis 39 zählt.
1
ISR(TIM0_OVF_vect)
2
{
3
  zähler_Periode++;
4
  if (zähler_Periode == 40 )
5
    zähler_Periode = 0;


Die ersten 3 ISR Aufrufe sind aber leicht identifiziert. Denn die liegen 
genau dann vor, wenn diese Variable einen der Werte 0, 1 oder 2 hat
1
ISR(TIM0_OVF_vect)
2
{
3
  zähler_Periode++;
4
  if (zähler_Periode == 40 )
5
    zähler_Periode = 0;
6
7
  if (zähler_Periode < 3 )
8
    PB1 auf HIGH
9
  else
10
    PB1 auf Low
11
}

: Bearbeitet durch User
von Mark (Gast)


Lesenswert?

Hallo,
habe jetzt die ganze Zeit sachen ausprobiert gesucht und weiter 
ausprobiert. Allerdings alles ohne Erfolg
1
#define F_CPU 4000000UL
2
#include <avr/interrupt.h>
3
4
5
int main(void) {
6
  DDRB |= (1 << PB0);  // PB0 = Output
7
  TIMSK |= (1<<TOIE0);  // Interrupt beim Overflos auslösen
8
  sei();    // Interrupts freigeben
9
  TCCR0A |= (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);   // COM0B1 -> Clear OC0A/OC0B on Compare Match,
10
                                  // set OC0A/OC0B at BOTTOM (non-inverting mode)
11
                                // WGM01/ WGM00 -> Mode 3 Fast PWM
12
  TCCR0B |= (1 << WGM02) | (1 << CS02) | (1 << CS00); // prescale 1024  -> 4Mhz/1024/50Hz = 78 (20ms = 50Hz)
13
  OCR0A = 77; // Timer zählt 78 Takte = 20ms und springt danach wieder auf 0
14
  while(1) {
15
    OCR0B = 6; // 78/20ms*1ms~4;  78/20ms*1,5ms~6;  78/20ms*2ms~8
16
  }
17
}

Es gibt beim compilieren keinen Fehler, allerdings dreht sich der 
servomotor gar nicht. Eigentlich müsste er sich jetzt mittig 
positionieren.

hatte es auch versucht, indem ich in dem Code
1
int zaehler;
2
3
ISR(TIM0_OVF_vect)
4
{
5
  zaehler++;
6
  if (zaehler == 77)
7
    zaehler = 0;
8
9
  if (zaehler< 6)
10
    PORTB |= (1<<PB0);
11
  else
12
    PORTB &= ~(1<<PB0);
13
}
einfüge.

und den Code wie ich es am anfang vorhatte, habe ich auch ausprobiert
1
#define F_CPU 4000000;
2
#include <avr/interrupt.h>
3
4
int zaehler;
5
6
ISR(TIM0_OVF_vect)
7
{
8
  zaehler++;
9
  if (zaehler == 40 )
10
    zaehler = 0;
11
12
  if (zaehler< 3 )
13
    PORTB |= (1<<PB0);
14
  else
15
    PORTB &= ~(1<<PB0);
16
}
17
18
int main(void) {
19
  DDRB |= (1 << PB0);  // PB0 = Output
20
  TIMSK |= (1<<TOIE0);  // Interrupt beim Overflos auslösen
21
  TCCR0B |= (1 << CS01); 
22
  sei();    // Interrupts freigeben
23
  while(1) {
24
    
25
  }
26
}
Aber diese sachen ergaben keinen Unterschied. Was habe ich noch falsch? 
Kann mir da noch jemand einen tip geben?
Schon einmal vielen dank
Mark

von Thomas E. (thomase)


Lesenswert?

Mark schrieb:
> Aber diese sachen ergaben keinen Unterschied. Was habe ich noch falsch?
> Kann mir da noch jemand einen tip geben?

Der Ausgang ist PB1(OC0B)!
Den Overflow-Int brauchst du für die PWM nicht. Den kannst du optional 
verwenden, falls du z.B. noch etwas zum Blinken hast.

1
#define F_CPU 4000000UL
2
#include <avr/interrupt.h>
3
4
int main(void) {
5
  DDRB |= (1 << PB1);  // OC0B = PB1 Output
6
  TIMSK |= (1<<TOIE0);  // Interrupt beim Overflos auslösen
7
  TCCR0A |= (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);   // COM0B1 -> Clear OC0A/OC0B on Compare Match,
8
                                  // set OC0A/OC0B at BOTTOM (non-inverting mode)
9
                                // WGM01/ WGM00 -> Mode 3 Fast PWM
10
  TCCR0B |= (1 << WGM02) | (1 << CS02) | (1 << CS00); // prescale 1024  -> 4Mhz/1024/50Hz = 78 (20ms = 50Hz)
11
  OCR0A = 77; // Timer zählt 78 Takte = 20ms und springt danach wieder auf 0
12
  OCR0B = 6; // 78/20ms*1ms~4;  78/20ms*1,5ms~6;  78/20ms*2ms~8
13
  sei();    // Interrupts grundsätzlich als letztes global freigeben,
14
            // wenn jeder weiß, was er ist und keiner dem anderen
15
            // in die Parade fahren kann.
16
17
  while(1) 
18
  {
19
  }
20
} 
21
22
ISR(TIM0_OVF_vect)
23
{
24
  //Mach irgendwas
25
}

mfg.

: Bearbeitet durch User
von Mark (Gast)


Lesenswert?

Ah, jetzt funktioniert es. Ich hatte den Kontroller und den Servomotor 
mit der selben Batterie betrieben. Da hat sich auch nichts bewegt. Jetzt 
habe ich den Servomotor an einer externen Batterie angeschlossen und er 
bewegt sich.

Jetzt gucke ich, dass ich die Winkel durch Tastendruck ändern kann und 
evtl wie Thomas gefragt hat auch eine LED anzusteuern.
Aber bis hier hin, schon einmal ein sehr großes Dankeschön.

von Mark (Gast)


Lesenswert?

Ich habe noch einmal eine Nachfrage bezüglich des Parameters des OCR0B.
In diesem steckt ja im Prinzip die Pulsweite drinn.
Irgendwie passt die Rechnung aber bei mir hier nicht.
wenn ich OCR0B=6 -> 6*20ms/78~1,5ms (Servo steht in der Mitte)
OCR0B=4-> 1ms (Servo ist Links)
OCR0B=8-> 2ms (Servo ist rechts)

Allerdings durch ausprobieren fährt der Servo nur zwischen 6 und 16.9 
und dabei keine kompletten 180° (eher 160 oder so ähnlich)

Meine Frage wäre, ob oben die Berechnungen dann falsch wären, oder wieso 
ich so abweichende Werte eingeben muss?

von c-hater (Gast)


Lesenswert?

Mark schrieb:

> Allerdings durch ausprobieren fährt der Servo nur zwischen 6 und 16.9

6 und vor allem 16.9 was?

> und dabei keine kompletten 180° (eher 160 oder so ähnlich)

Das kann schon sein. Längst nicht jeder Servo fährt 180° und die 
allermeisten Servos sind auch nicht so genau, daß sie exakt bei 1 bzw. 
2ms duty ihre jeweilige Endposition erreichen. Da gibt es teils kräftige 
Toleranzen.

von Mark (Gast)


Lesenswert?

mit 6 und 6.9 meinte ich
OCR0B=6
und
OCR0B=16.9
aber danke für die Info

von Thomas E. (thomase)


Lesenswert?

Mark schrieb:
> mit 6 und 6.9 meinte ich
> OCR0B=6
> und
> OCR0B=16.9
> aber danke für die Info

Das ist Integer. Da kannst du 16 oder 17 eingeben. Aber nicht 16.9 oder 
irgendeine andere Fließkommazahl.
Hinterm Komma wird einfach abgeschnitten. D.h. aus 16.9 wird 16. Mit 17 
sollte er ein Stück weiter laufen.

mfg.

: Bearbeitet durch User
von Paul B. (paul_baumann)


Lesenswert?

Thomas schrob:
>Mit 17 sollte er ein Stück weiter laufen.

Das meine ich auch:

https://www.youtube.com/watch?v=yYsOucFRrDI

;-)
MfG Paul

von Thomas E. (thomase)


Lesenswert?

Paul Baumann schrieb:
> Thomas schrob:
>>Mit 17 sollte er ein Stück weiter laufen.
>
> Das meine ich auch:
>
> https://www.youtube.com/watch?v=yYsOucFRrDI
>
Ach Paul.

http://www.youtube.com/watch?v=2-g7bDHHEnA

mfg.

von Mark (Gast)


Lesenswert?

So, eine allerletzte nachfrage hätte ich jedoch doch noch. Und stelle 
ich für OCR0B ja jetzt werte zwischen 6 und 16 ein. Wenn der Servo in 
der endposition ist, summt der aber immer noch weiter. Wenn der Servo 
keinen Strom bekommt, kann ich diesen von Hannd noch ein stück weiter 
drehen (also er ist noch nicht in der Endposition).
Woran könnte das liegen? Oder liegt das am Motor?
http://www.conrad.de/ce/de/product/275460/Modelcraft-Micro-Servo-MC1811-MODELCRAFT-Micro-Servo-MC1811-Gleitlager-Getriebe-Kunststoff-JR?insert_kz=NA&hk=SEM&WT.srch=1&scamp=ALL_Dynamische_Suchanzeigen&saddg=Dynamische_Suchanzeigen&gclid=CJ-2t7rhvr0CFenjwgodIEgA5w

von Karl H. (kbuchegg)


Lesenswert?

Mark schrieb:

> ich für OCR0B ja jetzt werte zwischen 6 und 16 ein. Wenn der Servo in
> der endposition ist, summt der aber immer noch weiter. Wenn der Servo
> keinen Strom bekommt, kann ich diesen von Hannd noch ein stück weiter
> drehen (also er ist noch nicht in der Endposition).

Gut, schon mal wichtig.

Es gibt 3 Möglichkeiten.

Entweder steckt die Mechanik bereits im Endanschlag, so dass der Motor 
zwar versuchen würde, noch weiter zu drehen, was aber rein mechanisch 
nicht mehr möglich ist (den Fall hast du offenbar nicht)

oder aber die Position ist insofern 'schlecht', das das Poti an dieser 
Stelle ein bischen 'wackelt' und die Servoelektronik daher ständig ein 
klein wenig hin und her fährt. Sowas kommt vor. Lässt man das Servo ein 
klein wenig weiter fahren, ist das Motorbrummen wieder weg, weil hier 
das Poti stabil immer den gleichen Wert meldet.

oder dein Pulse schwanken ganz leicht. Das kann zb passieren, wenn dein 
CPU-Taktgeber nicht sehr zuverlässig ist und in der Frequenz schwankt. 
D.h. das Servo bekommt tatsächlich Pulse, die laufend ein klein wenig 
unterschiedlich sind und befolgt die selbstredend.

: Bearbeitet durch User
von Jörg E. (jackfritt)


Lesenswert?

Ich glaube je nach Servo gibt es eine elektrische und eine mechanische 
Endposition.
Die müssen nicht übereinstimmen. Wäre auch doof wenn das Servo gegen den 
mechanischen Anschlag fährt.

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.