Hi,
ich habe ein neues Projekt angefangen und habe im ersten Schritt erstmal
eine Spannungsmessung mit dem PIC realisiert und wollte sie mir zu
Testzwecken auf einem LCD anzeigen lassen.
Es ist eine Weile her, dass ich das letzte Mal programmiert habe(also
seid bitte etwas nachsichtig mit mir xD) und ich habe jetzt ein paar
Fragen zu meinem Programm:
Also wenn ich "float adc" als feste Zahl definiere läuft das Programm
ohne Probleme aber wenn ich "float adc=5/1024" verwende, bekomme ich nur
ne 0 als Ergebnis auf dem LCD angezeigt.
In meiner Vin_Calculation-Funktion muss ich für jeden Rechenschritt eine
neue Variable verwenden sonst kommt nichts raus, kann mir einer
vielleicht erklären warum es nicht mit einer klappt (ich vermute mal hat
was mit der internen Verarbeitung der Befehle im PIC zu tun oder
so...(overflow oder was weiß ich)?)
Und dann ganz unten im Code stelle ich ja Spannung auf dem LCD dar, bei
mir ist die zahl 8bit lang, wie kann das sein? Ich habe doch "txt1"
definiert müsste er nicht einfach nach 6bits abschneiden?
PS: verwende zum compilieren MicroC PRO for PIC und testen konnte ich
das ganze nur in einer Simulation (Proteus8), da ich zur Zeit noch
keinen PIC zur Verfügung habe.
Ich würde Flieskommaarithmetik auf einem 8bit uC wenn's geht vermeiden.
Den ADC kann man problemlos mit Festkomma auswerten...
Wenn du bei Float bleiben willst, versuch es mal so: adc=5.0/1024.0
Robert schrieb:> Also wenn ich "float adc" als feste Zahl definiere läuft das Programm> ohne Probleme aber wenn ich "float adc=5/1024" verwende, bekomme ich nur> ne 0 als Ergebnis auf dem LCD angezeigt.
logisch.
5 ist ein Integer
1024 ist ein Integer
Also wird die Division 5 / 1024 als Integer Division durchgeführt. Die
aber liefert keine Nachkommastellen. Die funktioniert so, wie du das
damals in der Grundschule gelernt hast:
13 / 5 ergibt 2, Rest 3
Nur das hier der Rest nicht weiter interessiert.
Das du dann das Ergebnnis der Division an einen float zuweist, ist zwar
nett. Ist aber völlig uninteresant. Der COmpiler kümmert sich bei der
Auswahl wie eine Operation zu implmentieren ist nie darum was mit dem
Ergebnis weiter passiert. Entscheidend sind immer nur die beiden
Operanden. Die bestimmen, wie eine Operation implementiert wird. Und int
durch int ist nun mal eine int-Division.
Dieses Vorgehen ist eigentlich in so ziemlich allen Programmierprachen
so. Bis auf die Pascal-Schiene, in der es 2 getrennte
Divisions-Operatoren gibt, je nachdem welche Division (Integer oder
Floating Point) man haben will.
Ach ja, hab den Wald vor lauter Bäumen nicht mehr gesehen logisch.
Hätte ich wohl ein paar Minuten länger drüber nachdem sollen.
hab jetzt einfach float adc=(float)5/1024 draus gemacht...
@Max naja es kommt ja noch bisschen Programm dazu und ich will noch paar
Divisionen etc. durchführen und da finde ich es persönlich von Vorteil
es erstmal alles auf einen gemeinsamen Nenner (float) zu bringen.
Robert schrieb:> Und dann ganz unten im Code stelle ich ja Spannung auf dem LCD dar, bei> mir ist die zahl 8bit lang, wie kann das sein? Ich habe doch "txt1"> definiert müsste er nicht einfach nach 6bits abschneiden?
Meinst du mit 8bit 8 Stellen/Zeichen?
Du übergibst der Funktion nur einen Pointer auf das erste Element deiner
String, die Funktion weiß nicht wie lange deine String ist und
überschreibt auch nicht deklarierten Speicher. Sieh dir mal in der Hilfe
an, was die Funktion gerne in die String schreiben würde.
Für die LCD_Out endet der String erst dort, wo die FloatToStr die
Nullterminierung gesetzt hat.
Ok hab das jetzt soweit hinbekommen.
Ich habe jetzt weiter programmiert und meine Spannungsmessung ist jetzt
in der while und ich möchte sie trotzdem auf dem LCD anzeigen, jedoch
ohne viel Zeit einzubüßen. Also wenn ich den lcd_out Befehl einfach in
die while Schreibe kommt ja nur Mist raus, da diese ja viel zu schnell
ist und wenn ich mit delay arbeite verlangsamere ich meine while
Schleife was ich nicht will. Gibt es eine Möglichkeit, wie ich sagen wir
mal alle 5sek den aktuellen Wert der Spannung auf dem lcd Anzeigen lasse
ohne die while zu verlangsamen?
Robert B. schrieb:> Ich habe jetzt weiter programmiert und meine Spannungsmessung ist jetzt> in der while und ich möchte sie trotzdem auf dem LCD anzeigen, jedoch> ohne viel Zeit einzubüßen. Also wenn ich den lcd_out Befehl einfach in> die while Schreibe kommt ja nur Mist raus, da diese ja viel zu schnell> ist und wenn ich mit delay arbeite verlangsamere ich meine while> Schleife was ich nicht will. Gibt es eine Möglichkeit, wie ich sagen wir> mal alle 5sek den aktuellen Wert der Spannung auf dem lcd Anzeigen lasse> ohne die while zu verlangsamen?
Eine Möglichkeit ist natürlich, dass du dir mit einem Timer einen
Basistakt ins Programm einziehst
FAQ: Timer
Den Timer stellst du so ein, dass er zb alle 0.1 Sekunden die ISR
aufruft. IN der ISR zählst du eine Variable bis 50 (das sind dann
logischerweise 0.1 Sekunden mal 50, oder eben 5 Sekunden) und wenn das
der Fall ist, setzt du dir eine globale (volatile) Variable auf 1.
In der Hauptschleife machst du, was es zu machen gibt und wenn dann auch
noch die Variable auf 1 ist, dann machst du die Ausgabe und setzt die
Variable wieder auf 0 zurück, auf das die ISR sie 5 Sekunden später
wieder auf 1 setzt.
Timingsachen bedeuten praktisch immer den Einsatz eines Timers. Vergiss
bitte delays. Wenn du mit den großen Jungs spielen willst, dann musst du
das Sandspielzeug 'delay' (ausser für ganz spezielle Fälle) zur Seite
legen.
Diese Technik der sog. 'Job-Flags' ist eine sehr allgemeine Technik und
du wirst sie noch oft brauchen. generell funktionieren Programme, die
scheinbar mehrere Dinge gleichzeitig machen oft genau so:
IN der Hauptschleife werden nacheinander die Jobflags abgeklappert und
nachgesehen ob es in einem Teilbereich etwas zu tun gibt. Wenn ja (wenn
also das Flag gesetzt ist), dann wird die Arbeit ausgeführt und das Flag
auf 0 zurück gesetzt. Wenn irgendein anderes Teilsystem eine Arbeit
erledigt haben will, dann setzt es das entsrpechende Jobflag auf 1.
Und delays - die sind, bis auf ganz kurze Delays, schlicht und
ergreifend verboten. Denn sie sind der Tod jeden Programmes, das sich um
mehrere Dinge 'gleichzeitig' kümmern muss.
@stefan o
Ich meine damit, dass die Loop ja recht schnell ist, sich somit also die
Zahl auf dem lcd auch viel zu schnell wechselt.
@ Karl werde mich mal bisschen einlesen danke für den Tipp ...
Ich wollte jetzt den Timer0 verwenden aber irgendwie komme ich damit
nicht klar.
Also in der FAQ steht ich muss erstmal die Bits im OPTION Register
setzen, also ich hab eine Frequenz von 10MHz und möchte mit Prescaler
256 rechnen, damit komme ich auf ~38 Interrupts pro Sekunde.
Aus der FAQ:
1
voidt0_ini()
2
{
3
// Datenblatt Seite 83
4
OPTION=0b11000100;
5
// 1------- (Betrifft nicht Timer0)
6
// -1------ (Betrifft nicht Timer0)
7
// --0----- Internen Takt für Timer0 verwenden
8
// ---0---- Bei externem Takt an steigender Flanke inkrementieren
9
// (in diesem Fall egal ob 1 oder 0 weil der interne Takt verwendet wird)
10
// ----0--- Prescaler für Timer0 verwenden
11
// -----100 Prescaler 1:32
12
13
T0IF=0;// Interruptflag von Timer0 löschen
14
GIE=1;// Alle nichtmaskierten Interrupts erlauben
15
T0IE=1;// Timer0 Interrupt erlauben
16
17
}
in meinem Fall muss ich ja bloß die letzten 2 Bits zu einer 1 machen,
damit der prescaler 256 ist oder? Aber irgendwie kann ich es nicht
kompilieren. Ich bekomme die Fehlermeldung undeclared identifier OPTION.
Damit habe ich ja bloß den Counter, jetzt muss ich ja irgendwo die
Overflows speichern oder? Dann kann ich z.B. nach 38 Overflows(also nach
einer Sekunde) meine Werte auf dem LCD anzeigen und dann den Counter
wieder auf 0 setzen. Soweit richtig?
z.B. so:
Versuch es mal so:
//Timer0
//Prescaler 1:128; TMR0 Preload = 61; Actual Interrupt Time : 9,984 ms
//Place/Copy this part in declaration section
void InitTimer0(){
OPTION_REG = 0x86;
TMR0 = 61;
INTCON = 0xA0;
}
void Interrupt(){
if (TMR0IF_bit){
TMR0IF_bit = 0;
TMR0 = 61;
//Enter your code here
}
}
Nun läßt du eine Variable 100 mal hochzählen, dann
hast du ca. 1 Sekunde. Dann verzweigst du zu deinem
LCD und gibst Daten auf dem LCD aus.
Also im Hauptprogramm abfragen wann deine Variable 100
erreicht hat.
ist das programmiertechnisch in Ordnung? Also das Programm läuft damit
auch soweit und scheint auch ca. jede Sekunde die Zahl auf dem LCD zu
ändern aber ich verstehe nicht ganz wie der Algorithmus genau
funktioniert. Ich rufe ja in jedem while Durchlauf die Funktion ISR auf
und dann guckt die ob ich Overflow habe oder nicht und je nach dem zählt
der counter dann hoch. Aber habe ich nicht immer nen Overflow im TMR0IF
stehen, da der ja schneller Overflow erreicht als ich die while durch
habe und ich somit eigentlich nicht wirklich 1 Sekunde vergeht bis ich
was auf dem LCD sehe sondern 38 while loops?
Ich hoffe ihr versteht was ich meine und es kann mir vielleicht einer
Licht ins Dunkel bringen.
Robert B. schrieb:> ist das programmiertechnisch in Ordnung?
Nein, die ISR wird von der Hardware automatisch aufgerufen wenn der
Timer überläuft, die musst du nicht mehr aufrufen, dann musst du das
TMR0-Interrupt und die Globalen Interrupts erst enablen.
Max H. schrieb:> Robert B. schrieb:>> ist das programmiertechnisch in Ordnung?> Nein, die ISR wird von der Hardware automatisch aufgerufen wenn der> Timer überläuft, die musst du nicht mehr aufrufen, dann musst du das> TMR0-Interrupt und die Globalen Interrupts erst enablen.
Wenn ich die ISR nicht in der while abrufe funktioniert es nicht. So wie
es jetzt ist läuft das Ganze....
Ne Idee was da falsch sein könnte?
Also so wie ich es verstehe macht das Programm jetzt folgendes:
1.)gucken ob in TMR0IF eine 1 steht wenn ja dann ist overflow und er
setzt es zurück und zählt mit dem counter einen hoch.
2.)geht zurück in die while und läuft sie durch
3.)guckt wieder ob overflow vorliegt und es geht wieder von vorne los
Es ist nur jetzt so, dass er nicht genau genug Zählt oder? Er muss ja
erst die while-Scheife durchlaufen(was ja auch seine Zeit kostet) und
dann checkt er erst ob overflow vorliegt und je nach dem zählt er den
Counter hoch.
Ich will aber, dass er unabhängig von der while den Counter hochzählt
und so bald der Counter den eingestellten Wert erreicht hat, die
if-Anweisung also die LCD Anzeige ausgeführt wird.
Was muss ich dazu ändern und ist es so wie ich es mir Vorstelle
überhaupt richtig und möglich???
Nein das ist falsch.
Alle ca. 22ms wird der Interrupt bei dir aufgerufen.
Und dann wird dein Hauptprogramm weiter geführt.
In der Hauptschleife kommt kein Aufruf für
die ISR. Du mußt nur die Interrupts zulassen.
Ein Beispielcode habe ich oben geschrieben.
Du musst die ISR nicht aufrufen, diese wird, wenn du Timer und
Interrupt richtig eingestellt hast automatisch aufgerufen sobald der
Timer überläuft.
Lies dir mal was zu Interrupts durch:
http://www.sprut.de/electronic/pic/int/int.htm
Sry stehe irgendwie voll auf dem Schlauch, vielleicht weil ich schon
solange an dem Problem sitze. Kann jemand von euch vielleicht so Gütig
sein und mir posten wie es in meinem Beispiel richtig geht?
Nimm die "void Interrupt()" welche <Stefan> um 14:58
gepostet hat. An der Stelle in der Interruptroutine wo "//enter your
code here" steht fügst du ein: needUpdate--;
In deiner Main vor dem while(1) kommt:
needUpdate = 100;
Das ISR() fliegt raus.
Dann deine Endlosschleife:
while(1) {
if( needUpdate == 0) {
needUpdate = 100;
LCD Anzeige
.....
.....
}
}
>> keine Besserung immer noch keine Anzeige....
Ist das alles?? Wenn ja, dann fehlt die Initialisierung des LCD!
Und LCD Anzeige ist ja wohl kein Funktionsaufruf.
Stell mal das GESAMTE Programm als Anhang rein....
Die Initialisierung steht selbstverständlich im richtigen Programm drin.
Mich wundert bloß warum funktioniert das ganze wenn ich die ISR() in der
while Aufrufe und wenn ich die weg lasse passiert nix....
so wie ich das verstehe sieht die while schleife die needUpdate variable
gar nicht... vielleicht läuft der Timer ja auch ohne ISR Funktion in der
while aber der Wert wird bloß nicht übergeben oder so und deswegen führt
er die if Anweisung nicht aus und ich sehe nix auf dem LCD
Vielleicht erwartet dein Compiler einen anderen Namen für die ISR.
Immerhin muß er ja wissen das "Interrupt" die ISR sein soll.
Ohne diese Kenntnis wird deine ISR niemals (automatisch) aufgerufen.
Zum Compiler gehören normalerweise auch einige Beispiele.
Da ist bestimmt etwas mit einer ISR dabei.
Da ich deinen Compiler nicht kenne, mußt du das Manual selber lesen ;)
>> Vielleicht erwartet dein Compiler einen anderen Namen für die ISR.> Immerhin muß er ja wissen das "Interrupt" die ISR sein soll.> Ohne diese Kenntnis wird deine ISR niemals (automatisch) aufgerufen.>> Zum Compiler gehören normalerweise auch einige Beispiele.> Da ist bestimmt etwas mit einer ISR dabei.>> Da ich deinen Compiler nicht kenne, mußt du das Manual selber lesen ;)
sowas in der Art denke ich mir auch ich benutze MicroC for PIC weiß ich
die ISR da nennen muss, damit er sie erkennt??
Anbei hier mal der gesamte Quellcode:
1
inti;
2
volatileneedUpdate;
3
staticintcounter=0;
4
floatadc=(float)5/1024;
5
unsignedVtemp;
6
floatVpv,Vtemp1,Vtemp2,Vtemp3;
7
chartext_1[]="Spannungsmessung";
8
chartext_2[]="Vpv:[V]";
9
chartxt1[7];
10
11
// Lcd pinout settings
12
sbitLCD_RSatRC2_bit;
13
sbitLCD_ENatRC3_bit;
14
sbitLCD_D7atRC7_bit;
15
sbitLCD_D6atRC6_bit;
16
sbitLCD_D5atRC5_bit;
17
sbitLCD_D4atRC4_bit;
18
19
// Pin direction
20
sbitLCD_RS_DirectionatTRISC2_bit;
21
sbitLCD_EN_DirectionatTRISC3_bit;
22
sbitLCD_D7_DirectionatTRISC7_bit;
23
sbitLCD_D6_DirectionatTRISC6_bit;
24
sbitLCD_D5_DirectionatTRISC5_bit;
25
sbitLCD_D4_DirectionatTRISC4_bit;
26
27
voidInitTimer0(){
28
TMR0=0;
29
OPTION_REG=0x86;
30
INTCON.TMR0IF=0;
31
INTCON.GIE=1;
32
INTCON.TMR0IE=1;
33
}
34
35
ISR(){
36
if(INTCON.TMR0IF){
37
INTCON.TMR0IF=0;
38
counter++;
39
}
40
if(counter==38){
41
counter=0;
42
needUpdate=1;
43
}
44
}
45
46
floatVin_Calculation(){// Calculation of Vinput
47
Vpv=0;
48
for(i=1;i<=4;i++){// i time read ADC input
49
Vtemp=ADC_Read(0);// ADC result from pin A0
50
Delay_us(100);// time for the Capacitor
51
Vpv=Vpv+Vtemp;
52
}
53
Vtemp1=Vpv*10;// potential divider
54
Vtemp2=Vtemp1*adc;
55
Vpv=Vtemp2/(i-1);
56
57
returnVpv;
58
}
59
60
61
voidmain(){
62
63
InitTimer0();
64
65
Lcd_Init();// Initialize LCD Module
66
Lcd_CMD(_LCD_CURSOR_OFF);// Cursor OFF
67
Lcd_Cmd(_Lcd_Clear);// Clear display
68
Lcd_Out(1,1,text_1);// Text on LCD 1row
69
Lcd_Out(2,1,text_2);// Text on LCD 2row
70
71
while(1){
72
73
Vpv=Vin_Calculation();
74
75
if(needUpdate==1){
76
needUpdate=0;
77
FloatToStr(Vpv,txt1);
78
Lcd_Out(2,11,txt1);
79
}
80
}
81
82
}
Edit: @hagi genau daran hat's gelegen. Ich danke dir vielmals jetzt
läuft es einwandfrei.