Grundlegende Timings mit einem STM32

Ich möchte sehr einfache Timings auf einem STM32 vornehmen. Zum Beispiel möchte ich meinen STM32 so programmieren, dass er 1 Minute lang Bytes auf dem UART ausgibt. Welche Uhr/Timer soll ich verwenden?

Wenn Sie das Referenzhandbuch durchsehen , sind viele Uhren verfügbar. Es scheint, dass einige der am besten geeigneten Uhren die Echtzeituhr (RTC), ein Allzweck-Timer oder ein Advanced-Control-Timer wären. Welche Uhr wäre am einfachsten zu verwenden, um grundlegende feste Timings zu erstellen?

Ein Allzweck-Timer klingt für Ihre Aufgabe am wenigsten komplex
github.com/dwelch67 Ich habe eine Reihe von cortex-m-Beispielen. Beachten Sie, dass der Cortex-m4 nicht den Systick-Timer hat, den der Cortex-m3 hat, gewöhnen Sie sich nicht daran, dass er da ist. (oder vielleicht war es der Cortex-m0, der es nicht hat). Die kurze Antwort ist jedoch, wählen Sie einfach einen der Timer aus, die Allzweck-Timer sind normalerweise einfach und benötigen einen, der ohne allzu große Probleme bis zu einer Sekunde zählen kann. keine Notwendigkeit, Interrupts zu verwenden, zu kompliziert bis später, starte ohne Interrupts.

Antworten (3)

Für periodische Aufgaben würde ich die Verwendung eines SysTick-Interrupts empfehlen. Hier ist eine Beispielanwendung:

void SysTick_Handler(void) {
    static uint8_t tick   = 0;
    static uint8_t second = 0;
    static uint8_t minute = 0;

    switch (tick++) {
        case 0:
            // task 0 here
            break;
        case 1:
            // task 1 here
            break;
        // and so on
        case 99:
            tick = 0;
            second = (second < 59) ? (second + 1) : 0;
            minute = (second == 0) ? ((minute < 59) ? (minute + 1) : 0) : minute;
            break;  
    }
}

int main(void)
{
    // interrupt at 100Hz
    SysTick_Config(SystemCoreClock/100);

    while (1);
}
Dies ist ein einfaches Beispiel, ja, aber Sie sollten keine 8-Bit-Ganzzahl für Ihre Tick-Variable verwenden. Abhängig von der Häufigkeit des Systick-Interrupts sollte es mindestens eine 16-Bit-Variable sein. Andernfalls können Sie möglicherweise nie hoch genug zählen, um eine Sekunde zu erreichen - insbesondere wenn Ihr Systick-Interrupt von einem typischen 32-kHz-Uhrquarz ausgeführt wird.
Der Tick-Zähler geht nur auf 99 und wird dann zurückgesetzt, daher bin ich mir nicht sicher, was Sie meinen.
Ich kenne diese Plattform nicht, aber; Wenn SysTick_Handler()es sich um eine Interrupt-Routine handelt, kommt es dann zu einem Stapelüberlauf, wenn Sie zu viel Code einfügen, oder ist dies eine gute Vorgehensweise?
@abdullahkahraman Diese Variablen im isr sind statisch, sie befinden sich nicht auf dem Stack
Mein Punkt ist, dass ich für die meisten Umstände eine bessere Timing-Präzision als einen 10-ms-Tick benötigt habe - normalerweise funktioniert 1 ms am besten für mich. Ich denke, es ist eine schlechte Praxis, einen 8-Bit-Wert zum Speichern der Ticks zu haben, da es sehr wahrscheinlich ist, dass Sie die Interrupt-Periode von etwa 10 ms auf 1 ms ändern möchten. Eine 8-Bit-Variable kann nicht bis 1000 zählen, daher werden Sie niemals auf Ihre case-Anweisung treffen, die die Sekunden erhöht. Es ist keine vage Angst – ich habe das schon einmal getan, daher nenne ich es Best Practices.

Eine Einschränkung von SysTick besteht darin, dass seine Geschwindigkeit an die Hauptuhr gebunden ist. Wenn Sie die Hauptuhr verlangsamen, um den Stromverbrauch zu reduzieren, verlangsamt sich SysTick entsprechend. Viele (alle?) STM32-Teile haben eine Art "Echtzeituhr"-Funktion, die unabhängig von der Hauptuhr laufen kann; in einigen Fällen kann es sogar unabhängig von der Hauptstromversorgung betrieben werden. Die Echtzeituhrfunktionen können so programmiert werden, dass sie die CPU zu bestimmten Zeiten aufwecken, obwohl ihre Genauigkeit ziemlich begrenzt ist (z. B. beschränken einige das Aufwachen auf Ein-Sekunden-Schritte, es sei denn, man wendet einige ziemlich eklige Tricks an). Beachten Sie auch, dass einige Chips aus irgendeinem Grund, der mich völlig verwirrt, die Zeit eher ärgerlicherweise als (0-9) Sekunden + (0-5) Zehner-Sekunden + (0-9) Minuten + (0-5) Zehner speichern Minuten + (0-9) Stunden bis 23, plus (0-2) Zehnerstunden bis 24 usw.

