Hallo,
ich möchte die Impulse eines Drehimpulsgebers mit einem PIC16F887
auswerten, und die Umdrehungen auf einer 7-Segment Anzeige ausgeben.
Ich habe volgenden Ansatz.
PIN A und PIN B des Drehgebers werden mit Interrupt Eingängen des µC
verbunden.
Sobald sich ein Pegel ändert wird ein Interrupt ausgelöst und der
aktuelle Pegel der PINs wird gespeichert.
Bei einem erneuten Interrupt wird der alte Pegel an den PINs mit dem
aktuellen verglichen.
Sobald der aktuelle Pegel von PIN B und der alte Pegel von PIN A gleich
sind soll der Zähler hochgezählt werden. Bei Ungleichheit reduziert.
Hier mein Programm:
1
#include <htc.h>
2
__CONFIG (LVP_OFF & WDTE_OFF); //Low Voltage Programming OFF
3
// Watch dog timer off
4
5
6
7
void interrupt IOC_ISR (void);
8
unsigned int count = 0;
9
unsigned int a_akt = 0; //Aktueller Status von PIN A
10
unsigned int b_akt = 0; //Aktueller Status von PIN B
11
unsigned int a_alt = 0; //Alter Status von PIN A
12
unsigned int b_alt = 0; //Alter Status von PIN B
13
unsigned int flag = 0;
14
15
void main (void)
16
{
17
OPTION_REG = 0b11000000; //Pull-up nicht geschalten (Externe Pulldowns)
18
//Interrupt on rising
19
INTCON = 0b10001001; //GIE =1
20
//RBIE=1
21
//RBIF=1
22
IOCB = 0b00110000; //Interrupt für RB4 und 5 freischalten
if (RBIF && RBIE && (flag == 0)) //Sobald Interrupt ausgelöst wird RB4 und RB5
68
{ //in Variable a_akt und b_akt einlesen
69
RBIF = 0;
70
PORTB = 0xf0;
71
if (RB4 || RB5)
72
{
73
a_akt = RB4;
74
b_akt = RB5;
75
flag = 1;
76
}
77
}
78
79
if (RBIF && RBIE && flag) //Soabld Interrupt ausgelöst wird und bereits
80
{ //die Variable a_akt bzw. b_akt beschrieben wurde
81
RBIF = 0; //diese in a_alt und b_alt kopieren
82
flag = 0;
83
PORTB= 0xf0;
84
if (RB4 || RB5)
85
{
86
a_alt = a_akt;
87
b_alt = b_akt;
88
89
90
if (b_akt == a_alt) //wenn b_akt gleich a_alt zähler um 1 erhöhen
91
{
92
count++;
93
}
94
95
if (b_akt != a_alt) //wenn b_akt ungleich a_alt zähler um 1 reduzieren
96
{
97
count--;
98
}
99
}
100
}
101
}
Das Problem ist, dass der Zähler anscheinend nicht hochgezählt wird. Ich
kann so viel drehen wie ich will, die Anzeige bleibt immer bei 00.
Die Signale vom Drehgeber, welchen ich übrigens Hardwareseitig entprellt
habe kommen am µC auch an.
Ich hoffe Ihr könnt mir weiterhelfen. Bin hier echt am verzweifeln.
Gruß
Max
max2d schrieb:> Ich hoffe Ihr könnt mir weiterhelfen. Bin hier echt am verzweifeln.
'Verzweifeln' ist der falsche Ansatz und der Fehlersuche nicht
zuträglich.
'Systematisches Vorgehen' wäre richtig.
Du hast offenbar irgendwas am PortD hängen, mit dem sich dein Programm
bemerkbar machen kann. Ein paar LED oder ev. sogar eine 7-Segment
Anzeige.
Das ist schon mal die halbe Miete und das wichtigste überhaupt. Denn es
ist nicht verboten, diese Teile erst mal anders zu verwenden, wie du dir
das im Endeffekt vorgestellt hast. Also schmeiss die Ausgabe aus der
Hauptschleife raus (mach dir aber eine Funktion um Zahlen auszugeben)
und benutzt diese Ausgabemöglichkeit um damit zuallererst mal
festzustellen:
* wird der Interrupt überhaupt ausgelöst.
Im Hauptprogramm schreibst du '0' auf die Anzeige (ausserhalb der while
Schleife) und in der ISR lässt du dann '1' ausgeben.
Startest du das Programm, musst du die 0 sehen. Drehst du am Encoder
(und nur dann), dann muss die Anzeige auf 1 wechseln.
Damit weißt du schon mal, dass die Interrupt Funktion aufgerufen wird.
Etwas das du vorher nicht mit Sicherheit wusstest.
Bzw. es könnte auch anders rum sein, dass dein Interrupt nicht ausgelöst
wird oder zu früh ausgelöst oder wie auch immer. Das alles kannst du mit
Hilfe der Anzeige feststellen. Anstatt "Ich glaube das das alles
funktioniert" bist du dann bei "Ich WEISS, dass das alles funktioniert /
nicht funktioniert".
Und so hangelst du dich weiter durch.
Wenn du sicher bist, dass die Interrupt Funktion aufgerufen wird, dann
kann man die Anzeige zb dazu benutzen, sich mal den Status der diversen
Variablen bzw. Flags ausgeben zu lassen.
Dann verzweifelst du auch nicht mehr sondern betreibst systematische
Fehlersuche, in dem du in deinem Programm alle Schritte verfolgst und
kontrollierst, ob jeweils das erwartete auch tatsächlich passiert.
Und dann wirds auch was mit Debuggging und wenn deine Idee tragfähig ist
dann funktioniert es am Ende auch, nachdem du alle Problemstellen aus
dem Weg geräumt hast.
Vielen Dank für deine schnelle und so ausführliche Antwort.
Ich habe einen Fehler gefunden.
Sobald ich in der ISR neben RBIF und RBIE den Zustand der flag abfrage,
wird der Interrupt nichtmehr gehandhabt. Die flag hat aber den richtigen
Zustand.
Hier das Programm:
1
#include <htc.h>
2
__CONFIG (LVP_OFF & WDTE_OFF); //Low Voltage Programming OFF
3
// Watch dog timer off
4
5
6
7
void interrupt IOC_ISR (void);
8
void anzeige (void);
9
unsigned int count = 0;
10
unsigned int a_akt = 0; //Aktueller Status von PIN A
11
unsigned int b_akt = 0; //Aktueller Status von PIN B
12
unsigned int a_alt = 0; //Alter Status von PIN A
13
unsigned int b_alt = 0; //Alter Status von PIN B
14
unsigned int flag = 0;
15
16
17
void main (void)
18
{
19
OPTION_REG = 0b11000000; //Pull-up nicht geschalten (Externe Pulldowns)
20
//Interrupt on rising
21
INTCON = 0b10001001; //GIE =1
22
//RBIE=1
23
//RBIF=1
24
IOCB = 0b00110000; //Interrupt für RB4 und 5 freischalten
Der PinChange Interrupt ist bei Drehencodern schonmal sehr schlecht,
benutz lieber einen Timer und frag die Eingänge periodisch ab.
Hier meine Routine zum Auslesen eines Drehencoders an PIND2 und PIND3,
die gegen Masse geschaltet werden.
1
volatilestruct{
2
uint8_tlast;
3
int8_tsteps;
4
}encoder;
5
6
ISR(TIMER0_OVF_vect)// 600x/sec
7
{
8
// Drehencoder auswerten
9
uint8_tP1=(PIND&(1<<PD2)),P2=(PIND&(1<<PD3));
10
if(P1!=encoder.last)
11
{
12
if((!encoder.last&&!P2)||(encoder.last&&P2))
13
{
14
encoder.steps+=1;
15
}
16
elseif((encoder.last&&!P2)||(!encoder.last&&P2))
17
{
18
encoder.steps-=1;
19
}
20
encoder.last=P1;
21
}
22
}
23
24
uint8_tEncoder_GetSteps(void)
25
{
26
cli();
27
uint8_ttmp=encoder.steps;
28
encoder.steps=0;
29
sei();
30
returntmp;
31
}
Ich hab keine Probleme mit der Prellung, auch bei schnellen rotieren.
Ich benutze den Encoder für die Menubedienung über ein LCD.
max2d schrieb:> Die Signale vom Drehgeber, welchen ich übrigens Hardwareseitig entprellt> habe kommen am µC auch an.
Das könntest du dir natürlich sparen wenn du den Drehgeber ordentlich
auswerten würdest und nicht mit unsäglichen Flankeninterrupts arbeiten
würdest.
http://www.dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29
>Mein Drehgeber liefert 4096 Flanken/U. Du meinst, dass da 600x/sec
<reichen?
>:-)
Du solltest wissen ob das reicht, oder ob die Abtastfrequenz höher sein
muß.
Drehzahl, Anzahl der Flanken pro Umdehung und schon kannst Du die
Frequenz der Drehgeberpulse berechnen.
Meinen ist immer der ganz falsche Ansatz, wissen mußt Du es.
Ist die Frequenz hoch, so kanns irgendwann mit der Interruptlösung (by
the way, die ist nur Mist) auch eng werden. Hardwaremäßige Entprellung
hat auch eine Grenzfrequenz...
Schau doch mal bei extremen Drehgeberfrequenzen nach PICs mit
Zweipahsen-Eingang. Die haben eine leistungsfähige Hardware auf dem
Chip... und Du hast dann keine Sorgen mit der Frequenz.
spontan schrieb:> Drehzahl, Anzahl der Flanken pro Umdehung und schon kannst Du die> Frequenz der Drehgeberpulse berechnen.
Gut, ich habe mal gerechnet: 30kHz brauche ich.
max2d schrieb:> Sobald sich ein Pegel ändert wird ein Interrupt ausgelöst und der> aktuelle Pegel der PINs wird gespeichert.
Das machst Du genau richtig!
polling schrieb:> Popcorn, Füße hoch. Der Einzelkämpfer ist wieder unterwegs :-)
Sagt ja sonst keiner was Vernüftiges, außer altbekannten Vorurteilen :-)
m.n. schrieb:> Das machst Du genau richtig!
Sagt der Troll, der seit Monaten den groben Unfug der völlig falschen
flankeninterruptgesteuerten Drehgeberauswertung hier propagiert.
Die Folgen sieht man an max2d:
Es funktioniert nicht.
m.n. schrieb:> Sagt ja sonst keiner was Vernüftiges, außer altbekannten Vorurteilen :-)
Manchmal kommen die Vorurteile auch von Erfahrungen, die man gemacht hat
und nun vermeidet...
MaWin schrieb:> Die Folgen sieht man an max2d:>> Es funktioniert nicht.
Das liegt aber daran, dass er einen PIC anstatt eines AVR verwendet.
@max2d
Warte noch ein wenig, bis heute Abend W.S. in seiner hoffentlich
geheizten Dachstube bei einer Flasche "Rote Toscana" sitzt und Dir dann
genau erklären wird, wie man es mit Interrupts auf einem PIC zum Laufen
bekommt.
Die jetzigen Lästerer werden sich dann neidisch wieder in ihre Ecken
verkriechen - bis der nächste Frager kommt :-)
m.n. schrieb:> Die jetzigen Lästerer werden sich dann neidisch wieder in ihre Ecken> verkriechen
worauf soll ich da neidisch sein?
Möglich das er da in seiner Syntax für den Portzugriff einen Fehler hat.
Kann durchaus sein. Auch fehlt mindestens 1 volatile. Aber selbst wenn
das mit externen Interrupts bei ihm funktioniert, bin ich deswegen nicht
neidisch. Bis jetzt bin ich mit den Ratschlägen alter Bekannter immer
gut gefahren, egal ob Tastenauswertung oder Drehencoder. Die
funktionieren auf Anhieb und sind robust, ohne dass ich mich groß drum
kümmern muss oder externe Hardware, wie irgendwelche
Entprellkondensatoren, brauchen würde.
Karl Heinz schrieb:> worauf soll ich da neidisch sein?
Dass andere Leute Sachen in den Griff bekommen, wozu sie selbst (die
Lästerer) nicht in der Lage sind.
Karl Heinz schrieb:> Bis jetzt bin ich mit den Ratschlägen alter Bekannter immer> gut gefahren, egal ob Tastenauswertung oder Drehencoder.
Du hattest ja schon einmal geschrieben, dass Du mich nicht kennst und
meine Programme daher nichts taugen würden. Du machst Dir nicht einmal
die Mühe, zu verstehen, welche Schaltungsdetails für welche Aufgabe
sinnvoll oder auch garnicht notwendig sind. Ein Sonnenbad der
Vorurteile!
Letztlich ist dies aber ein Armutszeugnis, was Du Dir damit ausstellst.
Zeig mir doch irgendeines der erprobten Programme, dass
Drehgeber/Lineargeber mit 30kHz auswerten könnte?
Aber verkriech Dich bitte nicht hinter der Standardausrede: soetwas
braucht man nicht.
2 Argumente im
http://www.mikrocontroller.net/articles/Drehgeber>[...] 1.Die Auflösung wird auf ein Viertel reduziert, weil nur jede >steigende
Flanke von A ausgewertet wird.
Falsch, PINChange reagiert auf jede Änderung. Somit ist's möglich, jede
Flanke zu bewerten
> 2.Pendelt der Encoder zwischen zwei Codes, bei denen A seinen Pegel>wechselt...
Man kann per SW unterdrücken. M.N hat der Anfang/ Ansatz mitgebracht,
ihr wolltet davon aber nicht wissen. Ich hab's längst gemacht, es geht
mit jedem Encoder AUF ANHIEB! mit oder ohne Kondensator.
euch noch einen schönen Feierabend.
Tany schrieb:> Man kann per SW unterdrücken. M.N hat der Anfang/ Ansatz mitgebracht,> ihr wolltet davon aber nicht wissen. Ich hab's längst gemacht, es geht> mit jedem Encoder AUF ANHIEB! mit oder ohne Kondensator.
Warum sollte man denn etwas per Software halbgar unterdrücken oder
"zurecht"frickeln, wenn es doch bereits eine solide Lösung gibt, welche
ohne Tricks auskommt?
René K. schrieb:> Warum sollte man denn etwas per Software halbgar unterdrücken oder> "zurecht"frickeln, wenn es doch bereits eine solide Lösung gibt, welche> ohne Tricks auskommt?
was erwartest du denn von jemandem, der nur Teile von Artikeln zitiert,
damit es sie als falsch bezeichnen kann?
m.n. schrieb:> Zeig mir doch irgendeines der erprobten Programme, dass> Drehgeber/Lineargeber mit 30kHz auswerten könnte?
Beispielsweise die
http://www.dse-faq.elektronik-kompendium.de/dse-faq.htm#F.29
auch 100kHz oder in Assembler 1MHz oder in Hardware 10MHz.
Nur Interruptflankengesteuerten haben mit hohen Abtastraten Probleme,
weil entweder Interrupts auftreten während Interrupts noch bearbeitet
werden, also verloren gehen oder aufgeschoben werden, und Überläufe im
Gegensatz zur pegelgesteuerten nicht erkannt werden
und weil die notwendige Entprellung, beispielsweise per RC Glied, auch
eine niedrige Endfrequenz ergibt.
Aber das wirst du in deinem Messias Flanken Wahn nie begreifen.
Danke für eure Antworten.
Da sich die Mehrheit dafür ausgesprochen hat das ganze mit einem Timer
Interrupt zu realisieren habe ich es mal probiert. Leider funktionierts
nicht.
Warum? Der Timer funktioniert (habe ich getestet )
1
#include <htc.h>
2
__CONFIG (LVP_OFF & WDTE_OFF); //Low Voltage Programming OFF
3
// Watch dog timer off
4
5
void anzeige (void);
6
7
unsigned int count = 0;
8
unsigned int flag = 0;
9
unsigned int a_akt = 0;
10
unsigned int b_akt = 0;
11
unsigned int a_alt = 0;
12
unsigned int b_alt = 0;
13
unsigned int a_zw = 0;
14
unsigned int b_zw = 0;
15
16
void main (void)
17
{
18
19
T1CON = 0b01110001;
20
GIE = 1;
21
PEIE = 1;
22
TMR1IE = 1;
23
TMR1L = 200;
24
TMR1H = 200;
25
26
TRISD = 0;
27
28
TRISB1= 1;
29
TRISB0 = 1;
30
ANSELH= 0;
31
32
while (1)
33
{
34
anzeige ();
35
}
36
}
37
38
39
void anzeige (void)
40
{
41
if (count == 0) //7-Segment: 00
42
{
43
PORTD = 0b00000000;
44
}
45
46
if (count == 1) //7-Sement: 11
47
{
48
PORTD = 0b00010001;
49
}
50
51
if (count ==2)
52
{
53
PORTD = 0b00100010;
54
}
55
56
if (count == 3)
57
{
58
PORTD = 0b00110011;
59
}
60
61
if (count == 4)
62
{
63
PORTD = 0b01000100;
64
}
65
66
if (count == 5)
67
{
68
PORTD = 0b01010101;
69
}
70
}
71
72
73
void interrupt timer (void)
74
{
75
if (TMR1IF)
76
{
77
TMR1L = 200;
78
TMR1H = 200;
79
TMR1IF= 0;
80
81
82
a_zw = RB0; //Aktuellen Stand des Drehgebers zwischenspeichern
83
b_zw = RB1; // "
84
85
if (a_zw != a_akt) //Sobald im zwischenspeicher ein anderer Wert als im a_akt speicher
86
{ //vorhanden ist. Wird zuerst a_akt in a_alt verschoben. Danach wird neuer Wert
87
a_alt = a_akt; //von a_zw in a_akt eingelesen
88
a_akt = a_zw;
89
flag = 1;
90
//count = 2;
91
}
92
93
if (b_zw != b_akt) //Gleiches Prinzip wie oben, nur für b
94
{
95
b_alt = b_akt;
96
b_akt = b_zw;
97
flag = 1;
98
//count = 3;
99
}
100
101
102
103
104
if (b_akt == a_alt) //wenn b_akt gleich a_alt zähler um 1 erhöhen
105
{
106
if (flag)
107
{
108
count++;
109
flag = 0;
110
}
111
}
112
113
if (b_akt != a_alt) //wenn b_akt ungleich a_alt zähler um 1 reduzieren
>Warum sollte man denn etwas per Software halbgar unterdrücken oder>"zurecht"frickeln, wenn es doch bereits eine solide Lösung gibt, welche>ohne Tricks auskommt?
Hätte gern eine persönliche Antwort von dir, warum was untenstehende
nicht funktionieren würde bzw. nicht solide wäre.
Ich kann mit beiden Variante umgehen /umsetzen. Bin kein Fanatiker im
Gegensatz zu manchem hier im Forum.
PhaseA:
ldi temp2,(0<<int_PhaseA)|(1<<int_PhaseB)
sts PCMSK2, temp2
sbrc enc_LastState,PhaseB
rjmp vor
sbrs enc_LastState,PhaseA
rjmp HL
dec puffer
rjmp end_PhaseA
HL:
inc puffer
rjmp end_phaseA
vor:
sbrs enc_LastState,PhaseA
rjmp LH
inc puffer
rjmp end_phaseA
LH:
dec puffer
end_PhaseA:
save puffer
ret
PhaseB:
ldi temp2,(1<<int_PhaseA)|(0<<int_PhaseB)
sts PCMSK2, temp2
ret
> Nun steht dort RB0 und RB1 nach RB4 und RB5, ist aber genau so falsch.
Was ist denn daran falsch? Ich frage die PINs RB0 und RB1 immer bei
einem Timer Interrupt ab, so wie ihr es vorgeschlagen habt.
> Warum nimmst du nicht den viel kürzeren code aus der dse faq?
Den Code verstehe ich nicht.
MaWin schrieb:>> Welchen Sinn hat ein Forum, wenn dich die Antworten nicht interessierrn?
Das Stimmt doch garnicht. Ihr habt zu mir gesagt ich soll nicht über die
Flankeninterrupts auswerten, sondern über einen Timer.
Was ich jetzt ja mache!
Tut mir leid, aber mit euren Programmen kann ich nichts anfangen. Die
sind total anders. Und dann auch noch ohne Kommentare
ich kenne deinen Controller nicht, aber ich lese folgendes aus den
bisherigen Antworten:
mit
a_zw = RB0; //Aktuellen Stand des Drehgebers
zwischenspeichern
bekommst du nicht den aktuellen Stand des Drehgebers, sondern nur eine
Konstante. Richtig abfragen müsstest du es mit
a_zw = PORTBbits.RB0;
Steht wie gesagt alles schon in den Antworten oben, du musst dich nur
auf deren Inhalte konzentrieren statt auf deren Tonfall ;-)
Achim S. schrieb:>> a_zw = RB0; //Aktuellen Stand des Drehgebers> zwischenspeichern>> bekommst du nicht den aktuellen Stand des Drehgebers, sondern nur eine> Konstante. Richtig abfragen müsstest du es mit>> a_zw = PORTBbits.RB0;>
Stimmt nicht, es reicht RB0. Funktioniert einwandfrei zumindest in einer
IF abfrage if (RB0) reicht also vollkommen.
max2d schrieb:> Stimmt nicht, es reicht RB0.
wenn dem so ist, dann hättest du das vielleicht gleich nach Stampedes
Beitrag klarstellen sollen.
Stampede schrieb:> MaWin schrieb:>> RB4 und RB5 sind doch Konstanten und nicht die Pins des Ports, oder ist>> das beim PIC anders?>> So ist es. PORTBbits.RB4 wäre korrekt.
Dann hättest du dich nicht darüber ärgern müssen, dass MaWin dich
mehrmals auf den selben, vermeintlichen Fehler hinweist.
Ob Stampede recht hat oder du kann ich nicht beurteilen: wie oben
beschrieben (und in deinem Zitat dann weggelassen) weiß ich nicht, wie
bei deinem Controller der Zugriff auf IO-Ports abläuft.
max2d schrieb:> Ich war mir anfangs mit der PORT abfrage selber nicht so sicher.
bin ich mir auch jetzt noch nicht. Wenn du mir dein define für RB0
zeigst, könnte ich mich wahrscheinlich entscheiden, ob ich Stampedes
oder deiner Schreibweise glaube.
max2d schrieb:> Ich glaube eher, dass es ein logisches Problem ist.
Stimmt, logische Probleme hat dein Code auch noch zu genüge ;-) Du
vergleichst z.B. den aktuellen Wert von Kanal A mit dem vorherigen Wert
von Kanal B. Richtig wäre, dass du die beiden Bits von KanalA und KanalB
in einen State zusammenfasst und vergleichst, ob ein Übergang von einem
State zu einem anderen stattgefunden hat.
max2d schrieb:> Tut mir leid, aber mit euren Programmen kann ich nichts anfangen.
Ich gebe dir recht, dass die Codebeispiele hier und bei dse-faq auf
Effizienz ausgelegt sind, und nicht auf einfache Verständlichkeit.
Deshalb hab ich dir zuliebe mal einen extra ausführlichen und extra
wohlkommentierten Code zusammengetippt. Wenn du anhand dieses Codes die
Idee besser verstehst, dann kannst du dich vielleicht auch mit den
anderen (effizienteren) Code-Beispielen anfreunden.
Bis zur endgültigen Klärung verwendet mein Code die Syntax von Stampede
für die Port-Abfrage ;-)
1
charaktstate;//aktueller Wert der beiden Kanäle
2
charlaststate;//Wert der beiden Kanäle bei der letzten Abfrage
3
intcounter;//Zähler für Position
4
5
6
//im Timer-Interrupt aufrufen
7
aktstate=0;
8
if(PORTBbits.RB0)aktstate|=1;//speichere aktuellen Wert von Kanal A in Bit 0 von Aktstate
9
if(PORTBbits.RB1)aktstate|=2;//speichere aktuellen Wert von Kanal B in Bit 1 von Aktstate
10
11
if(aktstate!=laststate){//falls es eine Änderung gab
12
//entscheide je nach letztem Zustand, in welche Richtung die Änderung lief
13
switch(laststate){
14
case0:
15
if(aktstate==1)counter++;//Übergang von 00 -> 01
16
elseif(aktstate==2)counter--;//Übergang von 00 -> 10
17
break;
18
case1:
19
if(aktstate==3)counter++;//Übergang von 01 -> 11
20
elseif(aktstate==0)counter--;//Übergang von 01 -> 00
21
break;
22
case2:
23
if(aktstate==0)counter++;//Übergang von 10 -> 00
24
elseif(aktstate==3)counter--;//Übergang von 10 -> 11
25
break;
26
case3:
27
if(aktstate==2)counter++;//Übergang von 11 -> 10
28
elseif(aktstate==1)counter--;//Übergang von 11 -> 01
29
break;
30
}
31
laststate=aktstate;//übernimm neuen Zustand als laststate
Paul Baumann schrieb:> selbst für einen> "Nicht-C-Programmierer"
zu der Spezies zähle ich mich selbst eigentlich auch ;-)
(Beim Eintippen hatte ich zuerst diverse Brocken VHDL "beigemischt",
weil mir das sehr viel präsenter ist als C-Syntax.)
Aber es freut mich, wenn der Code zum Verständnis hilft...
Achim S. schrieb:> Ich gebe dir recht, dass die Codebeispiele hier und bei dse-faq auf> Effizienz ausgelegt sind, und nicht auf einfache Verständlichkeit.
Nachfolgender Code ist effizient und kommentiert, was doch kein
Gegensatz sein muß.
temp = alter_zustand ^ neuer_zustand; // aenderungen testen
alter_zustand = neuer_zustand; // fuer naechsten Aufruf
if(temp & BIT(PHASE_A)) { // bei Aenderung von PHASE_A
if(neuer_zustand == 0 || neuer_zustand == MASKE)
temp = 1;
else
temp = -1;
} else { // bei Aenderung von PHASE_B
if(neuer_zustand == BIT(PHASE_A) || neuer_zustand == BIT(PHASE_B))
temp = 1;
else
temp = -1;
}
count += temp; // Zaehler anpassen
Der Aufruf dieses Codes erfolgt per PCINT und ist in Gänze hier zu
sehen: Beitrag "Schrittmotor als Drehgeber mit Dynamik, AVR"
Die Routine macht eine 4-fach Flankenauswertung, wie dies üblich ist.
Also, ich habe deinen Code mal auf meinen µC gespielt. Wenn ich am
Drehgeber drehe zählt er manchmal richtig hoch.
Manchmal kann ich drehen, und es passiert garnichts.
Und manchmal Springt er mehrere Werte bei nur einer Umdrehung hoch.
Was genau ist denn jetzt "dein Code"? Der von m.n. oder der von mir?
Nur für den Fall, dass es sich um meinen Code handeln sollte:
- hast du sicher gestellt ob PORTBbits.RB0 oder einfach RB0 richtig ist?
Welche Variante verwendest du jetzt und wie sieht nochmal dein define
für RB0 aus?
- in welchen Zeitabständen wird dein Timerinterrupt aufgerufen?
Vermutest du aufgrund der Programmierung, dass die Wiederholrate deines
Timer-Interrupts richtig ist oder hast du das mal mit einem toggelnden
Pin nachgemessen?
Achim S. schrieb:> Was genau ist denn jetzt "dein Code"? Der von m.n. oder der von mir?
Deiner (Achim S.)
> Nur für den Fall, dass es sich um meinen Code handeln sollte:> - hast du sicher gestellt ob PORTBbits.RB0 oder einfach RB0 richtig ist?
Ja, hab beides ausprobiert. Funktioniert beides.
> Welche Variante verwendest du jetzt und wie sieht nochmal dein define> für RB0 aus?
Ich habe keinen define für RB0. Ich setze nur den Pin als Ausgang und
kann dann den Port mit == abfragen oder mit = beschreiben.
> - in welchen Zeitabständen wird dein Timerinterrupt aufgerufen?
Das ist eine gute Frage. Ich habe den timer1 verwendet und die beiden
Register mit 260 vorgeladen.
> Vermutest du aufgrund der Programmierung, dass die Wiederholrate deines> Timer-Interrupts richtig ist oder hast du das mal mit einem toggelnden> Pin nachgemessen?
Das Timerinterrupt muss passen, habe damit mal PINs abgefragt und dann
auf der 7-Segment ausgeben lassen
max2d schrieb:>> Was genau ist denn jetzt "dein Code"? Der von m.n. oder der von mir?>> Deiner (Achim S.)
na, das ist ja bitter ;-)
Kannst du uns mal deine konkrete timer-ISR zeigen und ggf. die
Deklaration der Variablen und die Ausgabe der Ergebnisse auf den Port?
(nicht dass vielleicht irgendwo ein volatile fehlt...)
max2d schrieb:>> - hast du sicher gestellt ob PORTBbits.RB0 oder einfach RB0 richtig ist?>> Ja, hab beides ausprobiert. Funktioniert beides.
es wundert mich, dass beides das selbe Ergebnis liefern soll. Das define
für RB0 dürfte in irgendeiner header-Datei stecken,die pic16f887.h oder
ähnlich heißt. Kannst du mal in deinem Projekt nach der Datei suchen?
Oder kennt deine Entwicklungsumgebung ein "Find in Files", so dass du
mal eben schnell nach jedem Auftreten von RB0 suchen kannst?
max2d schrieb:>> - in welchen Zeitabständen wird dein Timerinterrupt aufgerufen?> Das ist eine gute Frage. Ich habe den timer1 verwendet und die beiden> Register mit 260 vorgeladen.
Tja, wäre das mein Lieblingscontroller und würde ich deinen
Initialisierungscode kennen, dann wüsste ich jetzt wie schnell der Timer
läuft. Da beides nicht der Fall ist schlage ich mal vor: toggle einfach
in der Timer-ISR einen Ausgang und schau nach, mit welcher Frequenz der
hin und her wackelt. Am betsen gehts mit nem Oszi. Wenn nicht vorhanden
dann vielleicht mit einem Multimeter, das Frequenzen messen kann.
PS: in der großen weiten Welt des Internet habe ich grade folgende Seite
gefunden:
http://blog.irwellsprings.com/getting-started-with-pic-microcontrollers-the-header-file/
Darin steht im Abschnitt "The main header file" wie du zu den relevanten
header-files findest, in denen die Infos zu deb IO-Ports definiert sind
(zumindest falls du die selbe Entwicklungsumgebung benutzt):
"Let’s examine this in detail, and follow the header file “trail” to
find out how it does this. To do this, highlight <xc.h> right click and
select Navigate->Go to Declaration"
__CONFIG (LVP_OFF & WDTE_OFF); //Low Voltage Programming OFF
3
// Watch dog timer off
4
5
6
7
8
void anzeige (void);
9
10
11
char aktstate;
12
char laststate;
13
int counter;
14
15
void main (void)
16
{
17
18
T1CON = 0b01000001;
19
GIE = 1;
20
PEIE = 1;
21
TMR1IE = 1;
22
TMR1L = 200;
23
TMR1H = 200;
24
25
TRISD = 0;
26
27
TRISB1= 1;
28
TRISB0 = 1;
29
ANSELH= 0;
30
31
while (1)
32
{
33
anzeige ();
34
}
35
}
36
37
38
void anzeige (void)
39
{
40
if (counter == 0) //7-Segment: 00
41
{
42
PORTD = 0b00000000;
43
}
44
45
if (counter == 1) //7-Sement: 11
46
{
47
PORTD = 0b00010001;
48
}
49
50
if (counter ==2)
51
{
52
PORTD = 0b00100010;
53
}
54
55
if (counter == 3)
56
{
57
PORTD = 0b00110011;
58
}
59
60
if (counter == 4)
61
{
62
PORTD = 0b01000100;
63
}
64
65
if (counter == 5)
66
{
67
PORTD = 0b01010101;
68
}
69
}
70
71
72
void interrupt timer (void)
73
{
74
if (TMR1IF)
75
{
76
TMR1L = 260;
77
TMR1H = 260;
78
TMR1IF= 0;
79
aktstate = 0;
80
81
82
83
if (RB0)
84
{
85
aktstate |= 1;
86
}
87
if (RB1)
88
{
89
aktstate |= 2;
90
}
91
92
if (aktstate != laststate)
93
{
94
switch (laststate)
95
{
96
case 0:
97
if (aktstate == 1) counter++;
98
else if (aktstate == 2) counter--;
99
break;
100
case 1:
101
if (aktstate == 3) counter++;
102
else if (aktstate == 0) counter--;
103
break;
104
case 2:
105
if (aktstate == 0) counter++;
106
else if (aktstate == 3) counter--;
107
break;
108
case 3:
109
if (aktstate == 2) counter++;
110
else if (aktstate ==1) counter--;
111
break;
112
113
}
114
laststate = aktstate;
115
}
116
}
117
}
Ich habe auch mal in meiner Libary (htc.h)nachgeschaut ob ich was mit
den PORTs finde. Leider sind in der htc.h noch soooo viele Verweise
drin, dass ich da ne weile dran sitzen würde.
Die Interrupt Frequenz vom Timer kann ich mit meinem Oszi nicht mehr
richtig messen. Ich sehe aber dass sich was tut.
mache mal Counter volatile
volatile int counter;
und die Anzeige sollte nur einmal auf Counter zugreifen, also vorher in
eine neue Variabel kopieren oder gleich ein Switch verwenden:
void anzeige (void)
{
int tmp = Counter;
if (tmp == 0) //7-Segment: 00
{
...
Peter II hats schon geschrieben: counter muss volatile deklariert
werden, sonst kriegt "anzeige" gar nicht mit, wenn sich in der
Interruptroutine etwas an "counter" geändert hat.
Außerdem zeigst du nur die counter-Werte 0..5 an. Wenn aus irgend
welchen Gründen der Counter ins negative läuft (z.B. weil die Kanäle
anders angeschlossen sind, als du denkst), kriegst du von allen weiteren
Änderungen nichts mehr mit. Deshalb: greif den Vorschlag von Peter II
auf, aber mach ein
temp = counter & 7;
daraus und zeige auf der Siebensegement alle temp-Werte von 0-7 an. Dann
kannst du keine Änderungen mehr verpassen.
max2d schrieb:> Die Interrupt Frequenz vom Timer kann ich mit meinem Oszi nicht mehr> richtig messen.
Was heißt das: zu schnell oder zu langsam für die Messung mit dem Oszi?
Ich weiß schon, dass das lästig und anfangs anstrengend ist, aber du
musst einen Weg finden, den Counter auf eine definierte Überlauffrequenz
einzustellen. Dass "sich da was tut" reicht nicht. Arbeite dich durchs
Datenblatt oder suche dir eine leichter verdauliche Tutoriumsseite bis
du es schaffst, den Timer auf ~1kHz einzstellen.
Und schon mal ein Tip vorneweg:
TMR1L = 260;
TMR1H = 260;
ist ziemlich sicher Unsinn, weil jedes dieser Register nur 8 Bit groß
sein dürfte (also maximal den Wert 255 aufnehmen kann).
Also, ich habe counter jetzt als volatile deklariert, und tmp = counter
&7 gesetzt.
Den Timer konnte ich jetzt so einstellen, dass er mit einer Frequenz von
ca. 2kHz läuft.
Wenn ich den Timer einen PIN toggeln lasse, liegt das Signal
komischerweise nur wenige Sekunden an. Nach ca. 8 Sekunden beginnt er
wieder zu toggeln. warum?!
na prima, das ist doch schon mal ein Fortschritt.
max2d schrieb:> Wenn ich den Timer einen PIN toggeln lasse, liegt das Signal> komischerweise nur wenige Sekunden an. Nach ca. 8 Sekunden beginnt er> wieder zu toggeln. warum?!
Das klingt in der Tat seltsam. Der Grund dürfte entweder in deiner
Hardware oder deiner Software oder in deiner Messung liegen ;-)
Denkbare Kandidaten wären:
- irgendwas mit dem Watchdog (obwohl dein Codeschnipsel nahelegt, dass
der nicht aktiv ist)
- irgendwas in deinem aktuellen Code führt dazu, dass sich das Programm
nach einiger Zeit aufhängt
- ein externes Problem (vielleicht schaltet dein Netzteil immer mal
wieder die Versorgung weg, vielleicht bekommt der Controller keinen
stabilen Takt oder der Takt ist zu schnell, ...)
- ein nicht korrekt beschalteter Reset-Pin, der durch die Gegend floatet
und ab und zu den Controller in reset hält
- vielleicht ist es auch nur ein Messartefakt (könnte vorkommen, wenn du
das Oszi z.B. im Roll-Mode mit einer niedrigen Abtastrate betreibst, so
dass es zufällig manchmal nur "zwischen den Pulsen" misst und du die
Pulse nicht erkennst)
Das sind mögliche Gründe, die mir spontan einfallen, es gibt sicher auch
noch andere Möglichkeiten. Aber arbeite erst mal daran, dass dein
Controller und dein Timer-Interrupt stabil laufen, danach gehts dann
weiter mit der Auswertung des Drehgebers...
Den Watchdog Timer habe ich deaktiviert.
Am Netzteil liegt es glaube ich weniger, ist so das teuerste was ich
hier gefunden habe.
Den Reset PIN habe ich auch richtig beschalten.
Ich glaube es muss am Code selber liegen. An der Quarz Frequenz liegt es
glaube ich nicht. Ich habe den internen Quarz mal auf die niedrigste
Frequenz gestellt, mit dem gleichen Ergebniss.
Hier mal mein Code:
1
#include <htc.h>
2
__CONFIG (LVP_OFF & WDTE_OFF); //Low Voltage Programming OFF
Hallo,
in der Timer-ISR toggelst du PortD (geht das überhaupt mit einem
kompletten Port?) aber in der main spielst du ebenso an PortD herum.
Damit kriegst du keine ordentliche Messung hin - lass einen anderen
Ausgangspin auf einem anderen Port, als Debug-Pin toggeln.
edit:
Habe das Problem gefunden. Ich hatte das T1CON Register falsch
beschrieben.
Nun habe ich es so beschrieben: T1CON = 0b00000001;
Wenn ich den Drehgeber jetzt ganz langsam um nur eine Raste nach rechts
oder links drehe kann ich sehen wie die 7-Segment Anzeige von 0 auf 5
hochzählt, allerdings in nur einer! umdrehung.
Kann es sein, dass ich Prelltechnische Probleme hab?
Ich habe die externe Entprellung nichtmer angeschlossen.
max2d schrieb:> Nun habe ich es so beschrieben: T1CON = 0b00000001;
Und was steuert das eine Bit, das du jetzt geändert hast?
max2d schrieb:> kann ich sehen wie die 7-Segment Anzeige von 0 auf 5> hochzählt, allerdings in nur einer! umdrehung.
Bei einer vollen Umdrehung (360°) oder bei einer neuen Rastung (z.B.
30°).
Wie viele Pulse soll dir dein Drehgeber denn bei einer Umdrehung
liefern? Auch einfache Drehgeber haben typisch 16 oder mehr Positionen
pro vollständiger Umdrehung. Es kann auch sein, dass du pro Rastung
schon 4 Positionen erhältst.
Wenn du eine geringere Auflösung willst, dann ignoriere einfach die
unteren Bits von Counter
temp = (counter >> 1) & 7;
Jetzt hat sich hier lange nichts mehr getan, ich war im Urlaub.
Sobald ich den Drehgeber um nur eine Raste drehe springt die Anzeige auf
55.
Jetzt habe ich mit tmp = (counter >>)&7; hinbekommen, dass bei einer
Raste die Anzeige nur um +1 erhöht wird.
Nun habe ich aber das Problem, dass meine Anzeige garnicht bis 9 kommt.
Woran könnte das liegen?
max2d schrieb:> tmp = (counter >>)&7;
...
> Nun habe ich aber das Problem, dass meine Anzeige garnicht bis 9 kommt.> Woran könnte das liegen?
Hm. Mal nachdenken...
Oliver
max2d schrieb:> Nun habe ich aber das Problem, dass meine Anzeige garnicht bis 9 kommt.> Woran könnte das liegen?
Na ja, lass uns noch mal schauen, warum diese Zeile eingeführt wurde:
Achim S. schrieb:> Außerdem zeigst du nur die counter-Werte 0..5 an. Wenn aus irgend> welchen Gründen der Counter ins negative läuft (z.B. weil die Kanäle> anders angeschlossen sind, als du denkst), kriegst du von allen weiteren> Änderungen nichts mehr mit. Deshalb: greif den Vorschlag von Peter II> auf, aber mach ein>> temp = counter & 7;>> daraus und zeige auf der Siebensegement alle temp-Werte von 0-7 an. Dann> kannst du keine Änderungen mehr verpassen.
Erklärt das deine Beobachtung?
max2d schrieb:> ich habe keine Ahnung was der Befehl macht :-D
den findest du in jedem Grundlagenbuch zu C erklärt. Wenn du ernshaft
programmieren willst, wirst du um das Lesen eines solchen Buchs nicht
herum kommen.
max2d schrieb:> Wie heißt denn dieser Befehl?
Wenn du das hier meinst:
> sry heißt:> tmp = (counter >> 2) &7;
Das ist eine Bitverschiebung um zwei Bits nach rechts und dann eine
UND-Verknüpfung. Findest du das wirklich nirgends?