Forum: Mikrocontroller und Digitale Elektronik Atmega 8515 Servoansteuerung


von Patrick B. (paddybo93)


Lesenswert?

Moin moin,

vorab, da dies mein erster Post ist werde ich mich kurz vorstellen. Ich 
bin Paddy, 22 Jahre alt und komme aus der Nähe von Hamburg. Bin 
gelernter Elektroniker für Betriebstechnik.

Ich bin mir leider nicht sicher ob ich hier im richtigen Bereich bin, 
sonst bitte korrigieren.

Ich habe vor kurzem erst mit der Mikrocontrollertechnik bzw. 
Hochsprachen angefangen, daher habe ich noch nicht so viel Erfahrung.

Ich wollte einen Servomotor mittels eines Atmega8515 ansteuern, nach 
diesem Vorbild: Modellbauservo Ansteuerung
Den Code habe ich etwas abgeändert, auf den 8515 bezogen.
1
#define F_CPU 16000000UL
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 = 5000-OCR1A;      // Das Servosignal wird aus der Differenz von
10
                // Periodenlänge (5000*0,004ms=20ms) und letztem
11
                // Vergleichswert (OCR1A) gebildet
12
}
13
14
15
int main (void)
16
{
17
  DDRB = 0b11111100;
18
  PORTB = (1<<PB1) | (1<<PB0);          // Pullup für PB0 und PB1
19
  
20
  TCCR1A = (1<<COM1A0);                 // Togglen bei Compare Match
21
  TCCR1B = (1<<WGM12) | (1<<CS11) | (1<<CS10);      // CTC-Mode; Prescaler 64
22
  TIMSK  = (1<<OCIE1A);                 // Timer-Compare Interrupt an
23
24
  
25
26
  
27
  OCR1A = 4625;                         // Neutralposition ((5000-4625)*0.004ms)=1,5ms)
28
  
29
  sei();                                // Interrupts global an
30
  
31
  while( 1 ) {
32
    
33
    if ( !(PINB & (1<<PINB0)) ) {       // Impuls-Zeit verlängern
34
      cli();
35
      OCR1A = OCR1A + 3;
36
      sei();
37
      _delay_ms(50);
38
    }
39
    
40
    if ( !(PINB & (1<<PINB1)) ) {      // Inpuls-Zeit verkürzen
41
      cli();
42
      OCR1A = OCR1A - 3;
43
      sei();
44
      _delay_ms(50);
45
    }
46
  }
47
48
  return 0;
49
}

Aber an meinem Servo tut sich leider gar nichts, ich habe mit nem Oszi 
an OC1A gemessen, da ist auch kein Signal. Kann mir jemand einen Tip 
geben woran es liegen kann?

vielen Dank
Gruß Paddy

: Bearbeitet durch User
von S. Landolt (Gast)


Lesenswert?

"However, note that the Data Direction Register (DDR) bit
corresponding to the OC1A or OC1B pin must be set in order
to enable the output driver."

OC1A liegt auf D5, ich sehe aber im Programm keine Zuweisung an DDRD.

von Patrick B. (paddybo93)


Lesenswert?

Ah okay vielen Dank. Muss ich den Port also noch als Ausgang beschalten?
1
DDRD |= (1 << PD5)

Ich dachte der wäre mit den folgenden Registern schon aktiviert:
1
TCCR1A = (1<<COM1A0);                 // Togglen bei Compare Match
2
TCCR1B = (1<<WGM12) | (1<<CS11) | (1<<CS10);      // CTC-Mode; Prescaler 64
3
TIMSK  = (1<<OCIE1A);                 // Timer-Compare Interrupt an

von S. Landolt (Gast)


Lesenswert?

> Muss ich den Port also noch als Ausgang beschalten?
Ja.

>Ich dachte der wäre mit den folgenden Registern schon aktiviert
Nein, deshalb hatte ich das Datenblatt des ATmega8515 zitiert.

> DDRD |= (1 << PD5)
Einfach mal ausprobieren.

von Patrick B. (paddybo93)


Lesenswert?

Hat funktioniert, ich kann jetzt mit nem Oszi am OC1A Pin ein Siganl 
messen. Zwar nicht das richtige aber egal, habe mich wohl noch irgendwo 
mit den Zahlen vertan ;-)

von Patrick B. (paddybo93)


Lesenswert?

Ich hoffe das liest hier noch jemand, da ich jetzt nicht unbedingt ein 
neues Thema aufmachen wollte.

Als nächstes wollte ich 2 Servos ansteuern (ebenfalls mit nem 
Atmega8515). Ich habe auf dieser Seite (Modellbauservo Ansteuerung) 
ganz unten auch den Code für bis zu 8 Servos gefunden, leider nur für 
nen Mega16.

