Forum: Mikrocontroller und Digitale Elektronik Timer Servo ansteuerung AVR Atmega16


von M. G. (elch)


Lesenswert?

Hallo,
Ich muss fuer ein Schulprojekt Servos ueber den Timer meines Atmega16's 
ansteuern.
Ich habe bereits versucht ein entsprechendes Programm zu schreiben, aber 
das Servo zuckt nur hin und her.
Warscheinlich habe ich nur einen Denkfehler in meinem Code ,aber ich 
finde ihn selbst nicht.
Hier der Teil des Codes:
1
    /*
2
             16000000 /8 (prescaler) =2000000
3
             2000000/255(timer) =7843,137255 (eine sekunde)
4
             7843,137255 / 50 =156,8627451 (20 ms)
5
             156,8627451/10=15,68627451
6
             0,68627451*255=175
7
             255-175=80 (rest)
8
            */
9
10
          if(timer_count==156 && pulse==1)    
11
            {
12
            
13
            PORTB |=(1<<PIN2); // Servo  puls an ( fuer 2 ms)
14
            PORTB |=(1<<PIN3);
15
            timer_count=0;
16
            pulse=0;
17
            TCNT0=80;
18
19
            }  
20
            else
21
          
22
              
23
24
             /*
25
             16000000 /8 (prescaler) =2000000
26
             2000000/255(timer) =7843,137255 (eine sekunde)
27
             7843,137255 / 50 =156,8627451 (20 ms)
28
             156,8627451 /20 = 7,843137255 (1 ms)
29
             7,843137255 *1,5=11,76470588
30
             0,76470588 x 255 = 195 (rest) 
31
             255-195= 60
32
            */
33
34
          if(timer_count==15 && pulse==0 ) 
35
             {
36
            PORTB &=~(1<<PIN2); // servo puls aus ( fuer 20 ms)
37
            PORTB &=~(1<<PIN3);
38
            timer_count=0;
39
            TCNT0=35;
40
            pulse=1;
41
        
42
            }

Danke fuer die Hilfe schonmal :)

von Rest (Gast)


Lesenswert?

Hast du auch ein Stück compilierfähigen Code?

Sonst liegt der Fehler immer im nicht geposteten Teil.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Der Codefetzen ist nicht ausreichend, um das Problem endgültig zu lösen.

>              2000000/255(timer) =7843,137255 (eine sekunde)

Die 255 sind falsch, hier ist nicht der Topwert des Zählers gefragt. Das 
müssen 256 sein, nämlich die Anzahl Schritte. Die 255 hast du noch 
paarmal falsch verwendet.

Aber was anderes... der Atmega16 hat doch einen CTC Modus beim Timer0. 
Richte den so ein, dass du eine Uhr bekommst, die im 2ms Abstand tickt 
(Prescaler 256, OCR0 124). Damit kannst du deine 2ms AN-Zeiten machen 
und deine 20ms AUS-Zeiten (Softwarezähler 10x2ms).

Alternativ kannst du eine PWM einrichten mit 1:10 Duty Cycle und 22ms 
Frequenz.

von M. G. (elch)


Lesenswert?

Hier der komplette code:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/delay.h>
4
#include <stdint.h>
5
  
6
  
7
  #define F_CPU 16000000;
8
  enable_interrupts(GLOBAL);      //interupts erlauben
9
  enable_interrupts(INT_TIMER0);     //timer 0 interupt erlauben
10
11
  //Globale variablen:
12
  volatile unsigned long  timer_count =0;  //fuer den timer
13
  volatile int      pulse      =0;   
14
15
16
     //////////////////////timer interupt////////////////////////////////
17
18
        ISR(TIMER0_OVF_vect)                 // timer overflow funktion
19
        {
20
           timer_count++;
21
              /*
22
             16000000 /8 (prescaler) =2000000
23
             2000000/255(timer) =7843,137255 (eine sekunde)
24
             7843,137255 / 50 =156,8627451 (20 ms)
25
             156,8627451/10=15,68627451
26
             0,68627451*255=175
27
             255-175=80 (rest)
28
            */
29
30
          if(timer_count==156 && pulse==1)    
31
            {
32
            
33
            PORTB |=(1<<PIN2); // Servo  puls an ( fuer 2 ms)
34
            PORTB |=(1<<PIN3);
35
            timer_count=0;
36
            pulse=0;
37
            TCNT0=80;
38
39
            }  
40
            else
41
          
42
              
43
44
             /*
45
             16000000 /8 (prescaler) =2000000
46
             2000000/255(timer) =7843,137255 (eine sekunde)
47
             7843,137255 / 50 =156,8627451 (20 ms)
48
             156,8627451 /20 = 7,843137255 (1 ms)
49
             7,843137255 *1,5=11,76470588
50
             0,76470588 x 255 = 195 (rest) 
51
             255-195= 60
52
            */
53
54
          if(timer_count==15 && pulse==0 ) 
55
             {
56
            PORTB &=~(1<<PIN2); // servo puls aus ( fuer 20 ms)
57
            PORTB &=~(1<<PIN3);
58
            timer_count=0;
59
            TCNT0=35;
60
            pulse=1;
61
        
62
            }
63
  
64
/////////////////////////////////////////////////////////////////////////////////////////////////////////            
65
         }
66
67
///////////////////////////////////////////////////////////////////////////////////////////
68
69
70
int main()
71
{  
72
  
73
74
////////////////////////////////////////////////////////////
75
//EINSTELLUNGEN:
76
     
77
  TCCR0 = (1<<CS01) ;    // timer 0 mit prescaler 8
78
  TIMSK = (1<<TOIE0);  //interupt timer0 enable
79
  sei();          //interupts einschalten
80
  DDRB=0xff;
81
  
82
83
///////////////////////////////////////////////////////////////
84
  
85
  while(1)
86
    {
87
    PORTB |=(1<<PIN0); //Test LED
88
    
89
    }
90
  return 0;
91
}