uint32_t present_time;
uint32_t last_clock_reading;
uint32_t last_raw_reading;

uint32_t get_time(void)
{
 // Sekunden und Minuten 0-9 im BCD-Format erfassen
  uint32_t this_clock_reading = (RTC->TR) & 0x0FFF;
  // Vorzeitiges Beenden, wenn keine Änderung
  if (this_clock_reading == last_raw_reading)
    return present_time;
  last_raw_reading = this_clock_reading;
 // Alle zehn Minuten sollten 600 Sekunden lang sein, nicht 4096
  this_clock_reading -= (4096-600)*(this_clock_reading & 0x0F00)
 // Jede Minute sollte 60 Sekunden lang sein, nicht 256
  this_clock_reading -= (256-60)*(this_clock_reading & 0x0F00)
 // Alle zehn Sekunden sollten 10 Sekunden sein, nicht 16
  this_clock_reading -= (16-10)*(this_clock_reading & 0x00F0);
  // Aktuelle Zeit aktualisieren
  if (last_clock_reading > this_clock_reading)
    present_time += 3600 + this_clock_reading - last_clock_reading;
  anders
    present_time += this_clock_reading - last_clock_reading;
  last_clock_reading = this_clock_reading;

  return present_time;
}
int set_alarm(uint32_t alarm_time) // Gibt -1 zurück, wenn die Alarmzeit bereits abgelaufen ist
{
  int32_t temp;
  // Wie weit in der Zukunft (wenn überhaupt) ist die Weckzeit?
  temp = (alarm_time - get_time());
  if (temp 3000) // Alarm auf maximal 50 Minuten in der Zukunft einstellen
    Temperatur = 3000;
 // Berechnen Sie die Uhrzeit, wann wir unseren Wecker haben sollten
  temp += last_clock_reading;
  wenn (temp > 3600)
    Temperatur -= 3600;
 // In BCD umwandeln, indem Korrekturen rückgängig gemacht werden, die beim Lesen durchgeführt würden
  Temperatur += (16-10)*(Temperatur / 10) + (256-60)*(Temperatur / 60) + (4096-60)*(Temperatur / 600);
 // Speichern Sie nun den Alarmwert (zuerst die Register schreiben)
  RTC -> WPR = 0xCA;
  RTC->WPR = 0x53;
  RTC->ALARM = temp | 0x80800000; // Alarm einstellen (passt nur Minuten und Sekunden an)
}

Bei Verwendung von Routinen wie der obigen wird die von der Anwendung verwendete Zeit in einem schönen, gleichmäßig inkrementierenden 32-Bit-Wert gehalten. Man kann bis zu 50 Minuten im Voraus einen Wecker stellen und den Chip dann einschlafen lassen; Der Chip wird aufwachen, wenn die Weckzeit eintrifft. Wenn Sie einen Alarm mehr als 50 Minuten im Voraus einstellen, wird er 50 Minuten im Voraus eingestellt. Wenn die CPU aufwacht, kann sie "zurück ins Bett" gehen, wenn die Weckzeit noch nicht gekommen ist.

Auf dem STM32L151 kann man die Uhr auf Wunsch so einstellen, dass sie mit 2x, 4x, 16x oder verschiedenen anderen Vielfachen der normalen Geschwindigkeit läuft. Der obige Code würde sich dabei nicht ändern; Die einzigen Auswirkungen wären (1) die Werte in present_time und der Parameter für set_alarm würden eine schnellere Zeiteinheit als Sekunden darstellen, und (2) die maximale Zeit vor dem nächsten Alarm würde entsprechend skaliert. Es ist etwas hässlich, mit BCD-Einheiten zu arbeiten, die zB 37,5 Sekunden, 3,75 Sekunden, 0,625 Sekunden und 0,0625 Sekunden darstellen (was die „Minuten“ und „Sekunden“ bei 16-facher Geschwindigkeit darstellen würden), aber so ist das Leben.

Wenn Sie Daten über einen bestimmten Zeitraum auf einem UART senden möchten, können Sie eine System-Timing-Methode verwenden, wie andere vorgeschlagen haben, um zu entscheiden, wann das Senden beendet werden soll. Dies wäre jedoch leicht asynchron zum Sendevorgang, der selbst Zeit verbraucht.

Ich persönlich wäre versucht, die zum Senden der Zeichen erforderliche Zeit als Quelle für das Timing zu verwenden. Vorausgesetzt, dass Sie es nie versäumen, ein neues Zeichen in das „Sendebereit“-Register zu schreiben, bevor das vorhandene Zeichen vollständig ausgetaktet ist, können Sie die serielle Übertragungsleitung ständig verwenden und (Baudrate) / (Zeichenlänge) Zeichen übertragen pro Sekunde.

In vielen Fällen sind beide Methoden ausreichend genau.