Meine Überlegung war nun einfach die Register für Prescaler und CTC Mode 
anpassen. Da ich statt dem Register OCR2 OCR1B verwendet habe habe ich 
ebanfalls in der Interrupt routine
1
ISR (TIMER1_COMPB_vect)
 verwendet.

Also Servo angeschlossen und siehe da es tut sich nichts, auf keinem der 
Pins :/
Hat vielleicht jemand eine Idee was ich falsch gemacht habe? Habe die 
Register schon mehrmals überprüft...
Hier mein code:
1
#define F_CPU 16000000UL
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <util/delay.h>
6
7
//
8
// Der Prescaler muss so gewählt werden, dass der Ausdruck
9
// für MILLISEC_BASE einen Wert kleiner als 128 ergibt
10
// MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
11
// soll.
12
//
13
#define PRESCALER      128
14
#define PRESCALER_BITS (1<<CS12) | ( 1 << CS10)
15
16
#define MILLISEC_BASE  ( F_CPU / PRESCALER / 1000 )
17
#define CENTER         ( MILLISEC_BASE / 2 )
18
19
//
20
// Konfiguration der Servoleitungen
21
//
22
#define NR_SERVOS      8
23
#define SERVO_DDR      DDRD
24
#define SERVO_PORT     PORTD
25
uint8_t ServoPuls[NR_SERVOS] = { 1<<PD0, 1<<PD1, 1<<PD2, 1<<PD3,
26
1<<PD4, 1<<PD5, 1<<PD6, 1<<PD7 };
27
//
28
// Werte für die Servoposition
29
// Gültige Werte laufen von 0 bis 2 * CENTER
30
// 0           ... ganz links
31
// CENTER      ... Mittelstellung
32
// 2 * CENTER  ... ganz rechts
33
//
34
volatile uint8_t ServoValue[NR_SERVOS];
35
36
ISR (TIMER1_COMPB_vect)
37
{
38
  static uint8_t ServoId = 0;
39
40
  //
41
  // den Puls des aktuellen Servos beenden
42
  //
43
  SERVO_PORT &= ~ServoPuls[ServoId];
44
45
  //
46
  // welches ist das nächste aktuelle Servo?
47
  //
48
  if( ++ServoId >= NR_SERVOS )
49
  ServoId = 0;
50
51
  //
52
  // die Ausgangsleitung fuer dieses Servo auf 1; den Puls beginnen
53
  //
54
  SERVO_PORT |= ServoPuls[ServoId];
55
56
  //
57
  // den Timer so einstellen, dass bei Pulsende, die ISR erneut aufgerufen wird
58
  //
59
  OCR1B = MILLISEC_BASE + ServoValue[ServoId];
60
}
61
62
void InitServo()
63
{
64
  uint8_t i;
65
66
  //
67
  // Die Servoleitungen auf Ausgang stellen
68
  //
69
  SERVO_DDR = ServoPuls[0] | ServoPuls[1] | ServoPuls[2] | ServoPuls[3] |
70
  ServoPuls[4] | ServoPuls[5] | ServoPuls[6] | ServoPuls[7];
71
72
  //
73
  // Alle Servos in Mittelstellung
74
  //
75
  for( i = 0; i < NR_SERVOS; ++i )
76
  ServoValue[i] = CENTER;
77
78
  //
79
  // Timer auf CTC Modus konfigurieren
80
  //
81
  OCR1B = MILLISEC_BASE + ServoValue[0];
82
  TIMSK |= (1<<OCIE1A)|(1<<OCIE1B);
83
  TCCR1B = (1<<WGM12) | PRESCALER_BITS;  // CTC mode
84
}
85
86
int main(void)
87
{
88
  InitServo();
89
90
  sei();
91
92
  _delay_ms( 1000 );
93
94
  //
95
  // testweise einfach alle 8 Servos ansteuern
96
  // jedes Servo soll sich unterschiedlich schnell bewegen
97
  //
98
  while( 1 ) {
99
100
101
    ServoValue[0] += 2;
102
    if( ServoValue[0] > 2*CENTER )
103
    ServoValue[0] -= 2*CENTER;
104
105
    ServoValue[1] += 1;
106
    if( ServoValue[1] > 2*CENTER )
107
    ServoValue[1] -= 2*CENTER;
108
109
    ServoValue[2] += 2;
110
    if( ServoValue[2] > 2*CENTER )
111
    ServoValue[2] -= 2*CENTER;
112
113
    ServoValue[3] += 3;
114
    if( ServoValue[3] > 2*CENTER )
115
    ServoValue[3] -= 2*CENTER;
116
117
    ServoValue[4] += 1;
118
    if( ServoValue[4] > 2*CENTER )
119
    ServoValue[4] -= 2*CENTER;
120
121
    ServoValue[5] += 3;
122
    if( ServoValue[5] > 2*CENTER )
123
    ServoValue[5] -= 2*CENTER;
124
125
    ServoValue[6] += 2;
126
    if( ServoValue[6] > 2*CENTER )
127
    ServoValue[6] -= 2*CENTER;
128
129
    ServoValue[7] += 1;
130
    if( ServoValue[7] > 2*CENTER )
131
    ServoValue[7] -= 2*CENTER;
132
133
    _delay_ms( 40 );
134
  }
135
}