Ich benutze ausserdem das STK500 zum programmieren und auf meiner 
Schaltung einen 16Mhz Oszillator.

Ob der Timer  bis 255 oder bis 256 zaehlt wusste ich nicht mehr genau , 
mit 256 hat es allerdings bisher auch nicht geklappt.
Das servo soll sich spaeter auch in andere Richtungen bewegen , die 2 ms 
sind nur als Beispielprogrammierung gedacht.

Vielen Dank schonmal!

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Hast du Unterlagen oder einen Link vom Servo, wie der anzusteuern ist?

Es hilft enorm, wenn du die Rechenvorschrift zuerst in eigenen Worten 
formulierst und dann erst anfängst das in Programmcode umzusetzen.

von STK500-Besitzer (Gast)


Lesenswert?

Guck dir mal die Compare-Einheit deines Timers an.
Damit kann man solche Aufgaben total einfach lösen (bis auf ein wenig 
Gehirnschmalz...).

von Albrecht H. (alieninside)


Lesenswert?

Max G. schrieb:
> ...
>
> Ich benutze ausserdem das STK500 zum programmieren und auf meiner
> Schaltung einen 16Mhz Oszillator.
>
> Ob der Timer  bis 255 oder bis 256 zaehlt wusste ich nicht mehr genau ,
> mit 256 hat es allerdings bisher auch nicht geklappt.

Von 0-255

> Das servo soll sich spaeter auch in andere Richtungen bewegen , die 2 ms

Dann wäre aber unabhängig von allem anderen 1.5ms ein guter Wert also 
die theoretische Mittelstellung, ob der Servo funktioniert merkt man 
dann daran, dass er die Position unter Gegenkraft hält.

> sind nur als Beispielprogrammierung gedacht.
>
> Vielen Dank schonmal!

Jetzt kommt es allerdings noch darauf an, wieviele Servos du steuern 
musst/willst, es gibt hier im Forum und auch auf avrfreaks.net, 
(Anmeldung erforderlich), Beispiele bei denen bis zu 20 Servos von einem 
Controller gesteuert werden. Wenn du jetzt aber nur 2 Servos hochpräzise 
(16Bit-Timer) und bis zu 4 weitere Servos weniger präzise (8Bit-Timer) 
steuern willst, ist die obereleganteste Methode dazu die PWM. Mit der 
16Bit-Timer-PWM läßt sich der Servo rein uC-technisch mit mehr als 1000 
Einzelschritten ansteuern also z.B. 180 Grad / 1000Schritte = 0,18 Grad 
pro Schritt. Das Beste daran ist, dass die PWM völlig unabhängig vom 
restlichen Programm läuft und auch keine Interrupts benötigt, man kann 
einfach wann immer man will neue Positionswerte für den Servo in ein 
Register schreiben und der Servo rennt los zur neuen Position.

Programmcode sind das nur wenige Zeilen (< 10) für die Initialisierung 
der PWM. Die einzige Hürde besteht darin sich die Timer-Sektion im 
Datenblatt durchzulesen UND zu verstehen, und ich weise gleich darauf 
hin, dass man dazu mehrmals vor- und zurückblättern muss, es steht 
nämlich definitiv nicht alles auf einer Seite. Und man braucht auch 
nicht zu versuchen die einzelnen PWM-Modi irgendwie logisch herzuleiten, 
die sind einfach so gegeben. Man schaut sich an was es da gibt und wählt 
dann einen aus. Für erste Versuche, empfiehlt sich z.B. WGM-Mode "14" 
(FastPWM), in diesem Mode wird die Grundfrequenz der PWM, (20ms, 50Hz) 
durch ICR1 festgelegt und die Länge der einzelnen (Servo)-Pulse, (1ms - 
2ms), durch Einträge in OCR1B oder OCR1A, die Steuerausgänge für die 
(zwei) Servos sind dann beim ATmega16 an Pin 18 und 19, (ja, die heißen 
so ähnlich nämlich OC1B und OC1A, das sind aber die Pins und nicht die 
Register).

Also:

1. Steuerpins für die Servos auf Ausgang.

2. WGM-Mode einstellen, z.B. Mode "14"-FastPWM, (Die WGM-Bits sind auf 
zwei Register verteilt!)

3. CompareMatch Mode einstellen, (z.B.: Set OCnA/OCnB on Compare Match, 
Clear OCnA/OCnB, Non-Inverting-Mode (1,0))

4. ICR1 mit der Grundfrequenz laden, (z.B. 20000 für 50Hz, (bei 
entsprechendem Prescaler und Taktfrequenz)).

5. Prescaler nicht vergessen, (bei einer Taktfrequenz von z.B. 8MHz und 
einem Prescaler von "8", kann man die PVM direkt ohne Umrechnungen mit 
Mikrosekunden "füttern", d.h. ein 1500 in OCR1B oder OCR1A geschrieben 
bewegt den Servo in Mittelposition).
Das Setzen des Prescalers startet die PWM.

6. In OCR1B oder OCR1A, (je nachdem wo der Servo angeschlossen ist), den 
gewünschten Positionswert schreiben.

von OlliW (Gast)


Lesenswert?

die gemachten Vorschläge solltest du dir IMHO gut anhören, und es gibt 
an vielen Stellen auch viele Beispielcodes. Basierend auf deinem 
Beispiel verstehe ich nicht wozu du TCNT0 explizit setzt, vielleicht 
klappt es so

volatile unsigned char servo_pulse_length= 12 //12 = 1.536ms

