Hallo zusammen,
ich benötige Hilfe bei der Auswertung eines Tachosignals.
Das Tachosignal wird von einem Anemometer erzeugt, und soll ausgewertet
werden.
Unten habe ich das Programm mal eingefügt. Ich verwende einen ATMega32,
AVR Studio 4 und den GCC.
Ich möchte das ab einer Windgeschwindkeit von ca. 50km/h der Ausgang
PORTD1 aus "high" gesetzt wird und unter 50km/h auf "Low" gesetzt wird.
Das Programm scheint auch zu Funktionieren, jedoch ist das Zählen der
Tachoimpulse leider sehr ungenau. Ich vermute mal, dass der Kontakt in
dem Anemometer sehr stark prellt. Nur so kann ich mir erklären warum bei
nur 3 Umdrehungen des Anemomenters der PORTD1 auf "high" geht.
Ich habe zum testen das Tachosignal im Programm gleich der
Windgeschwindigkeit gesetzt, da ich das Anemomenter selber drehe. So
sollte doch erst ab 10 Drehungen der Ausgang auf "high" gehen.
Versuche ich den Kontakt durch Wartezeiten zu entprellen, habe ich das
Problem, dass bei höheren Drehzahlen diese nicht mehr richtig erfasst
werden.
Wie kann ich vorgehen? Was kann ich tun um dieses Problem in den Griff
zubekommen.
Ist das Programm ggf. noch zu optimieren?
Ich bedanke mich für euere Hilfe und verbleibe
mit freundlichen Grüßen
Balou Baer
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
#include<C:\AVR\warten.h>
6
//#include <avr/bit.h> // Wird noch nicht benötigt.
7
#include<C:\AVR\lcd-pollin-jumper.h> // Am Pollin Board feur
8
// das LCD nur die Jumper DB4-DB7, R/W,E,RS stecken
9
10
volatileuint8_ta=0;
11
12
13
ISR(TIMER1_COMPA_vect)
14
{
15
a=1;
16
}
17
18
intmain(void)
19
20
{
21
uint8_tb=15;
22
uint16_tTachosignal=0,Windgeschwindigkeit=0;
23
24
DDRA=0b11110001;//Datenleitung Display die ersten 4 (0b1111XXXX)
25
DDRB=0b00000111;//Steuerleitung Display die letzten 3 (0bXXXXX111)
26
DDRC=0b00000000;//Konfigurieren der Ein und Ausgänge an Pord C; 0=Eingang, 1=Ausgang
27
DDRD=0b00001110;//Konfiguration der Ein und Ausgänge an Pord D; 0=Eingang, 1=Ausgang
PORTD=(1<<PD2);//Kontrolle ob Tachosignal erkannt LED2 = an
9
};
grundsätzlich erst mal eine Flankenerkennung rein machen!
Dich interessiert nur, wie oft der Pin von 0 auf 1 geht und nicht wie
oft ihn das Programm als auf 1 stehend vorgefunden hat. Denn der Pin
kann auch eine halbe Stunde lang auf 1 stehen. Deswegen willst du diese
Zeiten aber nicht zählen, was dein Programm aber munter tut.
Flanken, und damit Pulse erkennt man durch einen vorher-nachher
Vergleich. Wenn du kurz hintereinander auf das zu überwachende Signal
schaust und vorher war es auf 0 und jetzt ist es auf 1, dann ist da
offenbar ein Puls reingekommen
1
uint8_tvorher;
2
uint8_tjetzt;
3
4
...
5
6
intmain()
7
{
8
9
....
10
11
vorher=(PIND&(1<<PD0));
12
13
while(1){
14
15
jetzt=(PIND&(1<<PDO));
16
17
// Eine Flanke kann nur vorliegen, wenn sich 'jetzt' von 'vorher'
18
// unterscheidet
19
if(jetzt!=vorher){
20
21
// wir wollen aber nicht jede Flanke haben, sondern nur
22
// die, an denen das Signal von 0 auf 1 wechselt
23
// d.h. 'jetzt' darf nicht 0 sein, sondern muss ein 1 Bit
24
// aufweisen.
25
26
if(jetzt){
27
zaehler++;
28
29
....
30
}
31
}
32
}
und schreib nicht so viel Code auf einmal. DIe ganze Auswertung und
Zeitsteuerung ist jetzt noch uninteressant. Lass von mir aus eine LED
toggeln, wenn eine Flanke detektiert wird. Das ist einfach und schnell
implementiert. Wenn du das Rad langsam mit der Hand drehst, muss die LED
dann dauernd den Zustand wechseln. Dann sieht man schon, ob die Kontakte
prellen oder nicht.
Danke dir!
Der Gedanke ist mir noch nicht gekommen mit den Flanken. Habe leider nur
ein begrenztes Wissen von der C und uC Programmierung. Ich versuche mich
da nun etwas rein zu arbeiten. Grucken ob es mir gelingt ;)
Sind das „harte“ Flanken (von Elektronik erzeugt) oder „klapprige“
(von einem mechanischen Kontakt)? Für den erstgenannten Fall würde
sich wohl auf jeden Fall eine Zeitmessung per input capture direkt
durch die Timer-Hardware eignen. Im zweiten Fall kann man das auch
damit machen, muss aber zum Entprellen noch etwas mehr Aufwand
treiben.
jetzt bin ich komplett verwirrt ??????????
Sorry, aber ich weiß nicht wie das Anemometer das Signal erzeugt, ob es
mit einem Reedkontakt oder mit einem mechanischen Schalter versehen
wurde.
Aus Mechanischersicht, um so wenig Reibung wie möglich zu verursachen,
würde ich einen Reedkontakt nehmen. Das Anemometer ist leider nicht
aufschraubbar, daher keine Aussage möglich.
Vielleicht noch ein Wort zur Beschaltung.
VCC = 5V
Eingang Anemometer an +5V (VCC)
Ausgang Anemometer an PinD0 und über Pulldown Widerstand (15kOhm) an
Ground). Bei jeder Drehung kommt an PIND0 +5V kurz an.
Messgerät:
Würth Basic 71553400
Wäre es unverschämt, wenn ich fragen würde ob ihr mir für die Codezeilen
etwas stärker unter die Arme greifen könntet. Ich bin, sagen wir es mal
so, blutiger Anfänger in C und weiß nicht weiter.
Ich bedanke mich für euere Mühen und verbleibe
mit freundlichen Grüßen
Balou Baer
Der Code ist jetzt noch nicht auf Funktion getestet:
Balou Baer schrieb:> Das Anemometer ist leider nicht> aufschraubbar, daher keine Aussage möglich.
Aber das Signal kann man sich mal mit einem Oszi anschauen, ob es sauber
ist oder Prellungen macht. Wenn du keinen hast, kannst du vielleicht mal
jemanden fragen, der einen besitzt.
Abgesehen davon ist es trotzdem richtig, mit Flankenerkennung und
Software-Entprellung zu arbeiten. Nur um die Sache grob einschätzen zu
können, wäre es schon vorteilhaft, wenn man das Aussehen des
Eingangssignals kennt.
Balou Baer schrieb:> Wäre es unverschämt, wenn ich fragen würde ob ihr mir für die Codezeilen> etwas stärker unter die Arme greifen könntet.
und ich schreib ihm auch noch die wesentlichen Codezeilen hin (auch wenn
ich erst mal einen Fehler gemacht hab)
> Ich bin, sagen wir es mal so, blutiger Anfänger in C und weiß nicht weiter.
Lass ich nicht gelten.
Das man nicht von alleine auf die Idee kommt, kann ich akzeptieren. Aber
das man 5 Zeilen Code studiert und sich überlegt, wie und warum die
arbeiten, das bleibt dir nicht erspart. Da müssen alle durch. Musste ich
auch mal.
Programmieren ist nun mal ca 15% Beherrschung der Programmiersprache und
85% Algorithmen (sprich: Verfahren).
nicht mehr durchblickst (um ehrlich zu sein bräuchte ich da auch ein
wenig Zeit, bis ich die exakte Logik dahinter raus hab), dann mach dir
halt Hilfsvariablen. So wie ich das gezeigt habe. 2 Variablen, eine
'vorher', eine 'jetzt' und verfeinfache den Code durch die
Hilfsvariablen. Da ist doch nichts dabei und von Atmel kriegst du kein
Geld zurück, wenn du das SRAM nicht 'abnutzt'.
Und ich sag auch noch: fang erst mal einfacher an
Und mach auch noch einen Vorschlag dazu.
Ich mach den Vorschlag doch nicht aus Lust und Laune.
Was ist so schwer an.
und dann drehst du mal am Anemometer und siehst nach, ob und wann die
LED umschaltet und ob es da zu Fehlern kommt (wegen Prellen) und ob du
am Eingang einen Pullup Widerstand brauchst oder nicht.
Wer gleich zu Anfang zu viel will, der fällt auf die Nase. Das Um und
Auf bei der SW Entwicklung ist es, in Schritten vorzugehen. Erst mal mit
einer einfach Variante anfangen! Komplizierter wird es dann ohnehin von
alleine. Aber erst mal will man so schnell wie möglich was einfaches
haben, das man testen kann und das als Ausgangspunkt für den nächsten
Schritt dient.
Karl Heinz,
Ich wollte doch nicht kränken oder beleidigen. Ich bin nur nicht schlau
geworden aus deinen Teilen und habe mich dann, mit Hilfe der
Suchfunktion durchs Forum gerobbt. Suchbegriff Flankenerkennung.
Ich verstehe bei deiner ersten Antwort nicht, warum die Variable
“vorher“ vor der Schleife steht, damit ist die Variable fest und ändert
sich für meine Begriffe nicht mehr. Daher war für mich die Aussage
jetzt!= vorher immer wahr, wenn ich in der Schleife mehrmals den Pin
Abfrage. Egal ob er zwischendurch gesetzt oder nicht gesetzt war.
@npn:
Sorry besitze ich nicht und kenne leider auch keinen der eins haben
könnte. Das Anemometer ist von der Funkwetterstation PCE-FWS 20.
Gruß Balou Baer
Balou Baer schrieb:> Ich verstehe bei deiner ersten Antwort nicht, warum die Variable> “vorher“ vor der Schleife steht,
damit beim ersten Vergleich ein gültiger Zustand vorliegt. Sonst kann es
dir passieren, dass du vorher mit 'Pin liegt auf 0' annimmst und bei der
ersten Abfrage sofort auf einen Puls schliesst, weil der Pin in
Wirklichkeit die ganze Zeit auf 1 liegt.
> damit ist die Variable fest und ändert> sich für meine Begriffe nicht mehr.
Ich hab eine Ergänzung angebracht! Nicht gesehen?
Natürlich ändert sich 'vorher'. WEnn ein UNterschied festgestellt wird,
dann wird vorher auf den neuen WErt gesetzt. Man könnte das auch am ENde
der Schleife einfach immer machen, aber im Grunde ist es nur notwendig,
wenn man das erste mal einen Unterschied feststellt.
> Daher war für mich die Aussage> jetzt!= vorher immer wahr, wenn ich in der Schleife mehrmals den Pin> Abfrage.
Nein, denn durch
1
if(jetzt!=vorher){
2
vorher=jetzt;
3
4
....
wird ja der neue Zustand gemerkt, damit im nächsten Schleifendurchlauf
dann festgestellt wird, das der Pin zb immer noch auf 1 liegt und daher
keine Flanke vorliegen kann. Erst wenn der Pin wieder auf 0 zurück geht,
gibt es wieder einen Unterschied. Den merkt man sich und im
darauffolgenden Durchlauf sind dann beide wieder gleich, nämlich 0,
woran man erkennen kann das jetzt wieder keine Flanke da gewesen sein
kann.
Und so geht das immer dahin.
Hallo Karl Heinz,
jetzt habe ich das Verstanden mit dem "Vorher" vor der Whileschleife.
Habe gerade den Code getestet und toggeln tut er die LED ohne Probleme.
Jetzt habe ich noch einen Zähler ergänzt, diesen in eine IF Abfrage
gepackt und!!!!!! WAS!!!!! SOLL!!!!! ICH!!!!!! SAGEN!!!!
;) ;D HURA!!! egal ab ich schnell dreh oder langsam drehe!!!!!!!!! Er
zählt genau!!!!
Wenn das jetzt noch in meinem Programm so läuft!
KLASSE!!!!! und DANNNNKKKEEEEEEEE!!!!!!
Balou Baer
Hallo zu sammen, ich hoffe ich darf noch einmal stören.
anbei nun mein fertiger Code. Er funktioniert und die Kontroll LEDs sind
auch raus. Also so zu sagen von unnötigem Ballast befreit. Aber
vielleicht habt ihr ja noch eine Idee der Optimierung für diesen Code um
ihn kompakter oder evtl. "schneller" zumachen.
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
#define F_CPU 16000000UL
5
6
volatileuint8_ta=0;
7
uint8_tvorherwind,jetztwind,b=15;
8
doubleWindgeschwindigkeit=0,Tachosignal=0;
9
10
11
ISR(TIMER1_COMPA_vect)
12
{
13
a=1;//Wenn Timer abgelaufen wird a=1 gesetzt.
14
}
15
16
intmain(void)
17
18
{
19
DDRD=0b00000010;//Konfiguration der Ein und Ausgänge an Pord D; 0=Eingang, 1=Ausgang
Balou Baer schrieb:> vielleicht habt ihr ja noch eine Idee der Optimierung für diesen Code um> ihn kompakter oder evtl. "schneller" zumachen.
Da deine Windgeschwindigkeit nur dazu benutzt wird, um eine LED zu
schalten und der Zahlenwert an sich ja nirgends auf einem Display
auftaucht, verschaffst du hier
1
Tachosignal=Tachosignal*4;//Tachosignale hochgerechnet pro Minute
2
Tachosignal=Tachosignal*60;//Tachosignal hochgerechnet pro Stunde
3
Windgeschwindigkeit=Tachosignal*0.00094;//Tachosignale mit Umfang des Windmessers zu Windgeschwindigkeit
4
5
if(Windgeschwindigkeit>=50)//Vergleichszahl ist Windgeschwindigkeit in km/h
deinem µC eine schöne Fleissaufgabe.
Denn anstelle den µC jedesmal die Anzahl der Pulse in eine
Geschwindigkeit umrechnen zu lassen, könntest du dir auch einmalig mit
dem Taschenrechner ausrechnen, wieviele Pulse in deiner Messzeit bei
50km/h erwartet werden. Wenn du das ein wenig geschickt machst, dann
kann das sogar der Compiler für dich ausrechnen.
Nebeneffekt: die ganze Rechnerei und damit vor allem die ganze Floating
Point Rechnerei fällt für den µC zur Laufzeit weg.
Aber im Grunde wird das ziemlich egal sein. Denn dein µC hat sowieso
massig Zeit.
Anderes Thema. Anstelle der b-Schleife in der Hauptschleife, könntest du
auch a einfach weiter zählen lassen. als nur bis 1.
So ungefähr
Man darf in einer ISR durchaus ein klein wenig mehr machen, als nur eine
Variable auf 1 setzen. Das 'In einer ISR nichts machen' sollst du nicht
als Dogma sehen. Gemeint ist damit 'nichts was extrem lange dauert, wie
zb Ausgaben auf ein LCD oder eine UART'. Ein bischen rechnen ist schon
ok und im Endeffekt vereinfacht sich dadurch dann an anderen Stellen im
Programm die Logik oft enorm.
PS: Benenn deine Variablen vernünftig. Was an 'Windgeschwindigkeit'
etwas zu lang ist, das ist an 'a' bzw. 'b' zu kurz. Derartige
ein-buchstaben Variablen solltest du dir für Hilfsvariablen aufheben,
wie sie zb in for-Schleifen oft vorkommen. Aber sobald etwas eine vitale
Information enthält, hat es sich auch einen ordentlichen Namen verdient.
hier zb
1
b--;//Anzahl der durchzulaufenden Druchgänge wird im 1 verringert
der Kommentar ist schön und gut. Aber: das hier etwas um 1 verringert
wird, das sehe ich auch im Code. Nur steht im Code 'b' und der Kommentar
klärt mich darüber auf, dass das die Durchgänge sind. Dann nenn die
Variable doch gleich Durchgaenge, dann steht da
1
Durchgaenge--;
und der Kommentar wird komplett überflüssig, weil alles was mir der
Kommentar erzählt gleich direkt im Code steht.
Der Kommentar hier
1
b=15;// Variable B wird wieder auf 15 gesetzt.
der ist sowieso komplett überflüssig. Wen njemand nicht sieht, dass hier
die Variable b auf 15 gesetzt wird, dann soll er sich eine Brille
kaufen. Erzähl mir nicht im Kommentar, wie etwas gemacht wird. Das seh
ich im Code. Erzähl mir WARUM etwas gemacht wird. Zb würde ich gerne
wissen, warum ausgerechnet 15. Warum nicht 14 oder 16 oder 87? Aber
darüber schweigt der Kommentar. Dafür erzählt er mir etwas, was ich
sowieso schon weiß, wenn ich nur den Code lese.
Das zb
1
Tachosignal=Tachosignal*4;//Tachosignale hochgerechnet pro Minute
2
Tachosignal=Tachosignal*60;//Tachosignal hochgerechnet pro Stunde
ist ein guter Kommentar. Der erzählt mir, WARUM da mit 4 multipliziert
wird. Wenn du dann noch die C typischen Zusammenfassungen benutzt, dann
kann auch ein Blinder greifen, dass es einzig und alleine nur um das
Tachosignal geht, das braucht dann auch im Kommentar nicht mehr erwähnt
werden.
1
Tachosignal*=4;// -> Minute
2
Tachosignal*=60;// -> Stunde
(man will sich schliesslich ja auch nicht zu Tode tippen und das was du
dir an Tipparbeit ersparst kannst du ruhig in ein paar Leerzeichen
investieren. Die machen das Lesen des Codes leichter. Schliesslich ist
dein Gehirn seit Kindheit darauf trainiert, dass zwischen Wörtern ein
Leerraum ist. Das solltest du ausnutzen und nicht alles in einer
Buchstabenwurscht dahinschreiben.)
Also: Gestalte deinen Code so, dass der Code sein eigener Kommentar
wird. Ordentliche und gut gewählte Variablennamen sind da der erste
Schritt. Was dann noch an Kommentierung übrig bleibt, soll sich um das
WARUM drehen und nicht um das WIE.
Hallo Karl Heinz,
Ich werde mir deine Ratschläge zu Herzen nehmen. Was du sagst mit den
Variablen und Kommentaren macht Sinn.
Auch dein Ansatz mit dem Tachosignal und der ISR Routine. Nur wenn ich
die Zeit (1 Sekunde) noch für andere Sachen benötigen würde..... wobei,
dann setzt man halt eine weiter Variable in die ISR. Unser Lehrer, bei
dem wir uC programmieren lernen, sagt “ISRs immer!!! so kurz wie möglich
halten.“, den wenn der uC (hier jetzt gerade nicht) Zeitkitische Sachen
macht, kann das böse Folgen für eine Sache haben. Darum sollen wir uns
angewöhnen, immer nur einen Merker zu setzen, wenn die ISR nicht
kritisch ist, und diese später auswerten.
Ob ich noch Zeitkitische Sachen für die geplante Steuerung programmieren
muß, weiß ich nicht, da noch einige Sensoren und Aktoren, sowie der IIC
Bus fehlen/noch nicht programmiert sind. Auch der Ausgang von der
Geschwindigkeitsmessung wird nicht nur eine LED ein und aus schalten
;-). Ich programmiere den uC auf einem Experimentierboard und nutze die
LEDs nur zur optischen Darstellung bis die eigentliche Platine fertig
erstellt ist ;-).
Ich befürchte nur, das ich euch noch öfters für mein kleines privates
Übungsprojekt um Hilfe bitten muss.
Von daher bis später :-P.
Mit freundlichen Grüßen
Balou Baer
Hallo Karl Heinz,
ich würde deine Hilfe jetzt noch einmal benötigen, sorry.
Ich versuche gerade mit Hilfe eines Interrups (Int0) eine Regenmenge zu
erfassen. Dieses Signal wird ebenfalls über ein Tachosignal mittelt. Der
Interrups wird erkannt, aber die Ausgänge werden nicht geschaltet, woran
kann das liegen. Falsche Logik von mir?
Den Interrupt INT0 habe ich so konfiguriert, dass er bei einer fallenden
Ansprechen soll, das Funktioniert auch soweit ganz gut und die Kontroll
LED in der ISR Toggelt auch.
Nur die eigentlichen Abfragen scheinen nicht zu funktionieren. Daher nun
der Code aufgesplittet mit Komentaren meiner "Logik".
Hier stelle ich mir die Variablen ein und definiere mir die Interrups
usw.:
1
#define PULSEREGEN_LIMIT 2 // <- Hier die gewünschte Regenmenge eingeben
2
3
#define TIMEREGEN 10 // <- Hier Zeit zur Messung der Regenmenge eingeben.
GICR=0b11000000;//Freigeben der Externen Interrups
40
TCCR1B…….
41
42
sei();
43
44
//Zeit für Regen:
45
timeregen=TIMEREGEN;
46
47
…..
48
49
While(1)
50
{
51
//Ermittlung Windgeschwindigkeit
52
……
Ab hier steht der Code in einem durch siehe ganz Unten.
Hier habe ich mir gedacht:
Wenn es geregnet hat (pulseregen = X), frage ich den Timer ab und die
Anzahl der Pulse. Ist die Zeit abgelaufen UND die Anzahl der Pulse
GRÖßER oder GLEICH dem Pulse_Limit, sollen halt die Ausgänge geschaltet
werden.
1
if((timeregen==0)&(pulseregen>=PULSEREGEN_LIMIT))//Abfrage der Pulse
2
{
3
//Einschalten der Ausgänge
4
5
PORTD=(1<<PD4);//Ausgang zum Haupt uC an PIND3 INT1
6
PORTB&=^(1<<PB1);//Anzeige LED
7
8
pulseregen=0;//Zurücksetzen der Werte
9
timeregen=TIMEREGEN;//Zurücksetzen der Werte
10
}
Ist das nicht der Fall, die Zeit ist abgelaufen UND die Anzahl der Pulse
ist kleiner als das Limit, sollen die Ausgänge wieder ausgeschaltet
werden.
1
if((timeregen==0)&(pulseregen<<PULSEREGEN_LIMIT))
2
{
3
//Ausschalten der Ausgänge
4
5
PORTD&=~(1<<PD4);//Ausgang zum Haupt uC an PIND3 INT1
6
PORTB=(1<<PB1);//Anzeige LED
7
8
pulseregen=0;//Zurücksetzen der Werte
9
timeregen=TIMEREGEN;//Zurücksetzen der Werte
10
}
11
12
13
}
Macht aber der uC leider nicht so, wie ich mir das gedacht habe. Es wird
kein Ausgang gesetzt. Habe ich einen Denkfehler? Oder geht das so nicht?
Ich bedanke mich für die Hilfe und verbleibe
Mit freundlichen Grüßen
Balou Baer
1
// Ermittlung der Regenmenge
2
3
if((timeregen==0)&(pulseregen>=PULSEREGEN_LIMIT))//Abfrage der Pulse
4
{
5
//Einschalten der Ausgänge
6
7
PORTD=(1<<PD4);//Ausgang zum Haupt uC an PIND3 INT1
8
PORTB&=~(1<<PB1);//Anzeige LED
9
10
pulseregen=0;//Zurücksetzen der Werte
11
timeregen=TIMEREGEN;//Zurücksetzen der Werte
12
}
13
14
if((timeregen==0)&(pulseregen<<PULSEREGEN_LIMIT))
15
{
16
//Ausschalten der Ausgänge
17
18
PORTD&=~(1<<PD4);//Ausgang zum Haupt uC an PIND3 INT1
Balou Baer schrieb:> Macht aber der uC leider nicht so, wie ich mir das gedacht habe. Es wird> kein Ausgang gesetzt. Habe ich einen Denkfehler?
1)
volatile
2)
1
if((timeregen==0)&(pulseregen<<PULSEREGEN_LIMIT))
Was denkst du, was das '<<' hier bedeutet? "viel kleiner"?
(das '&' ist hier eigentlich auch falsch, funktioniert aber)
@ Stefan,
ich danke dir für die Info, dennoch läuft es nicht.
Was mir nach einigen weiteren Versuchen jetzt aufgefallen ist. Das Der
Timer nicht gestartet wird. Nehme ich die Zeile
1
MCUCR=0b00000011;
2
GICR=0b11000000;
raus. Läuft zwar der Timer wieder, aber der Interrupt an PIND3 wird
nicht mehr erkannt, weil der uC ja nicht mehr weiß, dass dieser PIN als
ein Interrupt verwendet werden soll.
Was mache ich falsch wenn ich das hier schreibe:
Balou Baer schrieb:> dann setzt man halt eine weiter Variable in die ISR. Unser Lehrer, bei> dem wir uC programmieren lernen, sagt “ISRs immer!!! so kurz wie möglich> halten.“,
Das ist aber auch kein Dogma.
Ein paar Variablen addieren, vergleichen oder dergleichen, das dauert
alles nur wenige Taktzyklen. Eine Uhr würde man so machen
1
ISR(....)
2
{
3
milliSekunden++;
4
if(milliSekunden==1000){
5
Sekunden++;
6
if(Sekunden==60){
7
Sekunden=0;
8
Minuten++;
9
if(Minuten==60){
10
Stunden++;
11
if(Stunden==24)
12
Stunden=0;
13
}
14
}
15
}
16
}
d.h. da kommmt die komplette Uhren-Logik in die ISR hinein. Auch wenn
das im C Code recht lang aussieht, in Assembler sind das nicht viele
Taktzyklen. Und letzten Endes ist es wichtig, dass die Uhr dann auch
wirklich jeden ISR Aufruf korrekt zählt und die Uhrzeit konsistent
hochgezählt wird. Die Alternative
1
ISR(...)
2
{
3
countClock=1;
4
}
5
6
intmain()
7
{
8
...
9
10
while(1){
11
12
CodeA
13
14
if(countClock){
15
countClock=0;
16
17
milliSekunden++;
18
if(milliSekunden==1000){
19
Sekunden++;
20
....
21
22
}
23
24
CodeB
25
}
26
}
krankt dran, dass es dir passieren kann, dass je nachdem, was alles an
den Stellen 'Code A' bzw. 'Code B' passiert, es dir passieren kann, dass
ein paar milliSekunden 'übersehen' werden, weil die Codestellen A oder B
zu lange dauern. Für eine Uhr ist das unbrauchbar.
Solange die INterrupts eingeschaltet sind und der Timer läuft, wird die
ISR aufgerufen. Und zwar zuverläsig und als Folge davon wird auch die
Zeit zuverlässig gezählt - da gibt es keine sich summierenden vergessene
Millisekunden.
D.h. ISR kurz halten ist ok. Aber auch nicht zu kurz. Generell in der
Informatik: sobald du anfängst Dinge dogmatisch zu sehen, gehst du den
falschen Weg. Man muss die Dinge verstehen und nicht einfach nur
auswendig gelerntes von sich geben. Du tust in einer ISR das was es zu
tun gibt, wobei du 'lang dauernde Dinge' rausnimmst. Was im konkreten
'lang dauernd' bedeutet, hängt von der Aufgabenstellung ab. Aber wenn
dir 10 oder 15 Taktzyklen in einer ISR zu viel sind, dann sollte man mal
in die Richtung überlegen, ob man nicht generell mit dem falschen µC
unterwegs ist. Bei 16Mhz sind 10 Taktzyklen weniger als 1µs (millionstel
Sekunde)! Es gibt die Anwendungen, bei denen es wirklich auf jede µs
ankommt. Zb bei der Generierung von Videosignalen. Aber ob ein Relais 1
oder 2µs früher oder später schaltet, spielt überhaupt keine Rolle. Da
ist die zeitliche Unsicherheit durch die Mechanik schon größer.
Also: alles mit Mass und Ziel. Letzten Endes ist durch die Modifikation
ja auch deine Hauptschleife einfacher geworden, so dass ich eher im
Gegenteil sogar sagen würde, dass in Summe gesehen das Programm ein
besseres Zeitverhalten hat.
Balou Baer schrieb:> TCCR1B=0b00001101; //Timer1 Konfigurieren> OCR1A=15624; //Vergleichsregister setzten> TIMSK=0b00010000; //Timer1 CompA-Interrupt AKTIVIEREN
solage du das so schreibst (also mit Binärzahlen anstelle ordentlicher
Schreibweise mit benannten Bits), weigere ich mich, den Code weiter zu
studieren.
Jemandem helfen ist ok. Aber wenn er mir dann auch noch Prügel zwischen
die Beine wirft, lass ich es bleiben.
Balou Baer schrieb:> Tachosignal=Tachosignal*4; //Tachosignale hochgerechnet pro Minute> Tachosignal=Tachosignal*60; //Tachosignal hochgerechnet pro Stunde
Wie soll man denn das verstehen? Tachosignal wird für die Dauer von ein
paar Maschinenzyklen auf Tachosignal * 4 gesetzt und dann gleich mit 60
multipliziert? warum? Wird die Anzahl der Pulse pro Minute noch irgendwo
gebraucht? Wenn ja, sollte sie einer eigenen Variablen zugewiesen
werden.
Balou Baer schrieb:> PORTD = ( 1 << PD4 ); //Ausgang zum Haupt uC an PIND3 INT1
Richtig wäre:
PORTD |= ( 1 << PD4 ); //Ausgang zum Haupt uC an PIND3 INT1
Sonst schaltest Du alle anderen Pins an dem Port versehentlich auf 0.
ernst oellers schrieb:> Balou Baer schrieb:>> Tachosignal=Tachosignal*4; //Tachosignale hochgerechnet pro Minute>> Tachosignal=Tachosignal*60; //Tachosignal hochgerechnet pro Stunde>> Wie soll man denn das verstehen? Tachosignal wird für die Dauer von ein> paar Maschinenzyklen auf Tachosignal * 4 gesetzt und dann gleich mit 60> multipliziert? warum?
Find ich nicht so schlimm.
Da die Variable nicht volatile ist, wird der Compiler den
Zwischenschritt rausoptimieren und wenn nicht, ist es auch nicht so
wild.
Man hätte natürlich genausogut auch
1
Tachosignal*=4*60;// von 1/4 Minute (15 Sekunden) auf Stunden
schreiben können. Noch besser wäre eine Präprozessor Lösung, die die
Multiplikation mit 4 mit den 15 Durchgängen in Beziehung setzt, von
denen jeder 1 Sekunde dauert.
@ Frank M.
Danke dir, für die Info, hatte mich schon immer gefragt warum da dieses
| bei einigen anderen vor dem = steht.
@ Karl Hein
Ich verwende die Binärcodierung nicht um dich oder anderen zu ärgern,
sondern ich kann dann sehr leicht die gesetzten Bits erkennen. Was aus
einer HEX Zahl nicht immer sofort zu sehen ist, oder was meinst du
genau?
oder Soll ich jetzt MCUCR |= (1<<ISC01);
MCUCR &=~(1<<ISC00);usw. schreiben?
Wenn ich 0bXXXXXXXX Schreibe z.B. MCUCR Register kann ich ganz einfach
aus den Datenblatt sehen, das
0 b X X X X X X X X
Bit 7 6 5 4 3 2 1 0
SE SM2 SM1 SM0 ISC11 ISC10 ISC01 ISC00 Bezeichung
bit 3 und 2 für das Ansprechverhalten von INT1 zuständig sind und Bit 1
und 0 für INT0. Hierbei habe ich setehen 0b00000010, Soll heißen löse
aus, wenn eine fallende Flanke erkannt wird.
Bit 7,6 und 5 sind für den Sleep Modus und wie ich den uC wieder
aufwecken kann. SleepMode braucht dieser uC nicht.
bei dem GICR Register für die Externen Interrupts das selber.
Bit 7 6 5 4 3 2 1 0
INT1 INT0 INT2 – – – IVSEL IVCE
Bit 7 oder 6 oder 5 sind für das Freischalten der Inerrupts zuständig.
und mit sei() werden dann quasi alle gesetzten Interrupts aktiviert.
Also ich kann dann die Bits über das Datenblatt halten und kann es
einfacher vergleichen.
Nur ich verstehe halt nicht, warum entweder nur die Timer laufen oder
nur die normalen Externen Interrupts. Kann ich diese nicht beide Laufen
lassen?
Balou Baer schrieb:> oder Soll ich jetzt MCUCR |= (1<<ISC01);> MCUCR &=~(1<<ISC00);usw. schreiben?
Nicht "|=" und "&=" sind entscheidend, aber die Benutzung der
Bitnamen schon.
Wenn dich die Schiebeoperatoren stören, kannst du den Macro _BV()
benutzen, den die avr-libc seit mehr als 10 Jahren mitbringt:
1
MCUCR=_BV(ISC01);
Der Vorteil der Bitnamen ist, dass man, wenn man diese einigermaßen
„intus“ hat, eben nicht erst noch im Datenblatt gegenlesen muss, ganz
im Gegensatz zu deinen Binärzahlen; die muss man auch, wenn man 10
Jahre lang schon mit AVRs arbeitet, immer wieder erst nachlesen gehen.
Wenn du unbedingt auch die nicht gesetzten Bits noch dokumentieren
willst, kannst du dir natürlich auch Makros „I“ (für „eins“) und
„O“ (für „null“) erfinden:
Hallo Jörg,
danke dir, jedoch verstehe ich als Anfänger jetzt nur Bahnhof.
Ich kann leider als Anfänger nur Werkzeuge einsetzten die ich kenne,
bzw. die ich bis jetzt erst gelernt habe. Leider sind die Aufgaben bzw.
der Unterricht nicht immer Realitätsnah, wenn ich mir hier die Tips und
Ratschläge, sowie die Kritiken durchlesen.
Ich stelle mir nun folgende Frage:
Also wenn ich jetzt
1
MCUCR=_BV(ISC01)
schreibe, woher will der Compiler dann wissen, welche Bits er setzten
soll, es gibt jetzt 4 Varianten: 0 0 Low Signal löst aus ; 0 1 jede
Eingangsänderung löst aus; 1 0 fallende Flanke löst aus; 1 1 steigende
Flanke löst aus.
Auch die Idee mit den Makros finde ich viel aufwendiger und
unübersichtlicher als meine binäre Sache, wenn ich den Schreibaufwand
sehe.
Jetzt eine kleine Kritik von mir und diese bitte nicht falsch verstehen,
denn ich bin wirklich, wirklich dankbar für die Hilfe von euch und auch
von Karl Heinz, denn auf die Flankenerkennung wäre ich nie gekommen.
Erstrecht nicht auf den Programmcode.
Jedoch ich bin Anfänger und hatte jetzt ca. ein halbes Jahr in
Abendschule (1 mal die Woche für 2 Stunden das Unterrichtsfach uC) in
dem wir mit GCC unser Experimentierboard mit einem ATMega32
programmieren lernen.
Jetzt wird hier ein Anfänger, sagen wir mal, zurecht gewiesen, warum und
weshalb man es so macht, wie man es macht. Vielleicht, weil man es nicht
anders gelernt hat und es nicht besser weiß.
Ich weiß das ich nicht optimal programmiere, weil ich es noch erlernen
muss. Jedoch scheinen hier Leute zu sein, die sich durch Nullen und
Einsen persöhnlich angegriffen fühlen. Klar, ich kann niemanden zur
Hilfe zwingen.
Ich versuche Ratschläge umzusetzten, jedoch kann ich nur Sachen
umsetzten, die ich auch verstehe. Und wenn ich mir keinen Rat mehr weiß,
möchte ich gern meine Frage hier stellen dürfen, ohne das ich für meine
nicht perfekt vorhandenen C und uC Kenntnisse in den Arsch getreten zu
werden.
Ich habe nichts dagen, wenn man mir sagt, warum machst du das mit
0bXXXXXXXXX und nicht mit XY Ungelöst und erklärt mir das dann so, das
es ein Anfänger auch verstehen könnte. Also warum man das so macht und
welche Vorteile diese Sache hat.
Und jetzt hängen wir uns hier an Binärer Schreibweise auf, um ein
Register zusetzten. Vielleicht wäre es ja einfach einfacher mir eine
Erklärung zu geben, warum ich nicht das Timer1 Register als Interrupt
und gleichzeitig den Externen Interrupt (Int0) mit dem MCUCR Register
nutzen kann.
Denn wenn mein Programm so aussieht, läuft der Timer nicht.
Wenn ich die externen Interrups wieder rauskommentiere, läuft der Timer
wieder.
WARUM???????? Das Kann doch jetzt nicht nur an Nullen und Einsen
liegen, die das gleiche bewirken wie
1
MCUCR=_BV(ISC01);
bei dem ICH noch nicht einmal sehe, welche Bits gesetzt oder nicht
gesetzt werden, also durch welche Bedingung der Interrupt ausgelöst
wird.
Bitte entschuldigt meine Worte und den vielen Text. Wenn jetzt noch die
Lösung des Problems heißt: "Es geht grundsätzlich so nicht, weil die
internen Interrupts nicht gleichzeitig mit externen Interrups laufen."
Dann verstehe ich erst recht die Welt nicht mehr!
Mit freundlichen Grüßen
Balou Baer
> schreibe, woher will der Compiler dann wissen,> welche Bits er setzten soll, [...]
Es gibt eine prozessorspezifische Include-Datei, welche vom Compiler
herangezogen wird, wenn Du
#include <avr/io.h>
schreibst. Dort steht das #define für ISC01 drin.
> Auch die Idee mit den Makros finde ich viel aufwendiger und> unübersichtlicher als meine binäre Sache, wenn ich den Schreibaufwand> sehe.
Es ist viel lesbarer. Wenn Du Deinen Code in ein paar Monaten liest,
wirst Du denken: "ISC01, ahja, ich weiß, was das ist". Dann wirst Du
dafür dankbar sein. Wenn Du aber 0b000010 liest, wirst Du erneut im
Datenblatt nachschauen: "Was war das denn nochmal?".
Ausserdem ist die Schreibweise mit Preprocessor-Konstanten weitaus
portabler. Wenn Du später mal ein Programm für einen anderen AVR
schreibst, kannst Du Deinen Code wiederverwenden. Denn auch wenn das Bit
ISC01 dann an einer anderen Stelle im Register sitzt, wird das korrekte
Bit gesetzt. Ja, das kommt vor!
Wenn Du jedoch 0b00000010 schreibst, dann ist der Code
höchstwahrscheinlich nicht portabel. Du müsstest dann bei der Übernahme
des Codes für einen anderen AVR-µC alle binären Konstanten im neuen
Datenblatt überprüfen. Bei Verwendung von ISC01 kannst Du jedoch den
Code quasi "blind" übernehmen.
Gruß,
Frank
Ok,
danke für die hinweise, also wie kann ich mit mcucr = _BV ( ISC01 ); im
Programm schreibe, wie ist denn dann das Bit vom ISC01 gesetzt? 0 oder
1??
Oder anders gefragt wie setzte ich mit der Schreibweise Bit auf 1 oder
0, das verstehe ich immer noch nicht so richtig.
Oder wie geht das hier:
"Wenn du unbedingt auch die nicht gesetzten Bits noch dokumentieren
willst, kannst du dir natürlich auch Makros „I“ (für „eins“) und
„O“ (für „null“) erfinden:
#define I(bit) (1 << (bit))
#define O(bit) 0
...
MCUCR = O(SE) | O(SM2) | O(SM1) | O(SM0) | O(ISC11) | O(ISC10) |
I(ISC01) | O(ISC00);"
????
( SE ) ( SM2 ) ( SM1 ) usw. versteht der Compiler? oder muss ich da noch
etwas einbinden? Muss ich bei dem define 0 ( bit ) muss ich da nicht
auch ( 0 << ( bit ) ) schreiben?
Ich bedankem ich für deine Mühen und verbleibe
mit freundlichen Grüßen
Balou Baer
Balou Baer schrieb:> danke für die hinweise, also wie kann ich mit mcucr = _BV ( ISC01 ); im> Programm schreibe, wie ist denn dann das Bit vom ISC01 gesetzt? 0 oder> 1??
Es ist gesetzt, also 1.
Was 0 ist, bedarf keiner weiteren Erwähnung.
> Oder anders gefragt wie setzte ich mit der Schreibweise Bit auf 1 oder> 0, das verstehe ich immer noch nicht so richtig.Setzen kannst du immer nur auf 1. Was nicht gesetzt ist, ist 0.
> Oder wie geht das hier:
Lös' es dir einfach gedanklich auf und überführ es in die
Binärschreibweise. Was anderes macht der Compiler auch nicht
draus.
Ich wollte dir das auch nicht unbedingt so empfehlen; ich wollte
damit nur eine Methode aufzeigen, wie man auch die 0-Bits (die
man genauso gut ganz weglassen könnte) halt noch dokumentieren
kann.
Zugegebenermaßen etwas subtil ist dabei natürlich die Verwendung
eines Makros namens I für ein gesetztes Bit und O für ein nicht
gesetztes; das O kann man in vielen Fonts kaum von der 0
unterscheiden. Eine 0 kann aber natürlich nicht als Makro-Name nach
dem #define auftauchen.
> ( SE ) ( SM2 ) ( SM1 ) usw. versteht der Compiler?
Ja.
> oder muss ich da noch> etwas einbinden?
Ja, <avr/io.h>, aber die wirst du wohl sowieso immer mit dabei
haben.
> Muss ich bei dem define 0 ( bit ) muss ich da nicht
Nein, nicht "define 0", sondern "define O". Falls du da keinen
Unterschied siehst, musst du deinen Browser einen anderen Font
benutzen lassen.
> auch ( 0 << ( bit ) ) schreiben?
Eine 0 kannst du so oft nach links schieben, wie du willst: es
bleibt einfach eine 0.
GICR=_BV(INT0);// 0b01000000; //Freigeben der Externen Interrups
7
8
sei();
warum funktioniert das so nicht?
Oder muss ich jetzt jetes einzelne TCCR1B schreiben?
P.S.: Der Timer löuft so nicht mehr, wenn ich das TCCR1B so schreibe,
alles andere Funktioniert.
P.S.: Wegen den Vorherigen Fragen, warum der Timer nicht läuft wenn ich
das MCUCR drin habe: INT 1 war nicht beschaltet! Daher hat Int1 ständig
ausgelöst und hat alles andere blockiert
GICR=_BV(INT1)|_BV(INT0);// 0b11000000; //Freigeben der Externen Interrups
9
10
sei();//Alle freigegebenen Interrupts werden EINGESCHALTET!!!!!!!!!!
11
12
13
///*
14
//ADC Wandler einstellen:
15
16
ADCSRA=_BV(ADEN)|_BV(ADIE)|_BV(ADPS0)|_BV(ADPS1)|_BV(ADPS2);//0b10000111; //Aktivieren und einstellen des ADC Wandlers hier 128
17
ADMUX=_BV(REFS0);//Aktivieren der externen Referencspannung
18
//*/
Dann habe ich noch eine Frage mit dem setzten der Bits im Register:
Wenn ich jetzt z.B. den AD - Wandler starten möchte, muss ich doch das
BIT ADSC im ADCSRA-Register setzen.
Jetzt möchte ich aber erst später im Programm dieses BIT setzen und
schreibe an die entsprechende stelle im Programm;
1
2
ADCSRA=_BV(ADSC);//Starte AD - Messung
Wie verhalten sich die anderen BITS in dem Register, die ich ja schon
vorher bei der Initialisierung gesetzt habe? Gehen die wieder auf 0?
Ich bedanke mich für deine Hilfe und verbleibe
mit freundlichen Grüßen
Balou Baer
Balou Baer schrieb:> Aber was anderes, wäre die Schreibweise so ok???
Grundsätzlich: ja.
> Jetzt möchte ich aber erst später im Programm dieses BIT setzen und> schreibe an die entsprechende stelle im Programm;>>
1
>ADCSRA=_BV(ADSC);//Starte AD - Messung
2
>
Damit löscht du aber die Anderen Bits wieder.
Um 1 Bit zu setzen, und NUR DIESES eine Bit zu setzen und alle anderen
unverändert zu lassen, veroderst du das Bit
1
ADCSRA|=_BV(ADSC);
WEnn du 1 Bit gezielt auf 0 zwingen willst, dann wird es als 'Kehrwert'
verundet
1
register&=~_BV(bitname);
lerne diese Dinge. Sie sind das um und auf in der µC-Programmierung.
Dinge wie Einzelbits setzen bzw. löschen musst du im SChlaf beherrschen.
Dann spielt es auch keine ROlle mehr, ob du
1
ADCSRA|=(1<<ADSC);
oder
1
ADCSRA|=_BV(ADSC);
schreibst. Ist sowieso dassselbe, weil sich hinter dem Makro _BV nichts
anderes als eine Umsetzung in die << Schreibweise verbirgt.
Bitmanipulation
Um die Frage noch zu beantworten:
> Wie verhalten sich die anderen BITS in dem Register, die ich ja schon> vorher bei der Initialisierung gesetzt habe? Gehen die wieder auf 0?
Was erwartest du, was passiert, wenn du
1
uint8_ti=5;
2
3
i=8;
schreibst? Nach der Zuweisung von 8 steht auch 8 im Register. Von der 5
bleibt kein Bit übrig.
Nicht anders ist es hier: Zuweisung ist Zuweisung. Dem Register wird ein
neuer WErt zugewisen. Und zwar dem kompletten Register. Nach
1
ADCSRA=(1<<ADSC);
hat das Register ADCSRA einen neuen Wert bekommen. Und zwar alle 8 Bit.
Und zwar den Wert, der sich aus der Auswertung des Ausdrucks 1<<ADSC
ergibt.
Da ist nichts magisch drann. Das ist einfach nur eine Zuweisung und ein
Verschiebeoperator, der das 1-Bit der dezimalen 1 soooft nach links
schiebt, bis es an der Position ADSC (welche Bitnummer das auch immer
ist) zu stehen kommt. Dieser Wert wird an ADCSRA zugeiwesen und damit
krigt ADCSRA als ganzes einen neuen Wert.
Und genau darin besteht ja der Trick bei
1
ADCSRA|=(1<<ADSC);
hier wird nicht einfach nur zugewisen. Sonder zuerst (wegen der |=
Operation) wird diese zurechtgeschobene 1 mit dem alten INhalt von
ADCSRA verodert. Beim Verodern kann aber ein Bit maximal von 0 auf 1
wechseln. Ein Bit welches schon 1 war, kann mittels einer Oder Operation
nicht auf 0 gebracht werden. Im Endeffekt bleiben daher bei dieser
Operation alle 1 Bits erhalten und es kommt noch ein 1 Bit an der
Position ADSC dazu.
Danke dir.
Das mit der uint8_t b = 7 und später b = 15 ist bekannt.
nur wie sich die Bits im Register verhalten wenn man die _BV Anweisung
nimmt war mir nicht klar.
Ich hoffe, dass ich das jetzt soweit verstanden haben.
Ich bedanke mich für euere Hilfe und verbleibe
mit freundlichen Grüßen
Balou Baer
Ich muss euch noch einmal stören.
Karl Heiz hatte mir damals beim entprellen eines Tasters geholfen s.o.
1
jetztzaehltaster_Y=(PIND&(1<<PD7)//Aktueller zustand an PIN D 7 wird abgerfagt
2
if(jetztzaehltaster_Y!=vorherzaehltaster_Y)//Ist der aktuelle Wert von PIN D7 nicht gleich dem vorherigen Wert, dann
3
{
4
vorherzaehltaster_Y=jetztzaehltaster_Y//wird der vorherige Wert mit dem aktuellen Wert überschrieben und
5
if(jetztzaehltaster_Y)//wenn jetztendschalter_Y, dann
6
{
7
zaehler_y_achse_runter--;
8
}
9
}
das war für eine positive Flanke. Jetzt benötige ich eine negative
Flanken erkennung, wenn ein gedrückter Taster losgelassen wird.
Wie würde das aussehen. so?
1
jetztzaehltaster_Y=(PIND&(1<<PD7)//Aktueller zustand an PIN D 7 wird abgerfagt
2
if(jetztzaehltaster_Y!=vorherzaehltaster_Y)//Ist der aktuelle Wert von PIN D7 nicht gleich dem vorherigen Wert, dann
3
{
4
vorherzaehltaster_Y=jetztzaehltaster_Y//wird der vorherige Wert mit dem aktuellen Wert überschrieben und
5
if(!jetztzaehltaster_Y)//wenn nicht jetztendschalter_Y, dann ???? So richtig????
6
{
7
zaehler_y_achse_runter--;
8
}
9
}
[/c]
Ich bedanke mich für euere Mühen und verbleibe
mit freunldichen Grüßen
Balou Baer