von Stefan F. (Gast)


Lesenswert?

> TIMSK |= (1<<OCIE1A)|(1<<OCIE1B);

Du hast hier zwei Interrupts aktiviert, aber dein Programm enthält nur 
eine Interruptroutine. Das würde ich als erste mal in Ordnung bringen.

Außerdem würde ich der Hauptschleife nicht so viel an den 
Servopositionen herum fummeln. Lass doch einfach mal jedes Servo an eine 
bestimmte feste Position fahren und messe nach, ob die erzeugten Signale 
auch dem entsprechen.

von Patrick B. (paddybo93)


Lesenswert?

Vielen Dank, werde ich morgen einmal austesten.

von Patrick B. (paddybo93)


Lesenswert?

Moin nochmal,

ich brauche noch einmal Hilfe :P
Ich habe mir jetzt einen Atmega 16 besorgt und den Originalen Code 
verwendet. Jetzt bekomme ich ein Signal, dieses ist allerdings zu lang. 
Wenn das Signal zum beispiel 2ms sein soll ist es 20ms (habe ich mit dem 
Oszi gemessen). Den Prescaler habe ich schon einige male überprüft, kann 
aber keinen Fehler feststellen. Die Taktfrequenz wird in dem Code ja mit 
11,056Mhz angegeben, da ich im Datenblatte aber 16Mhz gelesen habe, habe 
ich dieses geändert. Es hat sich allerdings nichts an der Funktion 
geändert.

Hier noch einmal der originale Code:
1
//
2
// Programm fuer einen ATmega16
3
//
4
#define F_CPU 11056000UL
5
6
#include <avr/io.h>
7
#include <avr/interrupt.h>
8
#include <util/delay.h>
9
10
//
11
// Der Prescaler muss so gewählt werden, dass der Ausdruck
12
// für MILLISEC_BASE einen Wert kleiner als 128 ergibt
13
// MILLISEC_BASE ist der Timerwert, der 1 Millisekunde Zeitdauer ergeben
14
// soll.
15
//
16
#define PRESCALER      128
17
#define PRESCALER_BITS (1<<CS22) | ( 1 << CS20 )
18
19
#define MILLISEC_BASE  ( F_CPU / PRESCALER / 1000 )
20
#define CENTER         ( MILLISEC_BASE / 2 )
21
22
//
23
// Konfiguration der Servoleitungen
24
//
25
#define NR_SERVOS      8
26
#define SERVO_DDR      DDRD
27
#define SERVO_PORT     PORTD
28
uint8_t ServoPuls[NR_SERVOS] = { 1<<PD0, 1<<PD1, 1<<PD2, 1<<PD3,
29
1<<PD4, 1<<PD5, 1<<PD6, 1<<PD7 };
30
//
31
// Werte für die Servoposition
32
// Gültige Werte laufen von 0 bis 2 * CENTER
33
// 0           ... ganz links
34
// CENTER      ... Mittelstellung
35
// 2 * CENTER  ... ganz rechts
36
//
37
volatile uint8_t ServoValue[NR_SERVOS];
38
39
ISR (TIMER2_COMP_vect)
40
{
41
  static uint8_t ServoId = 0;
42
  
43
  //
44
  // den Puls des aktuellen Servos beenden
45
  //
46
  SERVO_PORT &= ~ServoPuls[ServoId];
47
  
48
  //
49
  // welches ist das nächste aktuelle Servo?
50
  //
51
  if( ++ServoId >= NR_SERVOS )
52
  ServoId = 0;
53
  
54
  //
55
  // die Ausgangsleitung fuer dieses Servo auf 1; den Puls beginnen
56
  //
57
  SERVO_PORT |= ServoPuls[ServoId];
58
  
59
  //
60
  // den Timer so einstellen, dass bei Pulsende, die ISR erneut aufgerufen wird
61
  //
62
  OCR2 = MILLISEC_BASE + ServoValue[ServoId];
63
}
64
65
void InitServo()
66
{
67
  uint8_t i;
68
  
69
  //
70
  // Die Servoleitungen auf Ausgang stellen
71
  //
72
  SERVO_DDR = ServoPuls[0] | ServoPuls[1] | ServoPuls[2] | ServoPuls[3] |
73
  ServoPuls[4] | ServoPuls[5] | ServoPuls[6] | ServoPuls[7];
74
  
75
  //
76
  // Alle Servos in Mittelstellung
77
  //
78
  for( i = 0; i < NR_SERVOS; ++i )
79
  ServoValue[i] = CENTER;
80
  
81
  //
82
  // Timer auf CTC Modus konfigurieren
83
  //
84
  OCR2 = MILLISEC_BASE + ServoValue[0];
85
  TIMSK |= (1<<OCIE2);
86
  TCCR2 = (1<<WGM21) | PRESCALER_BITS;  // CTC mode
87
}
88
89
int main(void)
90
{
91
  InitServo();
92
  
93
  sei();
94
  
95
  _delay_ms( 1000 );
96
  
97
  //
98
  // testweise einfach alle 8 Servos ansteuern
99
  // jedes Servo soll sich unterschiedlich schnell bewegen
100
  //
101
  while( 1 ) {
102
    
103
    
104
    ServoValue[0] += 2;
105
    if( ServoValue[0] > 2*CENTER )
106
    ServoValue[0] -= 2*CENTER;
107
    
108
    ServoValue[1] += 1;
109
    if( ServoValue[1] > 2*CENTER )
110
    ServoValue[1] -= 2*CENTER;
111
    
112
    ServoValue[2] += 2;
113
    if( ServoValue[2] > 2*CENTER )
114
    ServoValue[2] -= 2*CENTER;
115
    
116
    ServoValue[3] += 3;
117
    if( ServoValue[3] > 2*CENTER )
118
    ServoValue[3] -= 2*CENTER;
119
    
120
    ServoValue[4] += 1;
121
    if( ServoValue[4] > 2*CENTER )
122
    ServoValue[4] -= 2*CENTER;
123
    
124
    ServoValue[5] += 3;
125
    if( ServoValue[5] > 2*CENTER )
126
    ServoValue[5] -= 2*CENTER;
127
    
128
    ServoValue[6] += 2;
129
    if( ServoValue[6] > 2*CENTER )
130
    ServoValue[6] -= 2*CENTER;
131
    
132
    ServoValue[7] += 1;
133
    if( ServoValue[7] > 2*CENTER )
134
    ServoValue[7] -= 2*CENTER;
135
    
136
    _delay_ms( 40 );
137
  }
138
}

