Hallo zusammen,
ich habe ein Problem, das ich mir so langsam gar nicht mehr Erklären
kann. Also ich verwende einen Mikrocontroller der Familie ATmega 2561,
der eine Linearisierungsfunktion für ein W1 Stellglied berechnet. Soweit
so gut, er macht „eigentlich“ was er soll.
Mein Problem ist, das es gelegentlich zu Fehlern kommt.
Beispiel: alles Funktioniert, Spannungsversorgung wird abgeschaltet und
nach einer Zeit wieder eingeschaltet und siehe da, der Mikrocontroller
berechnet die Funktion falsch. Nach einem hin und her aus Aus-/ und
Einschalten geht’s dann wieder, wie durch Zauberhand.
Hat jemand schonmahl Ähnliche Probleme gehabt?
Viele Grüße und schonmahl Danke im Voraus für Anregungen und Tipps
Tobias
> und siehe da, der Mikrocontroller berechnet die Funktion falsch.> Nach einem hin und her aus Aus-/ und Einschalten geht’s dann wieder,> wie durch Zauberhand.
Klingt nach nicht initialisierten Variablen, die mit zufälligen Werten
lostraben. Ein paar mal ist der Zufall dann eben auch gerade richtig, so
dass sich das Problem von selbst regelt. Ein ander mal dann eben wieder
nicht.
Hallo Karl Heinz,
besten Dank für den Tipp. Ich hatte in meiner Funktion Variablen gleich
mit einem Ergebniss Initialisiert.
z.B. float Bruch = a / b;
jetzt habe ich das so gemacht
float Bruch = 0;
Bruch = a/b;
Es hat denn anschein, als würde es jetzt besser laufen :-)
grüße Tobias
Kann mal jemand meine Mathe kontrollieren?
Im Code steht
1
K=1023/230;
2
Ua=u/K;
3
Ul=230;
4
UaDurchUl=Ua/Ul;
da wird ziemlich viel rumgerechnet. Aber was wird (sollte) eigentlich
gerechnet werden.
Da steht
UaDurchUL = Ua / Ul
Ul einsetzen
UaDurchUL = Ua / 230
Ua einsetzen
UaDurchUl = ( u / k ) / 230
Auf Doppelbruch erweitern
UaDurchUl = ( u / k ) / ( 230 / 1 )
Doppelbruch auflösen
UaDurchUL = u / ( k * 230 )
k einsetzen
UaDurchUL = u / ( 1023 / 230 * 230 )
kürzen
UaDurchUL = u / 1023
Da wird als eine Menge rumgerechnet, nur um u durch 1023 zu dividieren.
Bei jeder Floating Point Operation hat man aber entsprechende Rundungs
bzw. Darstellungsfehler. Die 'einfachere Version'
UaDurchUL = u / 1023.0;
ist also nicht nur schneller, sondern auch höchst wahrscheinlich näher
an der Wahrheit.
Tobias schrieb:> hallo karl Heinz,>> ähmm ist mir nicht klar.
Was mir nicht klar ist: Wwarum du das nicht weißt, wie sich die Sache
mit den Datentypen in C (bzw. in den meisten Programmiersprachen)
verhält?
> warum sollte den 1023/230 nicht 4.447 sein?
weil 1023 ein int ist und 230 ein int ist.
Damit ist das eine Integer Division. Und die erzeugt nun mal keine
Nachkommastellen.
Das du das Divisionsergebnis einem float zuweist, ist zwar nett, aber
völlig irrelevant. Entscheidend sind die Datentypen der beteilgten
Operanden. Und die sind nun mal beide int.
Seltsamer Name, der sich selber nicht verdient.
Ich vermute übrigens, dass du genau an dieser Stelle ein Problem mit der
Flankenerkennung hast. Denn dich interessiert das, was tatsächlich am
Pin ist keinen Deut, sondern du fabulierst dir selber eine Flanke her...
Was, wenn nach dem Einschalten der Eingangspin nicht den Pegel hat,
den deine Software erwartet?
Hallo Lothar,
das kommt daher, das ich um meine Funktion durchführen zu können, die
Leitdauer an meinem Triac messen muss. Da die Leitdauererfassung aber
immer im Nulldurchgang der Wechselspannung eine kurze Leitdauer misst,
obwohl keine vorhanden ist, habe ich das so abgestellt.
Was halt komisch ist, ist das ich ein anderes Verhalten habe, wenn ich
im Betrieb den Programmer rausziehe oder reinstecke, das hat doch
eigentlich nichts mit der Funktion zu tun, oder?
Tobias schrieb:> Also bis jetzt hat das alles keinen großen Einfluss.
Noch sind wir ja nicht fertig.
Bis jetzt hat mich ja die Logik noch überhaupt nicht interessiert,
sondern ich such erst mal den Code nach formalen Dingen ab, die seltsam
aussehen und mehr verwirren, als sie zum Verständnis des Codes
beitragen. Zb das hier
1
ISR(TIMER1_COMPB_vect)//Löschen aller gezündeten Pins
2
{
3
if(gezuendet_A=1)
4
{
5
if(weiterZuenden)
6
{
7
8
PORTC|=(1<<PC0);//T1 aus W1 Low Aktiv
9
gezuendet_A=0;
10
gezuendet_B=1;
11
12
13
if(zweiteHalbwelle)
14
{
15
//gezuendet=1;
16
zweiteHalbwelle=0;
17
OCR1C=ALPHAnutz+20000;
18
}
19
else
20
{
21
gezuendet_A=0;
22
weiterZuenden=0;
23
}
24
}
25
}
26
}
nach der Erkennung von weiterZuenden wird gezuendet_A auf jeden Fall
erst mal auf 0 gesetzt. D.h. das innere if suggeriert hier
1
if(zweiteHalbwelle)
2
{
3
//gezuendet=1;
4
zweiteHalbwelle=0;
5
OCR1C=ALPHAnutz+20000;
6
}
7
else
8
{
9
gezuendet_A=0;
10
weiterZuenden=0;
11
}
dass dies nur im Falle einer zweiten Halbwelle der Fall wäre.
Tatsächlich stimmt das aber gar nicht. gezuendet_A ist auf jeden Fall 0,
egal wie dieser if ausgeht. Betrachtet man nur das if, kommt man also zu
falschen Schlussfolgerungen, bzw. in weiterem Kontext dann eben zu: was
stimmt denn jetzt eigentlich? Immer 0, oder nur im Falle
zweiteHalbwelle.
Das sind so die Dinge, die einem bei rein formaler Durchsicht schon mal
auffallen. Genauso wie du für meinen Geschmack im Programm da ein wenig
sehr freizügig rumrechnest. Hast du die Funktion 'Linearisierung' schon
mal in einem PC Programm getestet, indem du sie systematisch in einer
Schleife mit variierenden Werten durchgejagt und das Ergebnis
kontrolliert hast?
Tobias schrieb:> habe ich das so abgestellt.
Die Flankenerkennung funktioniert nicht, wenn du nicht mit der
Aussenschaltung garantiert immmer mit dem selben Pegel am Pin beginnst.
Nur mal angenommen, du erwartest zuerst ein "low" am Eingang, der ist
aber gerade "high": dann erkennst du immer dort eine fallende Flanke, wo
in der Realität eine steigende Flanke ist...
> Was halt komisch ist, ist das ich ein anderes Verhalten habe, wenn ich> im Betrieb den Programmer rausziehe oder reinstecke, das hat doch> eigentlich nichts mit der Funktion zu tun, oder?
Hast du zusätzlich noch EMV-Sauereien auf dem Interrupt-Pin?
Tobias schrieb:> Hallo Lothar,>> das kommt daher, das ich um meine Funktion durchführen zu können, die> Leitdauer an meinem Triac messen muss. Da die Leitdauererfassung aber> immer im Nulldurchgang der Wechselspannung eine kurze Leitdauer misst,> obwohl keine vorhanden ist, habe ich das so abgestellt.
Dann ist aber 'fallendeFlanke' ein ganz schlechter Name.
Der suggeriert nämlich etwas, was offensichtlich nicht stimmt bzw. auch
gar nicht erwartet war.
Sieht trotzdem seltsam aus, der ganze Interrupt. Offenbar wird da
abwechselnd TCNT auf 0 gesetzt bzw. der TCNT geholt. Da wird also was
ausgemessen.
Der Interrupt ist eingestellt auf
1
EIMSK|=(1<<INT0);//aktiviere INT0
2
EICRA|=(1<<ISC01);//INT0 reagiert auf fallende Flanke
ergo, wird die Zeitdauer von einer fallenden Flanke zur nächsten
ausgemessen, aber nur bei jedem 2.ten mal.
Was das mit deiner Erklärung über die Leitdauer zu tun hat, ist mir
allerdings nicht klar. Eine irgendwie erkennbare Erkennung eines
'Fehlerzustands' (Zitat: "obwohl keine vorhanden ist") kann ich keine
erkennen. Da wird sich halt drauf verlassen, dass der jeweils erste
Interrupt schon passen wird.
Ok, also da das mit dem if(gezuendet_A=1) habe ich rausgeschmissen.
Funktion funktioniert immer noch. Mittlerweile auch so, dass ich
Spannung An- und Aus machen kann und es geht dann auch immer noch
>EICRA|=(1<<ISC01);//INT0 reagiert auf fallende Flanke
3
>
Ach entschuldigung. Das ist ja gar nicht der INT0, über dessen ISR wir
reden.
Dessen initialisierung ist ja hier
1
EIMSK|=(1<<INT1);//aktiviere INT1
2
EICRA|=(1<<ISC10);//INT1 reagiert auf fallende und steigende Flanke
ok. Selbes Problem - wie von Lothar schon angemerkt. Da wird drauf
vertraut, dass die erste Flanke, die einen Interrupt auslöst, schon eine
fallende sein wird.
GUt.
Aber: was wenn dem nicht so ist?
Karl Heinz schrieb:> Ach entschuldigung.
Übrigens:
Das ist besonders schlau, die Initialisierung der externen Interrupts in
2 getrennte Funktionen zu stecken, die dann auch noch "TimerxInit"
heißen.
Tobias schrieb:> Ok, also da das mit dem if(gezuendet_A=1) habe ich rausgeschmissen.
Na wenn das mal gut geht.
Da war offenbar eine Verriegelung der einzelnen Compare Interrupts
vorgesehen, so dass der ganze COmpare-Interrupt Tanz nur dann zu
Veränderungen am Pin führt, wenn hier
1
ISR(INT0_vect)//NDG; 240us Verzögerung
2
{
3
leitdauerErkennung=1;
4
5
6
TCNT1=0;
7
ALPHAnutz=ALPHA;
8
OCR1A=ALPHAnutz;
9
gezuendet_A=1;
10
gezuendet_B=0;
11
12
13
}
der ganze Vorgang angestossen wird. Sprich: bleiben die INT0 Interrupts
aus, dann laufen die Compare-Interrupts ins Leere und der Pin PC0 wird
nicht mehr angesteuert.
Ist dieses Verhalten jetzt noch gewährleistet (ich habs nicht
durchdacht, was dann passiert)
Ok, nach einigem nachdenken und einer kleineren Überarbeitung des
Quellcodes, (Dank eurer Anregungen) ist der momentane Stand, das es beim
Einschalten erst gar nicht funktioniert, dann falsch und zu guter Letzt
richtig. Neu ist, dass das Verhalten jetzt immer gleich ist.
Neu hin zu gekommen ist, das wenn ich am Potentiometer
drehe (also einen anderen ADC Wert „u“ vorgebe) es scheinbar keinen
Einfluss auf meine Funktion hat. Die Funktion spielt also die ganze Zeit
mit dem gleichen wert rum.
In deinem Programm taucht jetzt plötzlich ein Timer 4 auf.
Von dem gibst du 3 Compare Interrupts frei, hast aber nur für einen
davon eine ISR -> Prozessorreset.
Dein Programm fängt dauernd wieder von vorne an.
Es ist keine gute Idee, an einem Programm zu viel auf einmal zu
verändern (bzw. dann auch gleich noch Erweiterungen einzubauen). Eine
Änderung, testen was sich verändert hat. Dann die nächste Änderung.
Tobias schrieb:> Mir ist nicht ganz klar, warum mein Programm wieder von vorne anfängt?
Der Compiler baut defaultmäßig für jede nicht vorhandene ISR einen
Sprung zum Programmanfang ein (Adresse 0).
Gruß Dietrich
Mal vereinfacht ausgedrückt:
Im Speicher liegt eine Tabelle mit den Adressen aller InterruptRoutinen.
Tritt ein Ereignis ein das einen Interrupt auslöst (bspw. TimerCompare),
so wird in dieser Tabelle nach der Adresse gesucht und dorthin
gesprungen.
Hast du allerdings für deinen Compare-Interrupt keine Routine angelegt,
so kann in der Tabelle natürlich auch keine Service Routine hinterlegt
sein.
Stattdessen liegt dort ein Sprung nach _reset.
die Interrupts für Output Compare 4 A, Output Compare 4 B und Output
Compare 4 C freigibst, dann MUSST du auch für jeden davon eine ISR
haben:
1
ISR(TIMER4_COMPA_vect)
2
{
3
...
4
}
5
6
ISR(TIMER4_COMPB_vect)
7
{
8
...
9
}
10
11
ISR(TIMER4_COMPC_vect)
12
{
13
...
14
}
Ob die Funktionen etwas machen oder nicht, spielt keine Rolle. Sie
müssen nur in deinem Programm existieren.
Jede ISR, die NICHT in deinem Programm existiert, ist standardmässig so
'verdrahtet', dass sie zu einem Prozessorreset führt. Tritt der
entsprechende Interrupt auf, dann kommt es daher zu einem
Prozessorreset.
Und bei dir treten diese Interrupts auf. Denn der Timer läuft, die
COmpare Register haben einen Wert (auch wenn der 0 ist) und der
Verglichsmechanismus samt versuchtem Auslösen eines Interrupts läuft
ständig, wenn der Timer läuft. Und irgendwann ist dann eben der
Timerwert wieder 0 und damit hast du einen Compare Match zb mit OCR4B.
Der entsprechende Interrupt ist freigegeben, also wird die ISR
angesprungen, die du aber nicht geschrieben hast. Statt dessen geht es
in die 'Default-ISR', die einen Reset auslöst.
Du kannst es dir also aussuchen:
Entweder du baust die Funktionen rein oder du gibst die entsprechenden
Interrupts (die du nicht benötigst), nicht frei.
Das sind deine 2 Optionen. Such dir die aus, die dir am sinnvollsten
erscheint.
Tobias schrieb:> Ok, danke für den Tipp.> Ich habe die leeren Vergleichswerte rausgenommen. Funktioniert aber> immer noch nciht so ganz wie ich es gerne hätte :(
Sag mal, hast du die letzten 3 Beiträge mit Absicht überlesen?
Ganz ehrlich, du hast von Tuten und Blasen keine Ahnung. Du doktorst
irgendwo rum, solange bis es zufällig funktioniert.
Oder aber, es zufällig "annähernd" das macht was du willst.
Ganz ehrlich, was du vorhast ist noch zwei Nummern zu hoch für dich.
Mach das avr-gcc Tutorium hier durch, und zwar von Anfang bis Ende.
Masl hat es zwar etwas krass ausgedrückt, aber so unrecht hat er nicht.
Das Problem ist halt, dass du einen Haufen Code hast, der nicht wirklich
funktioniert und keine Ahnung wo man mit Fehlersuche anfangen soll.
Gerade am Anfang seiner Karriere, kann ein Neuling nichts dümmeres
machen, als haufenweise ungetesteten Code schreiben. Die beste Methode
ist immer noch schrittweise vorgehen. Und auch ganz wichtig: von Anfang
an Testmöglichkeiten vorsehen. Ob das eine UART ist oder ein LCD, oder
ob man sich mit ein paar LED an ein paar Portpins behelfen muss, wichtig
ist, dass man nachvollziehen kann, was das Programm macht und vor allen
Dingen warum es das tut (Variablenwerte!).
Auch wenn die gegenseitige Steuerung der 3 Compare Interrupts vom Timer
1 ein wenig unübersichtlich ist durch die vielen Flags, die sich
gegenseitig setzen bzw. freigeben, ... ich denke, der Teil funktioniert.
Mit einer Statusvariablen, die die Werte von 0 bis 4 durchläuft und in
den ISR weitergzählt bzw. auf den richtigen Wert geprüft wird, wäre das
alles wahrscheinlich einfacher nachzuvollziehen gewesen.
Ich hab ja immer noch die Monsterfunktion mit der Berechnung in
Verdacht. Ob da unter allen Umständen das richtige rauskommt, dafür
würde ich meine Hand nicht ins Feuer legen, ehe ich nicht auf dem PC die
Funktion mal in einer Schleife mit wechselnden Werten für die Argumente
aufgerufen und die Werte kontrolliert habe.
Hallo,
bin ich der einzige, dem aufgefallen ist, dass bei der Berechnung von
"bruch" eine Division durch Null durchgeführt wird? Es gilt:
nenner = (-(float)sinusVonLambda);
und
int sinusVonLambda = 0;
und dazwischen wird sinusVonLambda nicht modifiziert.
Abgesehen davon ist bruch als int definiert. Was dann dabei herauskommt
wage ich nicht zu beurteilen (hängt auch von den trigonometrischen
Funktionen ab). Insgesammt ist die Linearisierung ein kruder Mix aus int
und float, ich denke fast, dass hier durchgängig float oder aber eine
richtige Fixpunkt-Implementation besser wären.
Schöne Grüße,
Martin