Forum: Mikrocontroller und Digitale Elektronik Attiny85 Servo


von Sebastian M. (meeresgott)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe ein Problem mit der Programmierung eines Attiny85. Ich möchte 
diesen als "Servo-Treiber" verwenden. Das Standard Protokoll für die 
Ansteuerung eines Hobby-Servos ist eine Art des PWM's. Die Frequenz ist 
~50Hz und ein High in der Länge von 1ms entspricht 0° Ausschlag und ein 
High in der Länge von 2ms entspricht 180° Ausschlag. Zur Ansteuerung des 
Motors verwende ich eine L9110H H-Brücke und ein handelsübliches linear 
Poti als Messglied. Um 90° zu kalibrieren habe ich zusätzlich noch einen 
Taster angeschlossen. Ich habe euch dazu mal eine Skizze gemalt 
(gezeichnet trifft nicht wirklich zu ;) ).

Ich habe Probleme beim einlesen, des Steuerungssignales.
Das ist der Code den ich bisher habe soweit reduziert, dass Pin 2 1 ist,
wenn das Signal länger als 1,5 ms und 0, wenn es kürzer ist.
Allerdings will der Code nicht funktionieren. Könnt Ihr mir da auf die 
Sprünge helfen?
1
 
2
#include <avr/io.h>
3
#include <avr/interrupt.h> 
4
5
#define ICP PINB0
6
7
#define CLK_F 1.0/16000000.0
8
9
//Der "Overflow-Counter" wird immer um eins erhöht, wenn der Timer einmal durchgelaufen ist
10
int ov_counter;
11
12
int rising,falling;
13
long ticks;
14
volatile float upTime;
15
16
float getPulseTime();
17
18
/**
19
 * Diese Methode erhöt die Counter variable immer dann, wenn der Timer einmal durchgelaufen ist
20
 * (via Interrupt)
21
 */
22
ISR(TIM1_OVF_vect)
23
{
24
  ov_counter ++;
25
}
26
27
 /**
28
  * Interrupt für den PIN
29
  */
30
ISR(INT0_vect){
31
  if ( (PINB & (1<<PINB2)) == 0) //Rising Edge
32
  {
33
    //Timer zurücksetzen
34
    TCNT1 = 0;
35
    //Counter variable zurücksetzten
36
    ov_counter = 0;
37
  }
38
  else //Falling Edge
39
  {
40
    //ticks berechnen 
41
    ticks = 256*ov_counter + TCNT1;
42
    //Zeit berechnen
43
    upTime = ((double) ticks ) * CLK_F;
44
  }
45
}
46
47
48
void setup() {
49
50
  //Pin 2 als Output
51
  DDRB |= (1<<DDB0);
52
  DDRB &= ~(1<<DDB2);
53
54
  PORTB |= (1<<PB2);
55
  PORTB &= ~(1<<PB0);
56
57
  //TimerCode
58
  TCCR1 &= ~((1<<COM1A1)|(1<<COM1A0) ); // Comperator A Mode Select
59
  TCCR1 &= ~((1<<COM1B1)|(1<<COM1B0));  //Comperator B output mode
60
61
  //Timer zählt von 0 - 255
62
  TCCR1 |= ((1<<CS13)|(1<<CS10));  
63
  TCCR1 &= ~((1<<CS11)|(1<<CS12));
64
65
  //Enable Timer Interrupt
66
  TIMSK |= (1<<TOIE1);
67
68
  //Enable External Interrupt
69
  GIMSK |=(1<<INT0);
70
  sei();
71
72
  //Interrupt bei rising and falling  
73
  MCUCR |=(1<<ISC00);
74
  MCUCR &= ~(1<<ISC01);
75
76
  TCNT1 = 0;
77
}
78
79
void loop() {
80
  // put your main code here, to run repeatedly:
81
  if(getPulseTime() < 0.0015)
82
  {
83
    PORTB |= (1<<PB0);
84
  } else if(getPulseTime () > 0.0015)
85
  {
86
    PORTB &= ~(1<<PB0);
87
  }
88
}
89
90
91
float getPulseTime(){
92
  return upTime;
93
}

