Hallo,
ich möchte an memcmp als 2. Argument den Rückgabewert einer Funktion
übergeben. Beim Rückgabewert handelt es sich um ein struct.
Folgender Code lässt sich zwar fehlerfrei compilieren (AVRStudio 5),
aber funktioniert nicht wie ich will:
Die Parameter, die an addSpanToTime übergeben werden, sollen nicht
verändert werden.
Was ist an obigem Code falsch? Ich habe schon Verschiedenes probiert,
komme aber auf keine funktionierende Lösung.
"time" ist in addSpanToTime eine lokale Variable auf dem Stack. Wenn die
Funktion zurückkehrt, verschwindet die Variable, und an dieser Stelle
auf dem Stack steht nun irgendetwas anderes. Du gibst aber einen Pointer
auf eben diese Stelle zurück, und arbeitest damit weiter - das kann nur
schief gehen.
Micha schrieb:> Was ist an obigem Code falsch?
Du gibt einen Zeiger auf eine lokale Variable zurück (ein Parameter ist
letztenlich nichts anderes). Wenn du diesen Zeiger nutzen willst,
existiert die Variable nicht mehr, weil die Funktion verlassen wurde.
1. "funktioniert nicht" ist fast die schlechteste mögliche Beschreibung
2. der Parameter einer Funktion wird beim Aufruf auf dem Stack abgelegt
und ist nur bis zum Ende der Funktion verfügbar.
Du gibst aber dessen Adresse zurück, und die wird verwendet, wenn der
Speicher schon wieder für etwas anderes benutzt wird.
Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu
vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit
zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt
dem entspricht, was du dir drunter vorstellst.
Vielen Dank für die Berichtigung. Dann muss ich eine Kopie des structs
in main anlegen, den Inhalt dort rein kopieren und damit arbeiten.
Klaus Wachtler schrieb:> "funktioniert nicht" ist fast die schlechteste mögliche Beschreibung
Naja es passiert halt Blödsinn. Vor dem return &time steht irgendetwas
in time.
A. K. schrieb:> Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu> vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit> zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt> dem entspricht, was du dir drunter vorstellst.
Wie kann sichergestellt werden, dass das nicht passiert, oder muss jedes
Element der Struktur einzeln verglichen werden?
Das struct hat folgende Typen als Member: uint16, uint8, uint8, uint8,
uint8 in dieser Reihenfolge, allerdings soll CurrentTime darüberhinaus
noch uint8, uint16, uint16 zusätzlich beinhalten.
Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist
welche Zeit einen früheren/späteren Zeitstempel darstellt.
Micha schrieb:> Vielen Dank für die Berichtigung. Dann muss ich eine Kopie des structs> in main anlegen, den Inhalt dort rein kopieren und damit arbeiten.
Nicht unbedingt.
Sagt ja keiner, dass du aus der Funktion heraus nicht auch ein
Strukturobjekt rausgeben darfst. Es darf halt kein Pointer sein, aber
Strukturobjekte sind in Ordnung.
Allerdings erhebt sich ob der Kopiererzeugung die Frage, ob eine
Spezialfunktion die genau die Fragestellung löst, die du hier mit 2
Funktionsaufrufen zu klären versuchst, nicht die bessere Variante ist.
>> dem entspricht, was du dir drunter vorstellst.> Wie kann sichergestellt werden, dass das nicht passiert, oder muss jedes> Element der Struktur einzeln verglichen werden?
Genau so wird das gemacht.
> Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist> welche Zeit einen früheren/späteren Zeitstempel darstellt.
solange, bis du aus irgendeinem Grund die Member der Struktur in eine
andere Reihenfolge bringst. Dann fliegt dein Programm aus heute
naheliegenden Gründen kräftig auf die Schnauze. Ob der Grund allerdings
in einem halben Jahr dann auch so naheliegend ist, ist eine ganz andere
Geschichte. Denn dann denkst du da nicht mehr daran und suchst dir erst
mal einen Wolf nach dem Problem.
Es hat schon seinen Grund, warum Zeiten normalerweise programmintern
nicht in der Form Sekunden,Minuten,Stunden,Tag,Monat,Jahr gespeichert
werden, sondern als Differenz von zb Sekunden zu einer Referenzzeit.
Denn dann kann man leicht damit rechnen. Und wenn ein Programm oft mit
Zeiten rechnen muss, dann lohnt es sich die interne Darstellung von der
externen zu trennen und nur dann in die externe Form zu konvertieren,
wenn auch wirklich eine Ausgabe ansteht (was in den meisten Programmen
viel seltener notwendig ist als irgendwelche Rechnungen oder
Vergleiche).
Micha schrieb:> Wie kann sichergestellt werden, dass das nicht passiert,
Man kann Strukturen so definieren, dass weder mittendrin noch am Ende
irgendwelche vom Compiler automatisch eingefügten Füllbytes auftreten.
Man kann sie auch gepackt definieren, dann entfallen diese meist
ebenfalls.
Man kann andererseits auch sicherstellen, dass in den Löchern 0
drinsteht, indem man lokale Strukturen grundsätzlich mit memset()
vorbesetzt. Bei globalen ist das sowieso schon der Fall.
Beides sind aber ziemlich unsaubere und fehleranfällige Methoden und
nicht zu empfehlen.
> oder muss jedes> Element der Struktur einzeln verglichen werden?
Das ist eindeutig der sicherste Weg.
> Das schöne an memcmp wäre in diesem Fall, dass auch gleich klar ist> welche Zeit einen früheren/späteren Zeitstempel darstellt.
Ganz böse Falle. Das funktioniert nur bei big endian Byteorder. Denn
wenn in memcmp byteweise verglichen wird, dann wird bei little endian
erst das untere dann das obere Byte verglichen, was bei uint16 (oder
grösser) nicht wirklich zum gewünschten Ergebnis führt.
Karl Heinz Buchegger schrieb:> Allerdings erhebt sich ob der Kopiererzeugung die Frage, ob eine> Spezialfunktion die genau die Fragestellung löst, die du hier mit 2> Funktionsaufrufen zu klären versuchst, nicht die bessere Variante ist.
Das Ergebnis von memcmp soll Teil einer komplexeren if-Abfrage werden
und u.U. ist das Ergebnis vorher schon false und memcmp wird gar nicht
mehr ausgeführt.
A. K. schrieb:> Abgesehen von dem bereits erwähnten Problem: Strukturen mit memcmp zu> vergleichen ist ziemlich gefährlich, weil unsichtbare Löcher mit> zufälligem Inhalt drin sein können, das Ergebnis also nicht unbedingt> dem entspricht, was du dir drunter vorstellst.
Gilt das auch für die "Behandlung" von Strukturen mittels memcpy,
eeprom_block_read und eeprom_block_write wenn Quell- und Zielstruktur
identisch sind?
Micha schrieb:> Gilt das auch für die "Behandlung" von Strukturen mittels memcpy,> eeprom_block_read und eeprom_block_write wenn Quell- und Zielstruktur> identisch sind?
Nein, das ist kein Problem.
Karl Heinz Buchegger schrieb:> Es hat schon seinen Grund, warum Zeiten normalerweise programmintern> nicht in der Form Sekunden,Minuten,Stunden,Tag,Monat,Jahr gespeichert> werden, sondern als Differenz von zb Sekunden zu einer Referenzzeit.> Denn dann kann man leicht damit rechnen. Und wenn ein Programm oft mit> Zeiten rechnen muss, dann lohnt es sich die interne Darstellung von der> externen zu trennen und nur dann in die externe Form zu konvertieren,> wenn auch wirklich eine Ausgabe ansteht (was in den meisten Programmen> viel seltener notwendig ist als irgendwelche Rechnungen oder> Vergleiche).
Das leuchtet ein. Reichen würde es mir als uint32 ab 1.1.2000, da ich
nicht damit rechne, dass ich das Gerätchen 2136 noch benutzen werde. Wie
sieht es nun aber aus, wenn die Zeit gelegentlich an einen PC geschickt
und von diesem aktualisiert werden soll? Dort liegt die Zeit ja als
uint64 vor. Welche Vorgehensweise empfiehlt um einfach umrechnen zu
können?
Micha schrieb:> Dort liegt die Zeit ja als uint64 vor
Tut sie das?
Solltest Du damit das windows-interne Zeitformat SYSTEMTIME meinen, so
ist eine Umrechnung leicht möglich, die zählt 100nsec-Intervalle seit
1604. Also den SYSTEMTIME-Wert durch 10000 teilen, und schon hast Du die
Sekunden seit 1604. Dann noch einen geeigneten Offset abziehen, und Du
landest beim von Dir gewünschten Wert.
Alternativ könntest Du auch die Zeitbehandlungsfunktionen der
C-Runtime-Library verwenden und den üblichen time_t-Wert bestimmen, der
Sekunden seit 1970 zählt.
A. K. schrieb:>> oder muss jedes>> Element der Struktur einzeln verglichen werden?>> Das ist eindeutig der sicherste Weg.
sicherlich nicht.
Irgend wann erweitert man seine Struktur und vergisst die
Vergleichsroutine
Micha schrieb:> Innerhalb SYSTEMTIME liegt die Zeit ja nicht direkt vor, sondern> muss erst berechnet werden, richtig? Bzw. nach FILETIME umgewandelt
Ja, sorry, das hatte ich durcheinandergebracht. Natürlich, FILETIME, den
Typ meinte ich.
Du könntest aber auch die Funktion time verwenden, die liefert den
allgemein üblichen Wert in ganzen Sekunden seit 1.1.1970.
Micha schrieb:> Die Parameter, die an addSpanToTime übergeben werden, sollen nicht> verändert werden.
Ich sehe Deine Parameter-Wahl jedoch als suboptimal an.
Würdest Du als ersten Parameter einen Pointer auf Deine Struct
akzeptieren, wäre Dein Problem, dass time eine lokale Variable ist,
sofort beseitigt. Ausserdem bräuchtest Du den Pointer auf die Struct
nicht mehr als Return-Wert zurückzugeben, da dieser ja als Argument
bereits da ist. Du könntest dann den Rückgabewert für etwas sinnvolleres
benutzen.
Wenn Du tatsächlich eine Kopie der struct brauchst, dann dupliziere die
struct in der aufrufenden Funktion und gib 2 Pointer runter, der eine
ist dann Pointer auf die Source-Struct und der andere der Pointer auf
die Target-Struct.
Meines Erachtens kann in C auf das Feature, structs by value
heruntergeben zu können, komplett verzichtet werden. Das macht nur
Ärger.
Vlad Tepesch schrieb:> sicherlich nicht.> Irgend wann erweitert man seine Struktur und vergisst die> Vergleichsroutine
Was wäre die Alternative? Abgesehen davon, das Programmieren bleiben zu
lassen und auf Gärtner umzuschulen. Denn dieser Aspekt tangiert saubere
Programmierung und Modularisierung. Wenn man den Code zur Strukt an
einer Stelle konzentriert, und man es dann immer noch vergisst, dann
wärs Zeit für die erwähnte Umschulung.
Zumindest fällt mir keine Lösung ein, die innerhalb der Vorgabe bleibt,
eine Struct zu verwenden. Und in der du bei Vergesslichkeit nicht auf
die Schnauze fliegst. Zumal alle memcmp-Varianten aufgrund inhärent
plattformabhängigen Verhaltens bei Inhalten >8Bit für mich ausscheiden.
Dass man Timestamps, die mit grösser/kleiner verglichen werden, auch als
geschlossene Integer darstellen kann wurde schon erwähnt. Wenn der
Vergleich aber das einzige Motiv für diese Darstellung sein sollte, dass
ist das aufgrund der Konvertierung unverhältnismässig aufwendig.
Frank M. schrieb:> Meines Erachtens kann in C auf das Feature, structs by value> heruntergeben zu können, komplett verzichtet werden. Das macht nur> Ärger.
Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit
struct-By-Value gestolpert. Kannst du ein Beispiel nennen?
Simon K. schrieb:> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit> struct-By-Value gestolpert. Kannst du ein Beispiel nennen?
Abgesehen von der miesen Performance kann Dir durchaus der Stack
überlaufen, wenn Du Structs mit vielen bzw. großen Struct-Members an
Funktionen runtergibst. Schließlich ist die Stack-Größe gerade auf
Mikrocontrollern begrenzt.
Aber selbst auf Unix-/Linux-/Windows-Systemen mache ich das nicht,
schließlich müssen alle Struct-Members kopiert werden - auch wenn Du nur
einen Wert aus der Struct in der aufgerufenen Funktion nutzt. Sowas ist
einfach Ressourcen-Verschwendung ohne Ende.
Frank M. schrieb:> Simon K. schrieb:>> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit>> struct-By-Value gestolpert. Kannst du ein Beispiel nennen?>> Abgesehen von der miesen Performance kann Dir durchaus der Stack> überlaufen, wenn Du Structs mit vielen bzw. großen Struct-Members an> Funktionen runtergibst. Schließlich ist die Stack-Größe gerade auf> Mikrocontrollern begrenzt.> Aber selbst auf Unix-/Linux-/Windows-Systemen mache ich das nicht,> schließlich müssen alle Struct-Members kopiert werden - auch wenn Du nur> einen Wert aus der Struct in der aufgerufenen Funktion nutzt. Sowas ist> einfach Ressourcen-Verschwendung ohne Ende.
Nach allgemeinem Tenor habe ich es so verstanden, dass der Compiler ab
einer bestimmten Größe automatisch "im Hintergrund" By-Reference
übergibt.
Nachgeprüft habe ich es noch nicht.
Simon K. schrieb:> Nach allgemeinem Tenor habe ich es so verstanden, dass der Compiler ab> einer bestimmten Größe automatisch "im Hintergrund" By-Reference> übergibt.
"Ab einer bestimmten Größe" klingt ziemlich schwammig, oder? ;-)
Was passiert denn dann, wenn Du einen Struct-Member in der aufgerufenen
Funktion änderst, wenn "ab einer bestimmten Größe" die Struct "im
Hintergrund" By-Reference heruntergegeben wird?
> Nachgeprüft habe ich es noch nicht.
Ich kann da nur vermuten, nämlich folgendes:
1. Ab einer bestimmten Größe wird die Struct vor dem Aufruf der Funktion
bereits kopiert
2. Es wird eine Referenz (sprich Pointer) auf die Kopie heruntergegeben.
Das könnte klappen: Aber die Kopie der Struct liegt trotzdem im SRAM -
egal ob Stack, Heap oder sonstwo - und kann den µC-Speicher zum platzen
bringen.
Ich persönlich finde es einfach unschön - weil schlecht kalkulierbar,
was da passiert. Und wie gesagt: Die Kopiererei kostet einfach nur
Ressourcen. Wenn ich wirklich eine Kopie brauche, dann sollte ich auch
selbst die Struct kopieren. Dann weiß ich wenigstens, was da abgeht.
Frank M. schrieb:> Meines Erachtens kann in C auf das Feature, structs by value> heruntergeben zu können, komplett verzichtet werden. Das macht nur> Ärger.
lustigerweise war das sogar mal so.
In den aller-aller-aller-ersten C Versionen wurden Strukturen genau so
übergeben, wie es mit Arrays heute noch passiert. Es wurde immer ein
Pointer übergeben. Bei kleinen Strukturen kann das dann auch schon mal
kontraproduktiv sein.
Übrigens:
Das mit dem Ärger würde ich nicht unterschreiben.
Das man als Programmierer mitdenken muss, ist unumstritten. Ob eine
Funktion einen Pointer nimmt oder doch gleich das ganze Objekt verrät
dem Aufrufer auch, was die Funktion vorhat.
foo( const struct Obj* pObj )
ich kann mich drauf verlassen, dass die Funktion das Objekt nicht
verändern wird
foo( struct Obj* pObj )
ich muss damit rechnen, dass die Funktion das Objekt nicht verändern
wird
foo( struct Obj obj )
genau wie der erste Punkt: ich kann mich darauf verlassen, dass die
Funktion das Objekt nicht verändert, allerdings benötigt die Funktion
eine Kopie des Objektes (aus welchem Grund auch immer)
> Und wie gesagt: Die Kopiererei kostet einfach nur> Ressourcen. Wenn ich wirklich eine Kopie brauche, dann sollte> ich auch selbst die Struct kopieren. Dann weiß ich wenigstens,> was da abgeht.
Damit hast du aber nichts (*) gewonnen.
Wenn eine Kopie notwendig ist, dann ist sie notwendig.
Ob die Kopie durchs Argument Passing erzeugt wird, oder ob sich die
Funktion selbst die Kopie erzeugt, ist ziemlich egal.
(*) ok. fast nichts
Du kannst den Speicher für die Kopie per malloc erzeugen und dann auf
'out of memory' reagieren. Mehr Vorteile hast du aber nicht.
Simon K. schrieb:> Ich bin in meiner bisherigen Karriere noch nicht über Probleme mit> struct-By-Value gestolpert.
Ich schon. Anfangs neigten Compiler dazu, struct returns über statischen
Speicher abzuwickeln. Das war natürlich nicht reentrant. Ist aber schon
ein paar Jahrzehnte her.
Karl Heinz Buchegger schrieb:> lustigerweise war das sogar mal so.
Ich kenne das auch noch, ich habe damals C im Jahr 1984 auf einem
UNIX-SYSTEM-V (VME-BUS-)System auf MC68020-Basis von der Pike auf
gelernt - natürlich mit einem K&R in der Hand ;-)
Viele der späteren Erweiterungen in C (ANSI, C99) sind zwar sinnvoll,
einige halte ich aber nachwievor für überflüssig. Ich programmiere meist
"defensiv", d.h. ich programmiere im Zweifel nach "altem Stil". Dann
weiß ich wenigstens, was passiert. Und ich käme niemals im Leben auf die
Idee, eine Struct by-value herunterzugeben.
Kennt jemand eine Funktion aus der libc, die eine struct by-value als
Argument erwartet? Ich nicht. Für Struct-Übergabe über Pointer kann ich
aber jede Menge Beispiele nennen ;-)
> Das mit dem Ärger würde ich nicht unterschreiben.
Okay, da habe ich vielleicht etwas übertrieben. Ich will nur
gewährleisten, dass meine Funktion das erwartete auch tut. Deshalb
begebe ich mich im Zweifel nicht in irgendwelche Abenteuer. Da weiß ich,
was ich habe.
Karl Heinz Buchegger schrieb:> Damit hast du aber nichts (*) gewonnen.
Doch: Sicherheit.
Ausserdem kenne ich kaum Anwendungsfälle, wo ich eine Struct als Kopie
lediglich temporär in einer Funktion brauche, d.h. eine Struct auf dem
Stack. Meist brauche ich Structs und deren Inhalte global über mehrere
Funktionen hinweg.
Gruß,
Frank
Frank M. schrieb:> Kennt jemand eine Funktion aus der libc, die eine struct by-value als> Argument erwartet? Ich nicht. Für Struct-Übergabe über Pointer kann ich> aber jede Menge Beispiele nennen ;-)
Nichts ist in C so präsent, wie all die Designentscheidungen, die in der
Frühzeit von C von K&R persönlich getroffen wurden :-)
Gerade in professionellen Libraries sieht man auch oft die Übergabe über
einen Pointer, deshalb meine Frage, warum man das macht.
Die aufgeführten Punkte für und dagegen sind interessant.
Frank M. schrieb:> Ausserdem kenne ich kaum Anwendungsfälle, wo ich eine Struct als Kopie> lediglich temporär in einer Funktion brauche, d.h. eine Struct auf dem> Stack. Meist brauche ich Structs und deren Inhalte global über mehrere> Funktionen hinweg.
Ich konstruiere mal
1
typedefstruct{
2
uint8_tred;
3
uint8_tgrn;
4
uint8_tblu;
5
}RGB;
6
7
RGBblend(RGBcolor1,RGBcolor2,doublet)
8
{
9
RGBresult;
10
result.red=color1.red*t+color2.red*(1.0-t);
11
result.grn=color1.grn*t+color2.grn*(1.0-t);
12
result.blu=color1.blu*t+color2.blu*(1.0-t);
13
14
returnresult;
15
}
16
17
RGBwhite={255,255,255};
18
RGBblack={0,0,0};
19
RGBblue={0,0,255};
20
21
intmain()
22
{
23
RGBgray;
24
25
gray=blend(black,white,0.4);
26
}
sollte klar sein, was das macht. Zudem ist es straight forward und
simpel.
Jetzt das ganze komplett mit Pointern
Ich seh da keinen großen Unterschied in der Sicherheit, ausser das mir
die 2.ter Version auch relativ leicht ermöglicht das hier zu tun
1
intmain()
2
{
3
RGB*gray;
4
5
blend(&black,NULL,0.4,&gray);
6
}
was in der ersten Version syntaktisch schon mal gar nicht möglich ist.
Die Verantwortung liegt in diesem Fall bei der Funktion, die prüfen muss
ob die Pointer gültig sind oder nicht.
Und ob der Compiler jetzt ein RGB Objekt an die Funktion übergibt oder
erst mal einen Pointer erzeugen muss, das schenkt sich gegenseitig
wirklich nicht viel.
Ganz abgesehen davon, dass mir die Pointer Version das Verwenden der
Funktion zb in Argumentlisten erschwert.
Gut. ist jetzt bei Farben nicht so das Thema. Man wird eher selten
schreiben
dirty_blue = blend( blend( black, white, 0.3 ), blue, 0.7 );
aber möglich wäre es. Und zb bei Vektoren kann das dann schon anders
aussehen
distance = vec_len( vec_sub( end, start ) );
Aber wie so oft gilt: "One size fits all" funktioniert bei
Baseball-Kappen, aber nicht in der Programmierung. 'Das' Patentrezept
gibt es nicht.
Simon K. schrieb:> Gerade in professionellen Libraries sieht man auch oft die Übergabe über> einen Pointer, deshalb meine Frage, warum man das macht.
ich würde sagen:
Weil man es meistens nicht mit so kleinen Strukturen zu tun hat, wie die
die ich da oben (mit Absicht) skizziert habe.
Dazu kommt, dass Libraries dem Compiler normalerweise nicht in
Source-Form zur Verfügung stehen, d.h. mit inlining ist da dann nicht
mehr viel zu machen damit der Compiler das Erzeugen der Kopie unter
Umständen verhindern kann. Denn die blend Funktion von oben kann ein
Compiler relativ problemlos inlinen und landet dann anstelle von
1
intmain()
2
{
3
RGBgray;
4
5
gray=blend(black,white,0.4);
6
}
bei
1
intmain()
2
{
3
RGBgray;
4
5
gray.red=black.red*0.4+white.red*0.6;
6
gray.grn=black.grn*0.4+white.grn*0.6;
7
gray.blu=black.blu*0.4+white.blu*0.6;
8
}
(in diesem Fall sind black und white Konstante. Nehmen wir mal an, das
wären sie nicht: Besser gehts nicht mehr. Das ist schon optimaler Code.
Das erzeugen der Kopie ist komplett weggefallen)
Da hab ich ja was angestossen... ;-)
Ich werde die Zeit jetzt als uint64 implementieren, dann muss ich mich
nicht mit den einzelnen Membern rumschlagen und kann sie direkt mit
Windows (und Linux?) austauschen. Außerdem werden Rechnungen, Vergleiche
und sonstige Operationen vereinfacht, was nicht unbedingt negativ ist.
Danke euch allen für die Anregungen und Tipps.
Rufus Τ. Firefly schrieb:> so ist eine Umrechnung leicht möglich, die zählt 100nsec-Intervalle seit> 1604. Also den SYSTEMTIME-Wert durch 10000 teilen, und schon hast Du die> Sekunden seit 1604.
Du verunsicherst mich: es muss doch durch 10000000 geteilt werden,
nicht?
Micha schrieb:> Du verunsicherst mich:
Ist nicht beabsichtigt gewesen. Ja, wenn nicht Millisekunden gewünscht
sind, ist "Dein" Quotient der richtigere. (Keine Ahnung, warum ich
ausgerechnet an Millisekunden denken musste)
if(TimeError>=CPU_CYCLES_BETWEEN_INTERRUPTS)// RTC too fast
17
{
18
//InterruptTicks += 0 // add zero ticks
19
TimeError-=CPU_CYCLES_BETWEEN_INTERRUPTS;
20
}
21
elseif(TimeError<=-CPU_CYCLES_BETWEEN_INTERRUPTS)// RTC too slow
22
{
23
InterruptTicks+=2;// add two ticks
24
TimeError+=CPU_CYCLES_BETWEEN_INTERRUPTS;
25
}
26
else
27
{
28
InterruptTicks+=1;// add one tick
29
}
30
31
// compute time every second
32
if(InterruptTicks>=F_INTERRUPTS)// one second has passed
33
{
34
CurrentTime+=ONE_SECOND;// add one second
35
TimeError+=ClockDeviation;// accumulate deviation
36
InterruptTicks-=F_INTERRUPTS;// subtract number of interrupts per second
37
}
38
}
Wenn ich nun eine neue Zeitinformation als uint64 vom PC empfange,
schreibe ich diese 1:1 nach CurrentTime. Für die RTC sollte ich nun aber
auch InterruptTicks aktualisieren. Folgender Code würde das richtige
Ergebnis liefern:
Mit dem entstehenden Rundungsfehler könnte ich sogar leben, aber der
Programmspeicher des verwendeten Mega168 ist damit zu 99.9% belegt.
Ich könnte ONE_SECOND auch in einer Schleife von CurrentTime
subtrahieren um die Modulo-Operation zu umgehen, aber die
Ausführungszeit wäre wohl trotzdem recht lange, da die Schleife
möglicherweise sehr oft durchlaufen werden muss.
Wie könnte ich das noch optimieren?
Wie wäre es, wenn Du auf dem PC (auf dem wohl offensichtlich von Dir
selbst geschriebene Software läuft) die Umrechnung erfolgen lässt und
statt des uint64_t, von dem Du große Teile wegwerfen willst, gleicht den
richtigen Sekundenwert überträgst? Dann kannst Du ONE_SECOND auf 1
setzen, was die Rechnerei in Deiner Interruptroutine vereinfacht, und
außerdem brauchst Du dann schlichtweg auf Deinem µC nicht mehr mit
uint64_t zu hantieren.
Rufus Τ. Firefly schrieb:> Wie wäre es, wenn Du auf dem PC (auf dem wohl offensichtlich von Dir> selbst geschriebene Software läuft) die Umrechnung erfolgen lässt und> statt des uint64_t, von dem Du große Teile wegwerfen willst, gleicht den> richtigen Sekundenwert überträgst?
Das wäre das einfachste, ja. Ich würde allerdings die Uhr gerne über den
PC abgleichen können (durch verändern von ClockDeviation), also uC-Uhr
zu PC-Uhr. Da der Controller über V-USB mit selbstgeschriebener
C#-Software kommuniziert spielen da noch ordentlich Verzögerungen mit
rein, das ist mir klar. Nichtsdestotrotz denke ich, dass das Ergebnis
besser wird je genauer die übertragene Zeit ist.
Micha schrieb:> rein, das ist mir klar. Nichtsdestotrotz denke ich, dass das Ergebnis> besser wird je genauer die übertragene Zeit ist.
Man kanns auch übertreiben.
Für eine normale Uhr und normalen Uhrenbetrieb reicht 1 Sekunde meistens
locker aus. Ob eine µC Uhr 1 Sekunde vor oder nach geht, spielt in >98%
aller Anwendungen überhaupt keine Rolle.
Karl Heinz Buchegger schrieb:> Man kanns auch übertreiben.> Für eine normale Uhr und normalen Uhrenbetrieb reicht 1 Sekunde meistens> locker aus. Ob eine µC Uhr 1 Sekunde vor oder nach geht, spielt in >98%> aller Anwendungen überhaupt keine Rolle.
Das ist klar und darum ging es mir gar nicht. Entschuldigung für die
missverständliche Erklärung.
Mein Gedankengang war folgender:
- Uhr am PC wird genau gestellt, an das USB Device gesendet und in eine
XML-Datei geschrieben
- nach (bspw.) 2 Monaten wird die Uhr des PCs erneut genau gestellt und
die Zeit vom Device gelesen
- nun wird die vergangene Zeit seit dem ersten Abgleich berechnet und
die daraus resultierende Abweichung der Quarzfrequenz
- dieser Wert wird als Korrekturwert für die RTC im Device benutzt
Angenommen die Verarbeitung und Übertragung der Zeiten dauert je 1 Sek.,
dann sind das 2 Sek. Abweichung in 2 Monaten, bei längerem Abstand
zwischen den Kalibrierungen entsprechend weniger.
Wieviele Schwingungen macht dein Quarz in 1 Sekunde.
Du wirst ja eine Variation des Problems machen: Wieviele
Quarzschwingungen muss ich abzählen, bis 1 Sekunde vergangen ist. Das
wiederrum bedeutet ja nichts anderes als dass du eine ganze Zahl
brauchst, die als Vergleichswert herhalten muss.
Wenn jetzt deine Uhr in 2 Monaten um 2 Sekunden falsch geht, wieviele
Schwingungen Different sind das dann in 1 Sekunde?
Wie sinnvoll ist es, den Quarz bis auf die letzte Schwingung
auszumessen, wenn er durch Veränderungen der Umgebungstemperatur in
seiner genauen Frequenz wesentlich mehr als diese 1 Schwingung sich
verändert?
Karl Heinz Buchegger schrieb:> Wenn jetzt deine Uhr in 2 Monaten um 2 Sekunden falsch geht, wieviele> Schwingungen Different sind das dann in 1 Sekunde?
Wenn ich mich nicht verrechnet habe 4,5.
> Wie sinnvoll ist es, den Quarz bis auf die letzte Schwingung> auszumessen, wenn er durch Veränderungen der Umgebungstemperatur in> seiner genauen Frequenz wesentlich mehr als diese 1 Schwingung sich> verändert?
Punkt für dich.
Immerhin könnte aber die fertigungsbedingte Toleranz verringert werden.
Micha schrieb:> Immerhin könnte aber die fertigungsbedingte Toleranz verringert werden.
Das ist schlecht ausgedrückt. Ersetze verringert durch ausgeglichen.
Aber das meintest du ja gar nicht. Ich werde jetzt machen was Rufus
vorgeschlagen hat: die Sekunden direkt in einem uint32 ablegen.
Zusätzlich übertrage ich vielleicht noch die ms als uint16 und
verheirate die mit InterruptTicks.