ISR(TIMER0_OVF_vect)  //is called every 0.128 ms
{
static unsigned char timer_count;
static unsigned char pulse;

  timer_count++;
  if( pulse ){
    if( timer_count>=servo_pulse_length ){
      pulse= 0;
      timer_count= 0;
      PORTB &=~(1<<PIN2); // Servo puls aus
    }
  }else{
    if( timer_count>=156-servo_pulse_length ){ //156 = 19.968ms
      pulse= 1;
      timer_count= 0;
      PORTB |=(1<<PIN2); // Servo puls an
    }
  }
}

hat so keine sehr hohe Auflösung, wenn du den Prescaler 1 setzt, und 
statt char dann einige int nimmst, bekommst du schon für viele Zwecke 
mehr als ausreichend Auflösung
have fun, Olli

von Albrecht H. (alieninside)


Lesenswert?

Kleine Verwirrung, das wäre ein passender Mode:

3. CompareMatch Mode einstellen, (Clear OC1A/OC1B on compare match, set 
OC1A/OC1B at BOTTOM, (non-inverting mode) (1,0))

von M. G. (elch)


Lesenswert?

Danke fuer die Vielen Antworten!
Ich denke ich werde es mit PWM probieren , allerdings habe ich da schon 
wieder die naechste Frage.
Ich habe mich schonmal theoretisch ueber die PWM Programmierung 
informiert, allerdings leuchtet mir nicht ein wie ich den Vergleichswert 
in OCR1A setzen muss damit ich die gewuenschte Frequenz erhalte.
Mit freundlichen Gruessen!

von Karl H. (kbuchegg)


Lesenswert?

Max G. schrieb:

> Ich habe mich schonmal theoretisch ueber die PWM Programmierung
> informiert, allerdings leuchtet mir nicht ein wie ich den Vergleichswert
> in OCR1A setzen muss damit ich die gewuenschte Frequenz erhalte.

Dann wirds Zeit, dass du dir klar machst, wie das Ganze auf 
Hardwareebene eigentlich funktioniert.

Da ist ein Timer.
Der zählt ständig

0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 1

Immer wenn er bei 0 ist, macht er etwas. zb einen Pin auf High setzen.

Wie oft macht er das in einer Sekunde?

Das hängt ganz offensichtlich davon ab, wie schnell der Timer zählt und 
es hängt auch davon ab, ob er bis 8 oder nur bis 4 zählt.

0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1

Jetzt kommt die 0 im selben Zeitraum öfter, weil der Timer ja nicht so 
weit hochzählt.

Wie schnell zählt denn jetzt der Timer (unabhängig davon wie weit er 
zählt)?

Der Timer hängt am Systemtakt (also an deinem Quarz mit dem auch der 
Rest des µC versorgt wird). Ist der 16Mhz dann macht der Timer 16 
Millionen Zählvorgänge in der Sekunde, isr der 12Mhz dann eben 12 
Million, ist der 3.768Mhz dann eben 3.768 Millionen.

Da das ziemlich viel ist, gibt es den Vorteiler. Der Vorteiler teilt die 
Systemfrequenz durch einen einstellbaren Faktor runter. Ist der 
Vorteiler 8 und der Systemtakt 16Mhz, dann macht der Timer nur 16/8 = 2 
Millionen Zählvorgänge in der Sekunde.

Jetzt interessieren uns ja nicht die Anzahl der Zählvorgänge sondern uns 
interessiert, wie oft dabei die 0 vorkommt.
Wenn der Timer also in 1 Sekunde bis 2 Millionen zählt UND er jedesmal 
bis 8 zählt (das sind 9 Zählvorgänge, auf die 0 nicht vergessen!) dann 
kommt die 0 genau 2000000 / 9 = 222222.222 mal vor. Schaltet man bei 
jeder 0 einen Pin ein und bei einem anderen Zählerstand wieder aus, dann 
hat man daher eine Frequenz von 222222.222Hz oder knapp 222kHz.
Lässt man den Timer nicht bis 8 sondern bis 138 zählen und verändert 
sonst nichts, dann beträgt diese Frequenz 2000000 / 139 = 14388.49Hz 
also schon bedeutend weniger.

Und, Überraschung, das wars. Das ist alles was man wissen muss um zu 
verstehen, wie man eine bestimmte Frequenz mit einem Timer erzeugen 
kann.

Die Freuqenz ergibt sich aus:
Systemfrequenz, Vorteiler und wie weit der Timer zählen muss. Die 
Systemfrequenz kannst du meistens nicht mehr beeinflussen, die ist 
vorgegeben. Aber an den beiden Parameter Vorteiler und Höchstwert kann 
man drehen, bis man die gewünschte Frequenz erreicht hat.

von Albrecht H. (alieninside)


Lesenswert?

Max G. schrieb:
> ...
> Ich habe mich schonmal theoretisch ueber die PWM Programmierung
> informiert, allerdings leuchtet mir nicht ein wie ich den Vergleichswert
> in OCR1A setzen muss damit ich die gewuenschte Frequenz erhalte.

Also wenn du dich auf meine Ausführungen beziehst, dann: Gar nicht!
Bei den von mir vorgeschlagenen Modi hat OCR1B bzw. OCR1A überhaupt 
nichts mit der Frequenz sondern nur mit der PULSWEITE zu tun, (für die 
Frequenz ist ICR1 zuständig).


Wenn du jeden Tag in die Schule gehen würdest, dann wäre die Frequenz 
deiner Schulbesuche:

f = 365 / 1_Jahr_in_Sekunden

oder auch:

f = 7 / 1_Woche_in_Sekunden