Schon mal im Voraus danke!
LG

von Sebastian M. (meeresgott)


Lesenswert?

EDIT die Rote Leitung in der Skizze ist die Leitung, die für den PWM 
genutzt wird

von Sebastian M. (meeresgott)


Lesenswert?

Ich habe gesehen, dass ich einen float type einen double Wert zu weise, 
trotz Änderung des Codes bleib das Ergebnis unverändert. Es wird an Pin 
PB0 nur ein High ausgegeben.

von Eric B. (beric)


Lesenswert?

Irgendwie verstehe ich nicht ganz wie du die Pulsweite versuchst zu 
berechnen. Bei der steigende Flanke wird der Zähler zurückgesetzt. So 
weit so gut.
Dann läuft das ganze los und irgendwann kommt die fallende Flanke. Ich 
würde da einfach die jetztige Zählerstand speichern und abwarten bis die 
nächste steigende Flanke kommt. Bei der Flanke dann wieder Zählerstand 
auslesen; das Verhältniss zwischen den vorher gespeicherte Wert und der 
jetztige ergibt die Pulsbreite.
1
ISR(INT0_vect){
2
  if ( (PINB & (1<<PINB2)) == 0) // Rising Edge
3
  {
4
    int tmp = TCNT1;
5
6
    // Timer zurücksetzen
7
    TCNT1 = 0;
8
9
    // Counter variable zurücksetzten
10
    ov_counter = 0;
11
12
    // Pulsbreite berechnen -- Wertbereich [0..1)
13
    upTime = (double) ticks / (double) tmp;
14
  }
15
  else // Falling Edge
16
  {
17
    ticks = ov_counter;
18
  }
19
}

von Mario M. (thelonging)


Lesenswert?

Was mir auffällt: Du programmierst einen Vorteiler von 256, berechnest 
die "upTime" aber mit 16 MHz. Also probier mal #define CLK_F 
256.0/16000000.0

@Eric: Er will ja nicht das Verhältnis, sondern die H-Zeit.

von Sebastian M. (meeresgott)


Lesenswert?

@Eric Das was schon Mario gesagt hat. Die Frequenz ist mir eigentlich 
egal, solange T>2ms ist. Wichtig ist mir, dass ich die High Zeit 
bekomme. Nur die alten Servos, die noch mit einem Kondensator und einen 
OP den PWM in ein analoges 0v-5v Signal konvertiert haben, für die ist 
die Frequenz wichtig.

@Mario Es ist ja ein 8Bit Timer da dachte ich, es ist sinnvoll 265 
Schritte zu haben oder ist ein Vorteiler was anderes?
Wenn ich CLK_F mit  256.0/16000000.0 definiere bekomme ich bei jeder 
Hight-Time zwischen 1ms und 2ms ein LOW an PINB0.

Sebastian W. schrieb:
> if(getPulseTime() < 0.0015)
>   {
>     PORTB |= (1<<PB0);
>   } else if(getPulseTime () > 0.0015)
>   {
>     PORTB &= ~(1<<PB0);
>   }

Würde dann heißen, dass die UpTime jetzt immer größer ist als 0.0015.
Irgendwie habe ich das Gefühl nah dran zu sein aber irgendwas scheine 
ich zu übersehen. und es wäre nett, wenn du mir kurz erklärst was der 
Vorteiler macht - wie gesagt ich dachte dass ist die Schritt-Anzahl :o

von Eric B. (beric)


Lesenswert?

Mario M. schrieb:
> @Eric: Er will ja nicht das Verhältnis, sondern die H-Zeit.

Die ergibt sich doch aus der Pulsbreite? Ich sehe jetzt aber wie es 
funktionieren soll, hatte vorher wohl Tomaten auf den Augen.

Wenn möglich würde ich auf double/float Berechnungen in den 
Interrupt-routinen verzichten und die in die getPulseTime() Funktion 
verlagern. Das spart auch noch ein globale Variable ;-)
1
 
2
#define CLK_F (256.0/16000000.0)
3
4
volatile long ticks;
5
6
float getPulseTime(){
7
  return ((float) ticks) * CLK_F;
8
}

