Ich habe einen (wie ich dachte) recht kurzen C-Code geschrieben, der auf
meinem Raspberry Pi läuft. Da die CPU mit 700Mhz rennt, bin ich davon
ausgegangen, dass ich mir um die Rechenzeit keine Sorgen machen brauche!
Nun war mein Plan, die Funktion alle 100ms aufzurufen. Da ich keine
besonders berühmten C-Kentnisse besitze und es bei Nutzung eines
Betriebssystems nicht so leicht zu sein scheint, einen Timerinterrupt
die Funktion aufrufen zu lassen, habe ich einfach am Ende der Funktion
ein "usleep (100000)" eingefügt, um 100ms schlafen zu gehen.
Dabei habe ich bemerkt, dass pro Sekunde nur 8 Durchläufe stattfinden,
ich habe also eine betrachte Zusatzzeit durch die Programmausführung.
Dann habe ich einfach mal den Sleepbefehl gelöscht und das Programm "so
schnell es geht" laufen lassen, um die Einträge in der .txt pro Sekunde
zu zählen - und komme auf etwa 44 Durchläufe/s -> über 20ms für einen
Durchlauf!
Kleine Erklärung zum Programm: Es wird ein 8-Kanal 18-bit-AD-Wandler
über SPI abgefragt und die 18 empfangenenen Bytes werden dann jeweils
wieder in eine Long-Variable mit Bitmanipulation zusammengesetzt.
Zum Schluss werden die Werte zusammen mit dem aktuellen Zeitstempel in
eine .txt-Datei geschrieben (jeweils getrennt durch ein Tabstop, damit
Excel die Daten gut importieren kann).
Kann mir jemand sagen, was an meinem Programm soviel Zeit schluckt? Die
Ausführungsgeschwindigkeit ist zwar bisher ausreichend, aber das
Programm soll noch um einen zweiten 8-Kanal AD-Wandler (allerdings nur
mit 14bit-Auflösung) erweitert werden und dann müssen immernoch 10
Werte/s pro Kanal abgefragt werden können...
Im Endeffekt komme ich aber wohl nicht umhin, die Funktion irgendwie
automatisiert über einen Timer aufzurufen, nicht wahr...? Und außerdem
stört mich, dass ich durch abfragen der Unix-Zeit auf 1s Genauigkeit
beschränkt bin, bei 10 Werten/s haben immer 10 Werte den gleichen
Zeitstempel. Naja, da muss man dann eben abzählen :p
MoinMoin,
EGSler schrieb:> ...in eine .txt-Datei geschrieben...
vor allem das wird etwas dauern...
EGSler schrieb:> Und außerdem> stört mich, dass ich durch abfragen der Unix-Zeit auf 1s Genauigkeit> beschränkt bin, bei 10 Werten/s haben immer 10 Werte den gleichen> Zeitstempel.
schaue dir das mal an:
http://man7.org/linux/man-pages/man2/gettimeofday.2.html
Grüße Uwe
EGSler schrieb:> Ich habe einen (wie ich dachte) recht kurzen C-Code geschrieben, der auf> meinem Raspberry Pi läuft.
Kurz != schnell.
EGSler schrieb:> // Schreiben der Werte in eine .txt-Datei> Ziel = fopen ("Werte.txt", "a");> if (Ziel != NULL)> {> fprintf(Ziel, "%d.%d.%d %02d:%02d:%02d \t",> tmnow->tm_mday, tmnow->tm_mon + 1, tmnow->tm_year + 1900,> tmnow->tm_hour, tmnow->tm_min, tmnow->tm_sec);> fprintf(Ziel, "%d \t", AD1_Ch1);> fprintf(Ziel, "%d \t", AD1_Ch2);> fprintf(Ziel, "%d \t", AD1_Ch3);> fprintf(Ziel, "%d \t", AD1_Ch4);> fprintf(Ziel, "%d \t", AD1_Ch5);> fprintf(Ziel, "%d \t", AD1_Ch6);> fprintf(Ziel, "%d \t", AD1_Ch7);> fprintf(Ziel, "%d \n", AD1_Ch8);> fclose(Ziel);> }
Dateizugriffe fressen vergleichsweise viel Zeit. Warum machst du vor
jedem Schreibzugriff ein "fopen"? Öffne die Datei doch nur einmal und
speicher dir das Dateihandle irgendwo. Und warum schreibst du nicht alle
Daten mit einem einzigen "fprintf"?
Naa, nevermind... Kaum hab ich mir nach ewigem Überlegen diesen Text von
den Fingern getippt und auf Absenden geklickt, da fällt mir auf, dass es
die SPI-Übetragung selber ist... ;/
Ich hatte die Frequenz auf 7,6kHz gestellt, kein Wunder dass alleine
diese schon knapp 20ms dauert oO
Ok, aber mein letzter Absatz ist trotzdem gültig - um einen Timer komme
ich wohl nicht rum!
Uwe Berger schrieb:> schaue dir das mal an:> http://man7.org/linux/man-pages/man2/gettimeofday.2.html
Das werde ich mir mal anschaun, danke!
Daniel H. schrieb:> Warum machst du vor> jedem Schreibzugriff ein "fopen"? Öffne die Datei doch nur einmal und> speicher dir das Dateihandle irgendwo. Und warum schreibst du nicht alle> Daten mit einem einzigen "fprintf"?
Ich habe irgendwo gelesen, dass die geschriebenen Daten ggf. verloren
gehen können, wenn die Datei vom Programm noch geöffnet ist und dieses
dann unregelmäßig beendet wird. Daher schließe ich sie immer zur
Sicherheit... Ist das unnötig?
Ja, du hast Recht, ein einzelnder fprintf würde wohl genügen, ich fand
das nur unübersichtlicher. Würde das Zusammenlegen in einen Befehl die
Geschwindigkeit erhöhen?
Viele Grüße
EGSler schrieb:> Ich habe irgendwo gelesen, dass die geschriebenen Daten ggf. verloren> gehen können, wenn die Datei vom Programm noch geöffnet ist und dieses> dann unregelmäßig beendet wird. Daher schließe ich sie immer zur> Sicherheit... Ist das unnötig?
Mit "fflush" kannst du jederzeit den aktuellen Bufferinhalt in die Datei
schreiben lassen, dadurch reicht dann ein "fopen" zu Beginn deines
Programms und ein "fclose" am Ende.
EGSler schrieb:> Würde das Zusammenlegen in einen Befehl die Geschwindigkeit erhöhen?
Theoretisch ja, praktisch ist die Frage, was der Compiler alles
optimiert. Im Zweifel muss man Messungen durchführen.
EGSler schrieb:> Naa, nevermind... Kaum hab ich mir nach ewigem Überlegen diesen Text von> den Fingern getippt und auf Absenden geklickt, da fällt mir auf, dass es> die SPI-Übetragung selber ist... ;/> Ich hatte die Frequenz auf 7,6kHz gestellt, kein Wunder dass alleine> diese schon knapp 20ms dauert oO>> Ok, aber mein letzter Absatz ist trotzdem gültig - um einen Timer komme> ich wohl nicht rum!
In einem Timer solltest aber keine Dateien schreiben, das dauert
"unabsehbar" lange.
Oder mach dafür eine RAM disk, dort schreibst Du die Einträge in jeweils
separate Files und ein Cronjob oder dauernd laufendes Shell-Script
verarbeitet diese in eine Datei auf der physischen Platte oder SD-Karte.
Irgendwie ist das Bauklötzchen-Code.
Immer Einen nach dem Anderen.
Warum nicht mal zwei auf einmal?
Z.B. fprintf(Ziel, "%d \t%d \t", AD1_Ch1, AD1_Ch1); oder mehr.
open und close bei jedem Zugriff zeugt von: "Ich weiß nicht was ich
mache, aber das mache ich gründlich".
Daniel H. schrieb:> Schau dir mal Wiring Pi an: http://wiringpi.com/reference/timing/
Das gibt leider Konflikte mit der bcm2835.h. Beide definieren ähnliche
Punkte.
Ich versuche momentan mit gettimeofday, auch wenn ich da irgendwie
ebenfalls Probleme habe...
Amateur schrieb:> Warum nicht mal zwei auf einmal?
Ist doch alles schon besprochen (und mittlerweile geändert)! ;)
Erstens, mach den Printf weg, schreib binaer. Ohne Formatierung.
Zweitens. Wenn die Daten sowieso auf dem Controller sind, schreib in ein
Array, die kann man beim Beenden des Programmes immer noch in einen
Datei schreiben.
Man kann ein Fileschreib auch erzwingen, ohne zu schliessen, das waere
dann etwas wie flush()
EGSler schrieb:> Da ich keine besonders berühmten C-Kentnisse besitze und es bei Nutzung> eines Betriebssystems nicht so leicht zu sein scheint, einen> Timerinterrupt die Funktion aufrufen zu lassen, habe ich einfach am Ende> der Funktion ein "usleep (100000)" eingefügt, um 100ms schlafen zu gehen.
Besser geeignet für sowas ist die Funktion clock_nanosleep().
EGSler schrieb:> Im Endeffekt komme ich aber wohl nicht umhin, die Funktion irgendwie> automatisiert über einen Timer aufzurufen, nicht wahr...?
Kann man auch machen:
man setitimer
man signal
Uwe Berger schrieb:> EGSler schrieb:>> Und außerdem>> stört mich, dass ich durch abfragen der Unix-Zeit auf 1s Genauigkeit>> beschränkt bin, bei 10 Werten/s haben immer 10 Werte den gleichen>> Zeitstempel.>> schaue dir das mal an:> http://man7.org/linux/man-pages/man2/gettimeofday.2.html
Die Funktion clock_gettime() ist noch etwas flexibler und genauer.
Daniel H. schrieb:> Was für Probleme denn?
Ich habe oben geposteten Code (nur mittlerweile mit zusammengefasten
fprintf) in eine If-Abgrafe gepackt, um das Programm alle 100ms zu
starten:
1
structtimevaltv;
2
lonngcurrentmicros=0;
3
longpreviousmicros=0;
4
longinterval=100000;
5
while(1)
6
{
7
gettimeofday(&tv,NULL);
8
currentmicros=tv.tv_usec;
9
if(currentmicros-previousmcicros>interval)
10
{
11
previousmicros=currentmicros;
12
...
13
...
14
}
15
}
Aber der Code in meiner If-Abfrage wird deutlich häufiger ausgeführt,
eigentlich so schnell wie das Programm ansich durchlaufen kann.
Das wird sicherlich wieder nur ein dummer Fehler sein, aber ich finde
ihn nicht... ;(
@ Siebzehn Zu Fuenfzehn
Das werde ich mir alles mal anschaun, wenn das Programm ansich läuft.
Das sind ja eher "Optimierungen". Als Hardware-Mensch bin ich froh, wenn
das Program erstmal tut was es soll, denn bis Mitte Dezember sollte das
Projekt weitesgehend fertiggestellt sein.
EGSler schrieb:> lonng currentmicros = 0;> long previousmicros = 0;
unsigned long !
Wenn du so arbeiten willst
> if(currentmicros-previousmcicros > interval)
dann willst du auf jeden Fall unsigned haben.
Wenns das nicht ist, dann poste deinen kompletten Code und nicht nur ein
für das Forum zurechtgeschnittenes Schnipselchen. Du tust damit dir und
uns einen Gefallen. Häng das File einfach an das Posting an. Das ist für
dich und für uns am allereinfachsten.
EGSler schrieb:> Das wird sicherlich wieder nur ein dummer Fehler sein, aber ich finde> ihn nicht... ;(
Bitte kopiere den Code und tippe Ihn nicht ab. Meim Abtippen machst du
neue Fehler oder tippst den eigentlichen Fehler gar nicht ab. (Siehe das
"lonng")
So können wir nicht sehen wo im Orginal die Fehler liegen, sondern nur
raten: z.B.
* ein zusätzliches Semikolon hinter der if.
* long statt unsigned long, aber das sollte nicht greifen, da in usec
auch so 35 Minuten Platz finden würden aber nur 1 Sekunde gebraucht wird
(danach zählt der andere u_sec hoch.
EGSler schrieb:> Ich habe oben geposteten Code (nur mittlerweile mit zusammengefasten> fprintf) in eine If-Abgrafe gepackt, um das Programm alle 100ms zu> starten:> struct timeval tv;> lonng currentmicros = 0;
Ich glaube dir nicht, daß du dieses Programm hast laufen lassen, denn
einen Typ namens lonng gibt es nicht. Bitte den ECHTEN Code zeigen!
Vorher brauchen wir eigentlich nicht weiter diskutieren.
> long previousmicros = 0;> long interval = 100000;> while(1)> {> gettimeofday(&tv, NULL);> currentmicros = tv.tv_usec;> if(currentmicros-previousmcicros > interval)> {> previousmicros = currentmicros;> ...> ...> }> }> Aber der Code in meiner If-Abfrage wird deutlich häufiger ausgeführt,> eigentlich so schnell wie das Programm ansich durchlaufen kann.
Erstens mal ist das sehr schlecht. So produziert dein Programm dauerhaft
100% CPU-Last, zweitens kann das so nicht richtig funktionieren. Dei
Zeit, die gettimeofday liefert, besteht aus zwei Komponenten, und du
mußt auch beide nutzen.
Du hast hier Überlaufprobleme. tv_usec wird jede Sekunde wieder auf 0
gesetzt. "currentmicros" kann also durchaus kleiner sein als
"previousmicros" und trotzdem wären mehr als "interval" usecs vergangen.
(z.B mit currentmicros == 999999)
wobei this_tick eine Struktur vom Typ struct timespec ist, die am Anfang
mal passend initialisiert werden (clock_gettime) und dann vor jedem
Warten um 100 Millisekunden erhöht werden muß, etwa so:
Ich hab den Code von Hand abgeschrieben, da ich ihn sonst immer mit
einem USB-Stick auf den Rechner mit Internet kopieren muss.
Im Anhang ist mal mein komplettes Programm, aber ich habe nun den Fehler
gefunden:
1
if((currentmicros-previousmicros)>interval)
Hier brauche ich eine Klammer um meine Division! Wie kommt das? Ich habe
hier ein Buch über C liegen, in dem steht eine Tabelle über die
Reihenfolge von Bearbeitungsschritten. In dieser Tabelle sind die
Rechenoperatoren (wie in diesem Fall Minus) vor den Vergleichern
aufgelistet, sie werden also mit einer höheren Priorität ausgeführt. In
meinem Falle aber scheinbar nicht ;/
Dass ich ein Überlaufproblem nach 1s habe ist mir bewusst, da arbeite
ich noch dran...
@ Rolf Magnus
Ok, das ist auch interessant. Behebt zumindest mein Problem mit der
100%-igen CPU-Last. Allerdings bin ich dann wieder abhänig von der
Durchlaufzeit des Programms...
Maan, auf einem Atmega ist es so schön leicht, einen Compare-Interrupt
auf 100ms einzustellen^^
Mit C und Linux kommt wohl noch nen bissel was auf mich zu, zumal ich
fürchte, dass ihr auch über den Rest des Codes schimpfen werdet, da das
mit Sicherheit kein guter Stil ist^^
> Hier brauche ich eine Klammer um meine Division!
Subtraktion. Aber egal.
Nein, du brauchst keine Klammern.
Was immer der tatsächliche Fehler war, die Klammern lösen ihn nicht.
[/c]
fprintf(Ziel, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t",
[/c]
Immer noch falsch.
Für long ist die korrekte Angabe ein %ld!
Du musst das ernster nehmen. Das sind schwere Fehler, wenn man sich hier
vertut!
EGSler schrieb:> @ Rolf Magnus> Ok, das ist auch interessant. Behebt zumindest mein Problem mit der> 100%-igen CPU-Last. Allerdings bin ich dann wieder abhänig von der> Durchlaufzeit des Programms...
Nein, eben nicht. Das ist ja gerade der große Vorteil von
clock_nanosleep! Schau dir meinen Codeausschnitt nochmal genau an und
denke darüber nach.
EGSler schrieb:> Maan, auf einem Atmega ist es so schön leicht, einen Compare-Interrupt> auf 100ms einzustellen^^
Wie schon gesagt lässt sich das mit setitimer() und signal() auf einem
Linux-System sehr ähnlich umsetzen. Man definiert einen Timer mit einem
vorgegebenen Intervall, und der löst ein Signal aus. Dann braucht man
noch einen Signalhandler, der im Prinzip einer ISR sehr ähnlich ist.
@ Rolf Magnus
Dann wird wohl eines der beiden der richtige Weg sein, ich lese mir mal
beides genauer durch und probiere das aus.
@ Karl Heinz
Sicher habe ich die Hinweise dankend aufgenommen, aber da liegt ja
gerade nicht mein Problem, diese Ausführungen funktionieren ja.
Das werde ich dann umschreiben, wenn der Rest klappt.
Andererseits sind die "%ld" natürlich auch schnell umgeschrieben, das
kann ich gerade machen.
if(Ziel==NULL||fgetc(Ziel)!=90)// Wenn Datei nicht geöffnet werden konnte oder das erste Zeichen in der Datei kein "Z" ist
schreib das so
1
if(Ziel==NULL||fgetc(Ziel)!='Z')
es gibt wirklich keinen Grund, hier den ASCII Code von 'Z' selber
hinschreiben zu müssen.
Dann kannst du dir auch den Kommentar sparen.
Denn was eine Abfrage des File Handles auf NULL bedeutet, muss man einem
C Programmierer nicht erklären. Und der Vergleich mit 'Z' ist auch
selbst erklärend.
Grundsätzlich: Wenn du versucht bist, derartige Kommentare zu schreiben,
solltest du immer nach einer Möglichkeit suchen bzw. dir überlegen wie
du den Code anders schreiben kannst, so dass du diesen Kommentar nicht
benötigst. Wenn du die Zeit fürs Schreiben des Kommentars bzw. die
Anschläge beim Tippen in eine derartige Modifikation steckst, dann hast
du wesentlich mehr davon, als von solchen Kommentaren. Der beste
Kommentar ist immer noch derjenige, der nicht gebraucht wird, weil alles
was im Kommentar stehen würde, schon im Code ersichtlich ist.
Gegebenenfalls muss man dann eben Variablennamen ein wenig ändern oder
Funktionsnamen ein wenig ändern oder sich die jeweils beste Darstellung
von konstanten Werten raussuchen (wie hier die Angabe des 'Z' anstelle
der gleichwertigen 90). Das alles rechnet sich aber für die Zukunft. Du
wirst sehen, dass du signifikant weniger blödsinnige Fehler machst, weil
du dir die Informationen nicht mehr aus Kommentaren holst, die aufgrund
Programmänderungen schon längst nicht mehr stimmen, sondern aus dem Code
selber, den du so formuliert hast, dass du in wenigen Sekunden den Sinn
erfassen kannst.
Hi Daniel,
Daniel H. schrieb:> Mit "fflush" kannst du jederzeit den aktuellen Bufferinhalt in die Datei> schreiben lassen, dadurch reicht dann ein "fopen" zu Beginn deines> Programms und ein "fclose" am Ende.
Das fflush(3) macht aber noch kein fsync(2). Anders gesagt: nach einem
fflush(3) stehen die Daten immer noch nicht auf der Festplatte (hier:
SD-Card), sondern nur im Dateipuffer des Betriebssystems.
HTH,
Karl
EGSler schrieb:> @ Rolf Magnus> Dann wird wohl eines der beiden der richtige Weg sein, ich lese mir mal> beides genauer durch und probiere das aus.>> @ Karl Heinz> Sicher habe ich die Hinweise dankend aufgenommen, aber da liegt ja> gerade nicht mein Problem, diese Ausführungen funktionieren ja.
'funktionieren' bedeutet noch lange nicht, dass es richtig ist.
EGSler schrieb:> @ Rolf Magnus> Dann wird wohl eines der beiden der richtige Weg sein, ich lese mir mal> beides genauer durch und probiere das aus.
Um das kurz zu erläutern: Bei clock_nanosleep() kann man im Gegensatz zu
usleep() auch eine absolute Zeit angeben, also man kann ihm nicht nur
sagen "warte für x Zeiteinheiten", sondern auch "Warte bis zum Zeitpunkt
x". Mit usleep() sieht es in Pseudocode ungefähr so aus:
1
schleife
2
{
3
irgendwas();
4
warte(100 ms);
5
}
Wenn irgendwas() jetzt 20 ms braucht, dauert der Gesamtzyklus 120 ms,
wenn es 50 ms braucht, dauert er 150 ms. Mit clock_nanosleep() kann man
das aber auch so machen:
1
x = aktuelle_zeit();
2
schleife
3
{
4
irgendwas();
5
x += 100 ms;
6
warte_bis_zeitpunkt(x);
7
}
Hier ist die Zykluszeit immer 100 ms, unabhängig davon, ob irgendwas()
nun 5, 20 oder 50 ms braucht. Erst wenn es mehr als 100 ms braucht,
kommt es natürlich zu Zykluszeitverletzungen, aber dann ist das System
einfach überlastet.
@Karl-Heinz
Da hast du natürlich Recht. Ich war von der Informationsflut kurzeitig
ein wenig überfordert, ich habe mich wieder berappelt^^
Ja, mit den Kommentaren hast du Recht. Ich dachte ich müsste auf "90"
abfragen, da meine Variable ja ein int ist.
Man sieht vermutlich alleine schon an meinen Kommentaren, wie viel
Erfahrung ich mit Software bereits habe...^^ Aber mit Büchern und
Internet kommt man gut vorwärts =)
@ Rolf
Ahh, das ist natürlich super. So hatte ich das bisher nicht verstanden.
Dann ist mein Zeit-Problem ja mit dieser Funktion ziehmlich gut zu
lösen, schätze ich!
Danke für eure Tipps bisher! =)