Das sagt etwas über Häufigkeit aus mit der ein Ereignis stattfindet, 
also wie oft du in die Schule gehst. Damit ist aber noch immer völlig 
unklar WIE LANGE du jeden Tag in die Schule gehst. Kann ja sein, dass du 
da jeden Tag immer bloß kurz "Hallo" sagst und dann gleich wieder 
abhaust. Oder das andere Extrem, dass du da jeden Tag 23h 59m 59s 
verbringst.


Die Pulsweitenmodulation besteht also aus zwei Komponenten:

1. Der Häufigkeit, mit der ein Ereignis stattfindet, (das wären beim 
(Standard)-Servo unveränderlich alle 20ms ein Puls, also 50 mal pro 
Sekunde -> 50Hz).

und

2. Der Dauer, die angibt wie lange das Ereignis andauert, (das wäre beim 
Servo die Dauer des Steuer-Pulses, die ist veränderlich und soll je nach 
gewünschter Position zwischen 1ms bis 2ms dauern).


Warum der Servo, wenn er alle 20ms ein Signal mit einer Dauer von 1ms 
bis 2ms erhält, in ein bestimmte Position fährt ist natürlich eine ganz 
andere Frage.


Bei den von mir vorgeschlagenen Einstellungen:

"WGM-Mode-14-FastPWM"
-> Dafür entsprechende Bits in TCCR1B und TCCR1A setzen.

+

"(1,0) Clear OC1A/OC1B on compare match, set
OC1A/OC1B at BOTTOM, (non-inverting mode)"
-> Dafür entsprechende Bits in TCCR1A setzen.


legt nun der Wert im Register "ICR1" die (von uns so gewünscht 
unveränderliche) Frequenz der PWM fest. Wohingegen Einträge in "OCR1B" 
und/oder "OCR1A" die Dauer/Länge der Pulse, die die PWM ausgibt, 
festlegen.

Die Werte die nun tatsächlich in "ICR1", "OCR1B" und/oder "OCR1A" 
eingetragen werden müssen, hängen ausschließlich von der Taktfrequenz 
des Mikrocontrollers und dem eingestellten  Vorteiler "Prescaler" ab.

Ok, schauen wir mal bei 16 MHz:

Wir brauchen eine Frequenz von 50 Hz, (der Timer soll dabei aber immer 
noch so fein einstellbar sein, dass er zusätzlich auch noch Zeiten 
zwischen 1ms und 2ms in ausreichend kleinen Schritten darstellen kann).

Und wie fängt man da an? Einfach mal 16MHz durch 50Hz teilen? Warum 
nicht, kann ja nicht schaden ...:

16000000Hz / 50Hz = 320000

Was bedeutet das? Nun, nicht viel, nur dass nach jeweils 320000 
Taktzyklen 20ms vergangen sind und dass das 50 mal in der Sekunde 
geschieht.
Wir sollten also irgendwie 320000 Taktzyklen abzählen können. Bisschen 
blöd auf einem 8Bit-uC, da wird meist nur von 0-255 gezählt, aber auch 
16Bit helfen nicht richtig weiter (0-65535)...
Programmiertechnisch und vor allem in C würde das Zählen mit ein paar 
großen Variablen natürlich schon gehen, aber das ganze soll ja ohne 
Spezial-Programm nur mit den Möglichkeiten der ATmega-Timer im 
Hintergrund ablaufen.

Aber Moment, da gibt es ja noch den Prescaler. Der Prescaler ist sehr 
einfach aufgebaut, da gibt es nicht viele Möglichkeiten, nämlich nur 
Teiler von: 0, 1, 8, 64, 256 und 1024.

Nehmen wir mal 8 ;-), da sieht die Rechnung schon etwas handlicher aus:

(16000000Hz / 8) / 50Hz = 40000

Na immerhin, das passt ja schon mal in ein 16Bit Register. Super Sache, 
wenn wir also den Prescaler auf 8 setzen und den Wert 40000 in ICR1 
schreiben, (und ICR1 ist zufälligerweise ein 16Bit-Register), dann läuft 
die PWM konstant mit 50Hz.

Bleibt noch die Pulsdauer, (also der Wert der in OCR1B und/oder OCR1A 
geschrieben werden muss um die Position des Servos festzulegen). Das ist 
jetzt einfach, wir wissen ja bereits, dass ein (Timer)-Wert von 40000 
genau 20ms entspricht.
Wieviele (vorgeteilte) Takte müssen es dann für 1ms, 1.5ms, 2ms, 1/10ms 
oder 1/100ms sein?

40000 / 20ms = 2000 (Timertakte für 1ms)

->
Timerwert 2000 ~ 1ms Pulsdauer
Timerwert 200 ~ 1/10 ms Pulsdauer
Timerwert 20 ~ 1/100 ms Pulsdauer
Timerwert 2 ~ 1/1000 ms Pulsdauer
Timerwert 1 ~ 1/2000 ms Pulsdauer

Bekannte Werte für ein Servo sind ja nun:
1.0 ms = ganz links (je nachdem von wo man draufschaut)
1.5 ms = Mittelstellung
2.0 ms = ganz rechts

also:
Timerwert 2000 = ganz links
Timerwert 3000 = Mittelstellung
Timerwert 4000 = ganz rechts

Und halt alles was dazwischen liegt.


Ergänzung:
Ja, es gibt auch PWM Modi bei denen die Frequenz mit OCR1A festgelegt 
wird.
Und: In der Praxis können die Werte für Servoausschlag "ganz links" und 
Servoausschlag "ganz rechts" um einiges von den Idealwerten 1,0ms bzw. 
2.0ms abweichen, also darunter bzw. darüber liegen.
Darauf achten, dass nicht schon in den Fuses ein Taktvorteiler aktiviert 
ist und überhaupt mit den Fuses sicherstellen, dass der Controller auch 
tatsächlich mit dem gewünschten Takt läuft.

von M. G. (elch)


Lesenswert?