von Falk B. (falk)


Lesenswert?

Man nehme einen 16 Bit Timer und generiere damit die PWM rein in 
Hardware. Zur Not tut es auch ein 8 Bit Timer, aber dann ist die 
Auflösung mieserabel.
Floatberechungen braucht da kein Mensch, es reicht einfachste 
Festkommaarithmetik.

von Mario M. (thelonging)


Lesenswert?

Schau mal in den Einstellungen der Arduino-IDE, mit welchem Takt der 
ATTiny85 betrieben wird. Ich glaube, es sind sogar nur 8 MHz. Also wäre 
CLK_F 256.0/8000000.0

Der Vorteiler verringert den Takt für die Timer, teilt ihn durch einen 
einstellbaren Wert. In Deinem Fall durch 256.

Float braucht man für sowas nicht, aber es ist hier auch nicht die 
Fehlerursache.

@Falk: Es geht um PWM Eingabe.

von Sebastian M. (meeresgott)


Lesenswert?

So ich habe meinen Fehler gefunden!
@Mario Das Problem lag zum einem an diesem Vorteiler (256.0/f anstatt 
durch 1/f) - Macht es eigentlich in einer solchen Situation mehr Sinn 
einen großen oder einen kleinen Vorteiler zu nehmen?

Die Frequenz mit der ich den Attiny85 betreibe ist 16MHz hatte ich am 
Anfang so im von mir gebrannten Bootloader eingestellt - Esso schneller 
desto besser denke ich mir :)

Aber das zweite Problem ist, dieses if Statement

Sebastian W. schrieb:
> if ( (PINB & (1<<PINB2)) == 0) //Rising Edge

Das == 0 muss weg, da dieses if Statement nur dann wahr ist, wenn es 
eine Falling Edge ist.

Zusammengefasste Lösung:
#define CLK_F 1.0/16000000.0 durch #define CLK_F 256.0/16000000.0 
ersetzen
und
if ( (PINB & (1<<PINB2)) == 0) durch if ( (PINB & (1<<PINB2)))

Bliebe jetzt nur die Frage übrig, ob es sinnvoll ist einen anderen 
Vorteiler als 1 zu haben

von Arno (Gast)


Lesenswert?

Sebastian W. schrieb:
> @Mario Es ist ja ein 8Bit Timer da dachte ich, es ist sinnvoll 265
> Schritte zu haben oder ist ein Vorteiler was anderes?

Ja, ein Vorteiler ist etwas anderes: Mit Vorteiler läuft der Timer 
langsamer. Statt einem Zählschritt pro Takt macht er nur einen 
Zählschritt in soviel Takten wie der Vorteiler eingestellt ist. Wenn der 
Vorteiler 256 stimmt (ich hab das Datenblatt jetzt nicht zur Hand) dann 
zählt der Timer alle 256 Takte eins weiter. Also alle 256/16000000 
Sekunden.

Wenn der Controller wirklich mit 16 MHz läuft. Das wäre die nächste 
Frage... hast du die ganzen Taktregister und -fuses korrekt eingestellt? 
Die meisten AVRs laufen ab Werk mit 1MHz, ich weiß nicht, wie es beim 
Tiny85 ist. Tipp: Nutze das Makro F_CPU (auch den Wert musst du richtig 
setzen, aber nur einmal, nicht an ggf. irgendwann mal 20 Stellen im 
Programm).

Und: Bist du sicher, dass das Signal nicht prellt? Eventuell im Falling 
Edge-Codezweig abbrechen, wenn nicht mindestens zum Beispiel 0,5ms seit 
der Rising Edge vergangen sind?

Als nächstes würde ich in den Assembler-Code schauen, ob nicht evtl. 
auch ticks und ov_counter volatile sein sollten. Eigentlich nicht, denn 
die Interrupts werden ja nicht unterbrochen...

