Ich habe selbst ein Programm geschrieben, um die Funktionen millis () und delay () ohne die Arduino-Bibliothek zu implementieren. Ich habe eine Zählvariable eingefügt, die jede Sekunde zählt und ihren Wert jede Sekunde über die serielle Schnittstelle sendet. Was ich fand, ist, dass sein Wert alle 3 Minuten um fast 2 Sekunden vom wahren Wert abweicht. Stimmt etwas mit meinem Code nicht? Oder ist das Serial.print() der Schuldige, der diese Routine verzögern kann? Wie lange dauert die Ausführung von Serial.print()?
Hier ist der Code:
Bearbeiten: Ich habe den Code so bearbeitet, aber die Zählung auf dem Arduino verzögert sich nach 4 Minuten immer noch um 4 Sekunden. Es verzögert sich nach 10 Minuten um 13 Sekunden, dh es zählt nur 587 Sekunden nach tatsächlichen 600 Sekunden.
Bearbeiten 2: Hier ist mein aktualisierter Code. Dennoch gibt es zeitliche Verzögerungen. Ich bekomme eine Verzögerung von ungefähr 6 Sekunden in 5 Minuten.
#include "Arduino.h"
#include <avr/io.h>
#include <avr/interrupt.h>
void toggle_led(void);
unsigned long volatile millis_count = 0;
volatile char state = 1;
unsigned long volatile current_count = 0;
unsigned int volatile count = 0;
ISR(TIMER0_COMPA_vect) { //Timer interrupt ISR
millis_count++;
if (millis_count - current_count == 1000) {
current_count = millis_count;
toggle_led();
Serial.println(count++);
}
}
int main(void) {
init();
TCCR0B = 0b11; //Timer settings for interrupt at every millisecond
OCR0A = 249;
TIMSK0 |= 0b10;
sei();
Serial.begin(9600);
DDRB |= 1<<5;
for (;;) {
}
}
void toggle_led(void) {
PORTB ^= (1<<5);
}
Ihre Verzögerung ist die Länge der Funktion newdelay() plus die Zeit, die zum Senden der seriellen Daten benötigt wird.
Du hast:
Jeder dieser Schritte braucht Zeit.
Um eine genaue Zeit von 1000 ms zu erhalten, können Sie entweder das serielle Senden von einem 1-Sekunden-Interrupt auslösen oder die Millisekundenzahl in Ihrer Schleife untersuchen und die seriellen Daten senden, wenn die Millisekunden 1000 durchlaufen:
unsigned long lastmil;
lastmil == newmillis();
for(;;)
{
if(((newmillis()%1000) == 0) && (lastmil != newmillis()))
{
lastmil = newmillis();
Serial.println(count++);
toggle_led();
}
}
(Eine andere Möglichkeit, die mehrfache Schleife für eine einzelne Millisekunde zu stoppen, wäre, eine kurze Verzögerung in das if (..) einzufügen, um sicherzustellen, dass die Routine mindestens 1 ms dauert.)
Die Funktion Serial.println() benötigt je nach Folgendem eine unterschiedliche Zeitdauer:
Bei 9600 Baud senden Sie 9600 Symbole pro Sekunde. Beim "8N1"-Format sind das 10 Symbole pro Byte. Für eine Zeichenfolge mit 3 Ziffern plus Wagenrücklauf und Zeilenvorschub sind das also 10 Symbole × 5 Zeichen, also 50 Symbole.
9600 Baud sind 0,0001041670,000104167 s pro Symbol (oder 104 µS pro Symbol), also sind 50 Symbole 0,00520835 s (oder 5,20835 ms).
Das ist die tatsächliche Sendezeit. Hinzu kommt dann noch der Zeitaufwand für die eigentliche Formatierung der Daten und den Aufruf der seriellen Routinen. All dies dauert eine unbestimmte Zeit. Um dies herauszufinden, müssen Sie den Assembler-Code kennen, in den die Routine kompiliert wird, und dann die Anzahl der Taktzyklen ermitteln, die jeder Befehl benötigt, und sie zusammenzählen.
Majenko hat ausführlich erklärt, warum Sie Zeitdrift sehen. Die Frage sollte jetzt sein, wie man es richtig macht. Sie sind bereits auf einen der vielen Gründe gestoßen, warum geschäftige Wartezeiten eine schlechte Methode für langfristiges Timing sind.
Ich kenne die AVR-Hardware nicht, auf der das Arduino basiert, aber ich bin sicher, dass es Timer hat. Es muss eine Möglichkeit geben, einen periodischen Interrupt vollständig in der Hardware einzurichten, indem einer dieser Timer verwendet wird. Ein Interrupt von 1 kHz (1 ms Periode) ist oft ein guter Kompromiss zwischen der zeitlichen Auflösung, nicht zu viel CPU-Auslastung und einem ausreichend kleinen Wert, den die Hardware nativ ausführen kann. Diese Interrupt-Routine wird regelmäßig 1000 Mal pro Sekunde von der Hardware aufgerufen, unabhängig davon, was Ihre Vordergrundroutine sonst tut. Es kann problemlos mehrere 1-ms-Ticks zählen, um Ticks mit längeren Perioden wie 1 Sekunde zu erzeugen. Da 1 Sekunde ziemlich langsam ist, kann die Interrupt-Routine jede Sekunde ein Flag setzen, das die Foregrond-Routine in ihrer Hauptereignisschleife überprüft und dann zurücksetzt.
Je nachdem, was sonst noch passiert, könnten Sie möglicherweise keine Interrupt-Routine verwenden, sondern die Hardware einfach bei jedem Takt ein Flag setzen lassen. In diesem Fall möchten Sie die Uhr wahrscheinlich länger ticken lassen, z. B. alle 10 ms. Der Vordergrundcode behandelt das in der Hauptereignisschleife und teilt es auf den eigentlich gewünschten 1er-Tick.
Weiter zu Olins Antwort ... so wird die Arduino-Funktion millis () tatsächlich implementiert ... Es gibt eine flüchtige globale 32-Bit-Zählervariable namens timer0_millis, die von der ISR TIMER0_OVF_vect verwaltet / aktualisiert wird, und die Millis-Funktion selbst nur stoppt Interrupts kurz, um diesen Wert in eine lokale Variable einzulesen und zurückzugeben. Sie können die Implementierung in der Datei „wiring.c“ in der Arduino-Kernbibliothek überprüfen, um zu sehen, wie sie es tatsächlich gemacht haben.
Neben den genannten Antworten gibt es noch eine weitere Möglichkeit der Zeitverschiebung. Die zum Takten des Arduino verwendeten Kristalle haben auch einen ziemlich weiten Toleranzbereich, so dass der Kristall selbst eine Drift hervorrufen kann. Wenn Sie dies über einen längeren Zeitraum verwenden möchten und bei Ihren Zeitmessungen präzise sein müssen, würde ich nach einem externen Taktchip suchen, z. B. dem DS1307, der sich einfach mit dem Arduino verbinden lässt. Diese Chips verwenden normalerweise einen "hochpräzisen" 32-kHz-Kristall, um die Zeit anzuzeigen (derselbe Kristall, der in gewöhnlichen Wanduhren verwendet wird).
bdutta74
bdutta74
Majenko
0xakhil