Wow, danke fuer die Ausfuehrliche Hilfe!
@Albrecht H.
Ich hab es mal nach deiner Beschreibung versucht und fand das auch alles 
recht einleuchtend , aber funktionieren tut es leider trotzdem noch 
nicht.
Hier ist das Programm das ich bis jetzt verfasst habe:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/delay.h>
4
#include <stdint.h>
5
    
6
  #define F_CPU 16000000;
7
  enable_interrupts(GLOBAL);      //interupts erlauben
8
  enable_interrupts(INT_TIMER1);     //timer 1 interupt erlauben
9
    
10
          
11
main()
12
{  
13
  //Grundeinstellungen:
14
  DDRB  =0xff;      // PORT B als ausgang
15
  DDRD |=(1<<DDD5)|(DDD1);// PIN D5 und D1 als Ausgang
16
  PORTD|=(1<<PIN1);
17
  TCCR1A |=(1<<WGM10)|(1<<COM1A1)|(1<<CS11); // 16 Bit timer /nicht invertierend/prescaler8
18
  TCCR1B |=(1<<WGM13)|(1<<WGM12)|(1<<WGM11); //PWM FAST MODE 14
19
  ICR1=40000; //Timer Frequenz vn 50 hz (20ms)
20
  OCR1A=3000; //Pulslaenge von 1,5 ms (Mittelstellung)
21
  sei();
22
23
  while(1)
24
  {}
25
26
  
27
}

Mit freundlichen Gruessen!

von Albrecht H. (alieninside)


Lesenswert?

Na ja, ..., also das ist jetzt für den ATmega644p, kompiliert aber auch 
für den ATmega16 und die Pinbelegung sollte zumindest für OC1B auch die 
selbe sein, probiers mal aus.

Die Tastenabfrage mit den langen Delays ist nur für Testzwecke gedacht 
und ansonsten ein Beispiel dafür, wie man es nicht machen sollte.

1
#define F_CPU 16000000UL
2
3
#include <avr/io.h>
4
#include <util/delay.h>
5
6
7
#define DefServoMinAbs 2000
8
#define DefServoMidAbs 3000
9
#define DefServoMaxAbs 4000
10
11
12
uint16_t tOCVal=0;
13
uint8_t ServoAdjMoveWidth=40;
14
15
int main(){
16
17
  DDRA  = 0b00000000;
18
  PORTA = 0b11111111;
19
20
21
  //Servo-Ausgang OC1B
22
  DDRD |=(1 << PD4);  //OC1B
23
24
  
25
  //TimerCounterControlRegisterA/B, WaveformGenerationMode
26
  // Fast PWM Mode 14
27
  TCCR1B |=(1<<WGM13);
28
  TCCR1B |=(1<<WGM12);
29
  TCCR1A |=(1<<WGM11);
30
  TCCR1A &=~(1<<WGM10);
31
32
  
33
  // Clear OCnA/OCnB on Compare Match, Set OCnA/OCnB on at Bottom (Non-Inverting Mode) (1,0)
34
  TCCR1A |=((1<<COM1A1) | (1<<COM1B1)); TCCR1A &=~((1<<COM1A0)|(1<<COM1B0));
35
36
  // PWM base frequency (higher values -> lower frequency)
37
  ICR1=40000; //PwmBaseFreq
38
39
  
40
  // Length of PWM Pulse OCR1B (higher values -> broader pulse)
41
  // Output Compare Register 1B
42
  OCR1B=DefServoMidAbs;
43
44
  
45
  // Prescaler, 8:
46
  TCCR1B &=~(1<<CS12);
47
  TCCR1B |=(1<<CS11);
48
  TCCR1B &=~(1<<CS10);
49
50
51
  while (1){
52
53
  // **************************************************************
54
    // + Button
55
    if ( ( ~PINA & (1<<PA0)) ){
56
      _delay_ms(200);
57
      tOCVal=OCR1B;
58
      tOCVal+=ServoAdjMoveWidth;
59
  
60
      if ((tOCVal>DefServoMaxAbs)){tOCVal=DefServoMaxAbs;}
61
62
      OCR1B =tOCVal;
63
64
    }
65
66
  // **************************************************************
67
    // - Button
68
    if ((~PINA & (1<<PA1))){
69
      _delay_ms(200);
70
      tOCVal=OCR1B;
71
      tOCVal-=ServoAdjMoveWidth;
72
  
73
      if ((tOCVal<DefServoMinAbs)){tOCVal=DefServoMinAbs;}
74
75
      OCR1B =tOCVal;
76
    }
77
78
79
    // **************************************************************
80
    if ((~PINA & (1<<PA2))){
81
    OCR1B =DefServoMinAbs;
82
    }
83
    // **************************************************************
84
    if ((~PINA & (1<<PA3))){
85
    OCR1B =DefServoMidAbs;
86
    }
87
    // **************************************************************
88
    if ((~PINA & (1<<PA4))){
89
    OCR1B =DefServoMaxAbs;
90
    }
91
  }
92
}

von M. G. (elch)


Lesenswert?

Vielen Danke, es funktiniert !! :D
Zumindest eingeschraenkt ;)
Bei jedem zweiten Einschalten zuckt das Servo einfach nur ohne sich an 
die Stelle zu begeben die programmiert wurde.
Woran koennte das liegen ?
Auch das andere Servo , das ich zwar an den Atmega angeschlossen habe , 
allerdings noch nicht programmiert, zuckt.
Mit freundlichen Gruessen!

von Albrecht H. (alieninside)


Lesenswert?

Max G. schrieb:
> Vielen Danke, es funktiniert !! :D
> Zumindest eingeschraenkt ;)
> Bei jedem zweiten Einschalten zuckt das Servo einfach nur ohne sich an
> die Stelle zu begeben die programmiert wurde.
> Woran koennte das liegen ?