Und wenn das soweit funktioniert, dann wirst du immer noch manchmal 
komische Effekte am Ausgang haben, wenn zufällig der Falling Edge IRQ 
genau dann kommt, während getPulseTime() läuft. Dann werden nämlich 
einige Bytes des alten upTime-Werts mit den anderen Bytes des neuen 
upTime-Werts vermischt. Dazu solltest du - mit den Funktionen aus 
util/atomic.h - die IRQs in getPulseTime abschalten, upTime in eine 
temporäre Variable einlesen, IRQs wieder einschalten und dann upTime 
zurückgeben.

In dem Zuge solltest du wie von Eric vorgeschlagen auch die 
float-Rechnung aus der ISR herauslösen - das kostet unnötig viel Zeit 
und es besteht das Risiko, dass du Timer-Overflow-IRQs verlierst, weil 
mehrere eintreffen, während noch in der Input-IRQ eine Float-Berechnung 
machst. Ist hier noch egal, weil du die Overflows ja erst zählst, wenn 
wieder eine Rising Edge kam, wird dir bei anderen Projekten aber 
bestimmt irgendwann das Timing verhageln (frag nicht, woher ich das 
weiß).

MfG, Arno

von Arno (Gast)


Lesenswert?

Ah, ich war zu langsam...

Sebastian W. schrieb:
> Sebastian W. schrieb:
>> if ( (PINB & (1<<PINB2)) == 0) //Rising Edge
>
> Das == 0 muss weg, da dieses if Statement nur dann wahr ist, wenn es
> eine Falling Edge ist.

Typischer Code-Kommentar-Widerspruch. Ich hab nur den Kommentar 
wahrgenommen...

Sebastian W. schrieb:
> Bliebe jetzt nur die Frage übrig, ob es sinnvoll ist einen anderen
> Vorteiler als 1 zu haben

Kommt drauf an, welche Auflösung du brauchst und was der Controller 
sonst noch so machen soll.

Kleinerer Vorteiler -> Mehr Overflow IRQs -> Weniger Rechenzeit für 
andere Anwendungen und du musst schneller auf Overflow IRQs reagieren, 
sonst gehen welche verloren, musst also den Zeitbedarf deiner anderen 
ISRs und Codeteile, in denen IRQs deaktivert sind, kürzer halten. Bei 
Vorteiler 1 hast du alle 256 Takte einen Overflow-IRQ, die ISR dürfte 
ca. 30 Takte brauchen, du hast also "nur" 226/256 Takte Zeit für dein 
Hauptprogramm. Und alle anderen ISRs dürfen nicht länger als 225 Takte 
brauchen, ebenso wie Codeteile im Hauptprogramm, in denen IRQs 
deaktiviert sind.

Größerer Vorteiler -> Schlechtere Auflösung. Bei Vorteiler 1 entspricht 
ein Zeitschritt 1/f Sekunden, also bei 16MHz wirst du die Pulszeit in 
Schritten von 62.5ns messen können. Willst du das? Bei Vorteiler 1024 
wären es nur noch Schritte von 64µs, du kannst also nicht mehr zwischen 
1,00ms und 1,03ms unterscheiden.

MfG, Arno

von Mario M. (thelonging)


Lesenswert?

Bei einem Vorteiler von 1 läuft der Zähler pro Sekunde 62500 mal über 
und der Prozessor ist zu viel mit Interrupts beschäftigt. Bei einem 
Vorteiler von 256 kommt der Zähler für die 1-2 ms Impulszeit auf einen 
Wert zwischen 62 und 125. Du misst die 180° also mit 3° Auflösung, was 
ausreichend sein dürfte. Den Overflow-Interrupt würdest Du dabei gar 
nicht brauchen, höchstens als Timeout.

von Falk B. (falk)


Lesenswert?

@Mario M. (thelonging)


>@Falk: Es geht um PWM Eingabe.

Stimmt, mein Fehler, hab nicht bis zum Ende gelesen. Aber auch dafür 
gibt es bessere Methoden, hier Input Capture. Verbraucht wenig CPU-Zeit 
und ist sehr genau. Allerdings hat der ATtiny85 diese nicht.

von Sebastian M. (meeresgott)


Lesenswert?