vielleicht weiß ja jemand wo der Fehler liegen könnte.
Vielen dank
Paddy

von Dietrich L. (dietrichl)


Lesenswert?

Patrick B. schrieb:
> vielleicht weiß ja jemand wo der Fehler liegen könnte.

Welchen Takt verwendest Du bzw. sind die Fuses auch darauf eingestellt?

Gruß Dietrich

von Draco (Gast)


Lesenswert?

Patrick B. schrieb:
> Ich habe mir jetzt einen Atmega 16 besorgt...


Auch den CKDIV8 Fuse gelöscht? ;-)

Neuer AVR wird immer mit aktiven Ck/8 geliefert, die Fuse muss erst 
geändert werden.

von Stefan F. (Gast)


Lesenswert?

Mit F_CPU stellst du nicht die gewünschte Taktfrequenz ein!

F_CPU muss der tatsächlichen Taktfrequenz entsprechen. Die Taktquelle 
und der Vorteiler dazu wird durch Fuses festgelegt. Die wiederum müssen 
entsprechend der Beschaltung eingestellt sein.

Welche Taktfrequenz und Quelle hat deine Schaltung tatsächlich?

von Patrick B. (paddybo93)


Lesenswert?

Vielen Dank für die schnellen Antworten, ich werde jetzt zu allererst 
mal die Fuses kontrollieren. Das mit der CKDIV8 Fuse wusste ich bisher 
nicht.

von Jakob (Gast)


Lesenswert?

mega16 und CKDIV8? Kenne ich nur von den Tiy-µCs

Beim mega16 sind CKSEL, CKOPT und SUT die zuständigen Fuses.
Die stehen bei fabrikfrischen µCs aber auch so, dass etwa
1 MHz intern wirksam sind.

"The device is shipped with CKSEL = “0001” and SUT = “10”.
 The default clock source setting is therefore the 1 MHz
 Internal RC Oscillator with longest startup time. This
 default setting ensures that all users can make their
 desired clock source setting using an In-System or
 Parallel Programmer."

von Patrick B. (paddybo93)


Lesenswert?

Stimmt, die waren auf 1Mhz gestellt. Habe sie jetzt auf 8Mhz und den 
Prescaler auf 64 und siehe da ich habe ein Signal von 1ms-2ms. Vielen 
Dank für die vielen Antworten :)

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.