Gut, da müsste ich jetzt mal selbst einen ATmega16 ins Steckbrett 
einsetzen, ich hab das vorher nur mit dem ATmega644p ausprobiert der 
halbwegs Pin- und Registerkompatibel zum ATmega16 ist.

Ok, gesagt getan. Und im ersten Moment dachte ich: "Oh, da stimmt ja 
tatsächlich was nicht!". Aber nein, Fehlalarm, es war nur die 
"SUT_CKSEL"-Fuse falsch gesetzt, nämlich auf "RC Osc. 8.0 MHz..." 
anstatt auf "Ext. Crystal/Resonator High Freq.;... 16K CK + 64 ms".

Danach funktionierte dann alles wie erwartet: Beim Einschalten der 
Spannungsversorgung ruckelt der Servo kurz und fährt dann sofort in 
Mittelposition, dann je nach Tastendruck, Links, Mitte, Rechts, 
schrittweise vor und zurück ohne Zittern oder sonstwas.


Und woran könnte es nun bei dir liegen?

Da sehe ich in erster Linie zwei wahrscheinliche Ursachen:

Vorab:
Mach erstmal meine Tastenabfragen komplett raus! Vielleicht prellt da 
was bei dir, darum kannst du dich aber später kümmern, (-> 
Artikelsammlung, Wiki, Peter Dannegger / Tastenentprellung).

Probier dann erst was einfaches aus wie, (zwischen den Delays noch eine 
LED als zusätzliche Kontrolle ein und auszuschalten kann definitiv 
hilfreich sein):
1
while (1){
2
3
_delay_ms(2000);
4
5
OCR1B=DefServoMidAbs;
6
7
_delay_ms(2000);
8
9
OCR1B =DefServoMinAbs;
10
11
_delay_ms(2000);
12
13
OCR1B =DefServoMaxAbs;
14
15
_delay_ms(2000);
16
17
}

Wenn es dann funktionuert lag es halt an den Tastern, (die werden in 
meinem Beispiel übrigens gegen GND (-) geschaltet).

Falls nicht dann:


1. Deine Taktfrequenz stimmt nicht:
Fuses überprüfen, (für 16MHz mit externem Quarz -> "SUT_CKSEL" auf "Ext. 
Crystal/Resonator High Freq.;... 16K CK + 64 ms")! Und wenn du ein 
Entwicklungsboard wie das STK500 verwendest, nachschauen ob die Jumper 
für den externen Quarz auch wirklich richtig gesetzt sind! Ich muss da 
auch selbst jedesmal im User Manual nachschauen, wie die richtige 
Einstellung ist, wenn ich mal was geändert habe!
Dann die Frequenz an OC1B gegen Masse mit dem Oszilloskop nachprüfen! 
Das müssen 50Hz sein, diese Messung funktioniert sogar mit meinem 
billigen Multimeter (UNI-T), mit Frequenzmessbereich.


2. Der Hardwareaufbau:
Also allzu schwierig kann es eigentlich nicht sein, der Aufbau 
funktioniert bei mir problemlos auf einem Steckbrett. Trotzdem sind 
16MHz natürlich etwas kritischer als 8MHz, (die ich aus nostalgischen 
Gründen öfter mal verwende).

Sind der Quarz und die zwei 22pF (oder 15pF) Kondensatoren so nah als 
möglich an den entsprechenden Prozessorpins?

Reset über 10K an (+), und mit 100nF an Masse?

Sind überall (VCC/GND) 100nF Abblock/Überbrückungs-Kondensatoren 
eingesetzt?

Ist die Spannungsversorgung stabil? Läuft die Schaltung mit 5V?

Bekommt der Servo genügend Strom? Die Spannungsversorgung sollte zum 
Testen am unbelasteten Servo mindestens 2A liefern können ohne 
einzubrechen!

Saubere Kontake, kurze Verbindungsleitungen, blabla, ...




> Auch das andere Servo , das ich zwar an den Atmega angeschlossen habe ,
> allerdings noch nicht programmiert, zuckt.
> Mit freundlichen Gruessen!

von Albrecht H. (alieninside)


Lesenswert?

Nachtrag:

Ich hab gerade nochmal ein paar Servos "wild" drehen lassen auch unter 
Last und mit Blockieren, ein Mikroservo hat dabei sogar seine Sperre 
durchbrochen und kreiselte dann nur noch. Also die können schon in die 
Mikrocontrollerschaltung einstreuen, wenn man es darauf anlegt, entweder 
über die gemeinsame Versorgungsleitung oder möglicherweise auch über den 
Steuereingang. Es ist mir auch ein paar mal gelungen das Programm in 
einen undefinierten Zustand zu bringen u.a. in dem ich einen üblen 
Wackelkontakt an der gemeinsamen Versorgungsleitung simulierte, ein 
zusätzlich angeschlossenes LCD kam auch mehrmals aus dem Takt und 
stellte die Wiedergabe ein.

Subjektiv hatte ich dabei den Eindruck, dass die Schaltung mit meinem 
alten ATmega16 mit 16MHz Quarz störanfälliger war als mit einem 8MHz 
Quarz und insgesamt störanfälliger als dieselbe Schaltung mit einem 
neuen ATmega644p.

Der ATmega644p war mit 8MHz kaum aus dem Takt zu bringen und startete 
höchstens mal neu über die Brownout-Detection wenn man es zu bunt trieb.

Es sind also u.U. noch ein paar Einstörmaßnahmen erforderlich ..., 
(Kondensatoren, Drosseln, Dioden?).

Ich erinnere mich, sowas auch mal mit einem 8051 aufgebaut zu haben, da 
hatte ich von vornherein, getrennte Spannungsversorgungen für uC und 
Servo verwendet und das ganze über einen Optokoppler zusammengeschaltet, 
aber ob das wirklich notwendig ist ...

Vorbildlich verhielt sich übrigens ein vergleichsweise teures 
Graupner-Digitalservo im roten Aluminiumgehäuse.

von M. G. (elch)


Lesenswert?

So, nachdem ich letzte Woche einiges um die Ohren hatte ,konnte ich mich 
gestern wieder dem Projekt widmen. Ich habe einfach die Servos durch 
zwei neue ersetzt und jetzt funktioniert alles wunderbar, die Servos 
bewegen sich an die Stelle die programmiert ist und bleiben dort. :)
Vielen Dank an alle die mir hier geantwortet haben ! :D