Hier der finale Code mit der Umsetzung all eurer Vorschläge für 
jemanden, der vielleicht auch einmal ein PWM-Signal mit einem Attiny85 
einlesen möchte :D
1
#include <avr/io.h>
2
#include <avr/interrupt.h> 
3
#include <util/atomic.h>
4
5
#define VORTEILER 256.0
6
#define CLK_F VORTEILER/F_CPU
7
8
//Der "Overflow-Counter" wird immer um eins erhöht, wenn der Timer einmal durchgelaufen ist
9
//Hab ich jetzt auch einfach mal volatile gemacht - schadet bestimmt nicht 
10
volatile int ov_counter;
11
volatile long ticks;
12
13
float getPulseTime();
14
15
/**
16
 * Diese Methode erhöt die Counter variable immer dann, wenn der Timer einmal durchgelaufen ist
17
 * (via Interrupt)
18
 */
19
ISR(TIM1_OVF_vect)
20
{
21
  ov_counter ++;
22
}
23
24
 /**
25
  * Interrupt für den PIN
26
  */
27
ISR(INT0_vect){
28
  if ( (PINB & (1<<PINB2))) //Rising Edge
29
  {
30
    //Timer zurücksetzen
31
    TCNT1 = 0;
32
    //Counter variable zurücksetzten
33
    ov_counter = 0;
34
  }
35
  else //Falling Edge
36
  {
37
    //ticks berechnen 
38
    ticks = VORTEILER*ov_counter + TCNT1;
39
  }
40
}
41
42
43
void setup() {
44
  //Pin 2 als Output
45
  DDRB |= (1<<DDB0);
46
  DDRB &= ~(1<<DDB2);
47
48
  PORTB |= (1<<PB2);
49
  PORTB &= ~(1<<PB0);
50
51
  //TimerCode
52
  TCCR1 &= ~((1<<COM1A1)|(1<<COM1A0) ); // Comperator A Mode Select
53
  TCCR1 &= ~((1<<COM1B1)|(1<<COM1B0));  //Comperator B output mode
54
55
  //Timer zählt von 0 - 255
56
  TCCR1 |= ((1<<CS13)|(1<<CS10));  
57
  TCCR1 &= ~((1<<CS11)|(1<<CS12));
58
59
  //Enable Timer Interrupt
60
  TIMSK |= (1<<TOIE1);
61
62
  //Enable External Interrupt
63
  GIMSK |=(1<<INT0);
64
  sei();
65
66
  //Interrupt bei rising and falling  
67
  MCUCR |=(1<<ISC00);
68
  MCUCR &= ~(1<<ISC01);
69
70
  TCNT1 = 0;
71
}
72
73
void loop() {
74
  if(getPulseTime() < 0.0015)
75
  {
76
    PORTB |= (1<<PB0);
77
  } else if(getPulseTime () > 0.0015)
78
  {
79
    PORTB &= ~(1<<PB0);
80
  }
81
}
82
83
84
float getPulseTime(){
85
  float tmp;
86
  ATOMIC_BLOCK(ATOMIC_FORCEON)
87
  {
88
    tmp = ((float) ticks ) * CLK_F;
89
  }
90
  return tmp;
91
}

korrigert mich bitte, wenn ich ATOMIC_BLOCK falsch verwendet habe.
Welchen Vorteiler ich genau nehmen werde wird dann die Praxis zeigen 
aber so wie es sich jetzt anhört ist ein Vorteiler von 256 per Glück 
nicht schlecht gewählt - Ich danke allen für eure Hilfe und @Arno zwar 
zu spät aber ich hätte mich bestimmt irgendwann gewundert, dass warum 
die Zeit ab und zu spinnt auf jeden fall vielen dank für den hinweis!

von Mario M. (thelonging)


Lesenswert?

@Falk: Vielleicht braucht Sebastian die PWM-Ausgabe ja noch zur 
Ansteuerung des Motors, also "stay tuned". ;-)

von Sebastian M. (meeresgott)


Lesenswert?

Mario M. schrieb:
> @Falk: Vielleicht braucht Sebastian die PWM-Ausgabe ja noch zur
> Ansteuerung des Motors, also "stay tuned". ;-)

Hatte ich eigentlich nicht vor, da ein "normaler" Servo sowas auch nicht 
hat, aber jetzt wo du es ansprichst ... ;)

