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
intov_counter;
11
12
intrising,falling;
13
longticks;
14
volatilefloatupTime;
15
16
floatgetPulseTime();
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
voidsetup(){
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
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.
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.
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.
@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
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 ;-)
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.
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.
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
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
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
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.
@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.
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
volatileintov_counter;
11
volatilelongticks;
12
13
floatgetPulseTime();
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
voidsetup(){
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
voidloop(){
74
if(getPulseTime()<0.0015)
75
{
76
PORTB|=(1<<PB0);
77
}elseif(getPulseTime()>0.0015)
78
{
79
PORTB&=~(1<<PB0);
80
}
81
}
82
83
84
floatgetPulseTime(){
85
floattmp;
86
ATOMIC_BLOCK(ATOMIC_FORCEON)
87
{
88
tmp=((float)ticks)*CLK_F;
89
}
90
returntmp;
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!
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 ... ;)
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:
@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.
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!
@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
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.
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