von Phillip H. (philharmony)


Lesenswert?

Hi, ich habe diesen Beitrag hier grade gefunden, und hänge auch mit 
meinem Servo-Program:

Ich möchste allerdings nicht die OCR-Pins benutzen, sondern in der 
Interrupt-Routine selbst nach einander die Pins von PortB ansteuern.

Ich benutze dazu den Fast-PWM Mode 14.
Quarz ist extern 14,76MHz.
Bei einem Prescaler von 8 ergibt sich dann ein Increment (also eine 
256tel ms) von 7.
Die Idee ist nun, die 2ms Gesamtlänge mit dem ICR1 zu realisieren (ICR1 
= 2  INCREMENT  256 = 3584), die Pulsbreite mit dem OCR1A = ((256 + 
Sollwert) * INCREMENT).
Benutzt wird dazu das TIMER1_COMPA Interrupt.
1
ISR(TIMER1_COMPA_vect)
2
{
3
  static uint8_t phase;
4
5
  if(phase)
6
  {
7
    phase = 0;
8
    PORT_SERVO |= (servos_used);
9
    OCR1A = (INCREMENT * (ONE_MS + servodata[0]));
10
  }
11
  else
12
  {
13
    phase = 1;
14
    PORT_SERVO &= ~(servos_used);  //Alle Servo-Pins auf 0
15
                //Hier habe ich eine ms ans ende angehängt
16
    OCR1A = (INCREMENT * ((ONE_MS * 2) - servodata[0]));
17
    return;
18
  }
19
}


Das Problem ist, daß ich eine Pulslänge von 18ms erhalte, COntroller ist 
richtig gefused.

Die init sieht so aus:
1
void init_servo_timer(void)
2
{
3
  TCCR1B |= (1 << CS11);    //Prescaler 8 
4
  TCCR1A |= (1 << WGM11);    //Fast PWM Mode
5
  TCCR1B |= (1 << WGM12) | (1 << WGM13);    //Fast PWM Mode
6
  TIMSK |= (1 << OCIE1A);    //Compare Match OCR1A Enabled
7
  ICR1 = (2* ONE_MS * INCREMENT);//Timer Top Value auf 2ms setzen
8
}
Ich versteh nicht, warum das Timing nicht stimmt. Habe schon mit allen 
möglichen Modi rumgespielt und komme jetzt nicht mehr weiter. Seht Ihr 
da was auf Anhieb?

von Phillip H. (philharmony)


Lesenswert?

Das is ja geil: es funktioniert, und zwar dann, wenn ich meine #defines 
durch die Plain-Text-Zahlenwerte ersetze!!!???
1
#define PRESCALER   1
2
#define RESOLUTION   256
3
#define ONE_MS    RESOLUTION-1
4
#define INCREMENT   F_CPU/(1000*PRESCALER*RESOLUTION)


So, die Funktion
1
ICR1 = (3 * ONE_MS* INCREMENT);
Hat als Ergebnis 768. (Also 3 * ONE_MS)
Wenn ich mir INCREMENT ausgeben lasse, kommt 0!

Hingegen
1
ICR1 = (3 * 255 * 58);
setzt alles korrekt.

Was ist denn das?

von Karl H. (kbuchegg)


Lesenswert?

1000*PRESCALER*RESOLUTION

ergibt einen satten Overflow

1000        ist ein int
PRESCALER   (mit dem Zahlenwert 1) ist ein int
RESOLUTION  (mit dem Zahlenwert 256) ist ein int

Also werden die Multiplikationen als int-Multiplikationen gemacht.

Das Ergebnis, 256000, passt aber nicht mehr in einen int -> Boing!
1
#define INCREMENT   F_CPU/(1000L*PRESCALER*RESOLUTION)

Das ist das eine.
Das andere ist das hier:
1
#define ONE_MS    RESOLUTION-1

Oh, oh. Gefährlich!

Mach doch mal die Textsubstitution
1
ICR1 = (3 * ONE_MS* INCREMENT);
1
ICR1 = ( 3 * RESOLUTION-1 * F_CPU/(1000L*PRESCALER*RESOLUTION) )

Tja. Nur sagen die Matheregeln, dass das so zu lesen ist
1
ICR1 = ( ( 3 * RESOLUTION ) - ( 1 * F_CPU/(1000L*PRESCALER*RESOLUTION) ) );

Fazit:
Gerade bei mathematischen Ausdrücken in Makros haben ein paar Klammern 
noch nie geschadet. Das vermeidet unliebsame Überraschungen bei der 
textuellen Substitution
1
#define ONE_MS      ( RESOLUTION-1 )
2
#define INCREMENT   ( F_CPU/(1000*PRESCALER*RESOLUTION) )

Aus
1
#define x   a-b
2
3
i = 2 * x

wird nun mal
1
i = 2*a - b;

und das ist meist nicht das, was eigentlich beabsichtigt war.

von Phillip H. (philharmony)


Lesenswert?

Klatsch
Oh man, wie blöd von mir.
Danke!