von Arno (Gast)


Lesenswert?

Sebastian W. schrieb:
> korrigert mich bitte, wenn ich ATOMIC_BLOCK falsch verwendet habe.
> Welchen Vorteiler ich genau nehmen werde wird dann die Praxis zeigen
> aber so wie es sich jetzt anhört ist ein Vorteiler von 256 per Glück
> nicht schlecht gewählt - Ich danke allen für eure Hilfe und @Arno zwar
> zu spät aber ich hätte mich bestimmt irgendwann gewundert, dass warum
> die Zeit ab und zu spinnt auf jeden fall vielen dank für den hinweis!

Du solltest die Zeit im Block möglichst kurz halten. Das bedeutet hier 
vor allem: Keine Berechnung, keine Umwandlung in float, sondern nur die 
vier Bytes kopieren, die du willst. Wenn du die einmal kopiert hast, 
kann sich ticks ja wieder ändern...

Sonst besteht wieder die Gefahr, dass dir Overflow-IRQs verloren gehen.

Sprich:
1
float getPulseTime(){
2
  long tmp;
3
  ATOMIC_BLOCK(ATOMIC_FORCEON)
4
  {
5
    tmp = ticks;
6
  }
7
  return ((float) tmp ) * CLK_F;;
8
}

MfG, Arno

von Falk B. (falk)


Lesenswert?

@Arno (Gast)

>Du solltest die Zeit im Block möglichst kurz halten. Das bedeutet hier
>vor allem: Keine Berechnung, keine Umwandlung in float,

Stimmt, ist aber immer noch Quark, dort mit Float zu arbeiten.

Und hat der OP mal grob gerechnet, wie gut seine Auflösung ist?

F_Timer = F_CPU / Prescaler = 8 MHz / 256 = 31,25kHz

Nmax = f_Timer * Tmax = 31,25kHz * 2ms = 62,5

D.h. hier kann mit knapp 6 Bit Auflösung die Pulsbreite gemessen werden. 
Naja. Da würde ich mal wenigstens den Vorteiler 64 nutzen, damit hat man 
die vierfache Auflösung und es reicht knapp ein 8 Bit Teiler. Wenn man 
es richtig machen will, nimmt man so oder so einen AVR mit 16 Bit Timer 
und ICP Funktion.

von Sebastian M. (meeresgott)


Lesenswert?

Falk B. schrieb:
> Stimmt, ist aber immer noch Quark, dort mit Float zu arbeiten.

Würdest du eher in ticks rechnen ?

Falk B. schrieb:
> F_Timer = F_CPU / Prescaler = 8 MHz / 256 = 31,25kHz

Ich arbeite mit 16Mhz also den Vorteiler 128 nutzen? Hieße ja dann auch, 
das ich mit 125 (2*62,5) knap eine 7Bit Auflösung habe oder nicht?

@Arno habe ich so übernommen Danke!

: Bearbeitet durch User
von Sebastian M. (meeresgott)


Lesenswert?

@Falk ich habe jetzt nochmal nachgerechnet - wieso habe ich eine 
Auflösung von 62,5 ? Ist das nicht eigentlich eine Auflösung von 62500? 
da du ja mit kHz rechnest ?

EDIT: Hat sich erledigt - sind ja ms

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ich lasse den Timer mit 1MHz laufen, damit habe ich 1µs Auflösung. Ohne 
irgendwelche Umwandlungen sinds dann Werte zwischen 1000 und 2000 für 
die Länge des Pulses:
Beitrag "Re: RC-Servoelektronik für DC-Motor"

Float kommt nicht vor.

von Arno (Gast)


Lesenswert?

Falk B. schrieb:
> @Arno (Gast)
>
>>Du solltest die Zeit im Block möglichst kurz halten. Das bedeutet hier
>>vor allem: Keine Berechnung, keine Umwandlung in float,
>
> Stimmt, ist aber immer noch Quark, dort mit Float zu arbeiten.

Ja, unnötig, aber außerhalb der ISRs und ATOMIC_BLOCKs auch unkritisch.

MfG, Arno

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.