Allerdings liefert mir
1
   ICR1 = (3 * ONE_MS * INCREMENT);
immernoch eine Warning "Integer Overflow in Expression", obwohl der Erg. 
ja mit 44064 eigentlich in das Int reinpassen sollte oder?

von Karl H. (kbuchegg)


Lesenswert?

Phillip Hommel schrieb:

> immernoch eine Warning "Integer Overflow in Expression", obwohl der Erg.
> ja mit 44064 eigentlich in das Int reinpassen sollte oder?

Ähm. 44064 passt in einen 16 Bit int nicht mehr rein.

von Phillip H. (philharmony)


Lesenswert?

DumdiDum, nix gesagt. Geht...Danke Leute!

von Robert M. (roberta)


Lesenswert?

Ich hoffe ich werde nicht der Leichenschändung gestraft für die Aktion..

Ich konnte den Code von Albrecht H. (alieninside) nicht nur verstehen 
sondern auch wunderbar an meinen ATmega88 mit 8MHz anpassen 2 Servos 
bzw. 2 Brushlessmotoren mit Regler lassen sich Einwand frei ansteuern.

mein Problem ist ich würder gerne 4 Motorensteuern ich dachte ich könnte 
einfach OC0A und OC0B mit dazu ziehen stehe aber inzwischen wieder im 
nirgendwo

hat jemensch nen Tipp?!

Danke an Albrecht H. (alieninside) für die Super erklärung mit Frequenz 
und Zählzeiten etc. endlich hab ich das geraft

von manohman (Gast)


Lesenswert?

Robert M. schrieb:
> Ich hoffe ich werde nicht der Leichenschändung gestraft für die Aktion..

> mein Problem ist ich würder gerne 4 Motorensteuern ich dachte ich könnte
> einfach OC0A und OC0B mit dazu ziehen stehe aber inzwischen wieder im
> nirgendwo

Mach 'nen eigenen thread auf.

> hat jemensch nen Tipp?!

Poste was du an Programm schon hast, eventuell Schaltplan aber 
mindestens die Anschlüsse zu den "Motoren" (hier geht es um 
Modellbauservos) und beschreibe was du mit "OC0A und OC0B mit 
dazuziehen" meinst. So weiß man ja noch nicht mal was (philharmony) hat 
ohne sich in den ganzen thread einarbeiten zu müssen und du dann doch 
irgendwas anderes gemacht hast.

von Robert M. (roberta)


Lesenswert?

Ok
Also ich will 4 Brushless Regler steuern die dann
(so zumindest der Plan) an OCR1A, OCR1B, OCR0A OCR0B hängen sollen.

Hier der angepasste Code von Albrecht
1
 #define F_CPU 8000000UL
2
3
#include <avr/io.h>
4
#include <util/delay.h>
5
6
7
#define DefMotorMin 1000
8
#define DefMotorMid 1500
9
#define DefMotorMax 2400
10
11
#define DefBase 950
12
13
int main() {
14
15
16
    //Servo-Ausgang OC1A und OC1B
17
    DDRB = (1 << PB1) | (1 << PB2); //OC1A und OC1B 
18
19
    //TimerCounterControlRegisterA/B, WaveformGenerationMode
20
    // Fast PWM Mode 14
21
    TCCR1B |= (1 << WGM13);
22
    TCCR1B |= (1 << WGM12);
23
    TCCR1A |= (1 << WGM11);
24
    TCCR1A &= ~(1 << WGM10);
25
26
27
    // Clear OCnA/OCnB on Compare Match, Set OCnA/OCnB on at Bottom (Non-Inverting Mode) (1,0)
28
    TCCR1A |= ((1 << COM1A1) | (1 << COM1B1));
29
    TCCR1A &= ~((1 << COM1A0) | (1 << COM1B0));
30
31
    // PWM base frequency (higher values -> lower frequency)
32
    ICR1 = 15000; //PwmBaseFreq
33
34
    // Prescaler, 8:
35
    TCCR1B &= ~(1 << CS12);
36
    TCCR1B |= (1 << CS11);
37
    TCCR1B &= ~(1 << CS10);
38
39
    //------------------------------
40
41
    //Servo-Ausgang OC0A und OC0B
42
    DDRD = (1 << PD5) | (1 << PD6); //OC0B und OC0A
43
44
    // Clear OCnA/OCnB on Compare Match, Set OCnA/OCnB on at Bottom (Non-Inverting Mode) (1,0)
45
    TCCR0A |= ((1 << COM0A1) | (1 << COM0B1));
46
    TCCR0A &= ~((1 << COM0A0) | (1 << COM0B0));
47
48
//       // Prescaler, 8:
49
//    TCCR0B &= ~(1 << CS02);
50
//    TCCR0B |= (1 << CS01);
51
//    TCCR0B &= ~(1 << CS00);
52
53
54
55
    // Regler initialisiren 
56
    OCR1B = DefBase;
57
    OCR1A = DefBase;
58
    OCR0B = DefBase;
59
    OCR0A = DefBase;
60
    _delay_ms(5000);
61
62
    while (1) {
63
64
        OCR1B = DefBase;
65
        OCR1A = DefBase;
66
        OCR0B = DefBase;
67
        OCR0A = DefBase;
68
69
        _delay_ms(2000);
70
71
        OCR1B = (DefBase + 650);
72
        OCR1A = DefMotorMax;
73
        OCR0B = (DefBase+800);
74
        OCR0A = DefMotorMax;
75
76
        _delay_ms(2000);
77
78
    }
79
}

OCR1A und OCR1B laufen wunderbar und ich kann die Motoren ansteuern.

von Hubert G. (hubertg)


Lesenswert?

Möglicherweise liegt es daran, das Timer0 ein 8bit Timer ist und Timer 1 
ein 16bit Timer.

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.