STM32 Anleitungen

Technische Daten, Hinweise und Programmierbeispiele für STM32 Mikrocontroller. Diese Webseiten sollen dabei helfen, von einem anderen Mikrocontroller auf STM32 zu wechseln.

Ich habe spezifische Seiten für drei STM32 Serien erstellt:

Weitere Anleitungen:

In den folgenden Absätzen findest du Informationen, die für alle STM32 Serien gleich sind.

Familien und Serien

Low Power Familie

STM32L0: ARM Cortex M0+ bis 32 MHz, mit EEPROM
STM32U0: ARM Cortex M0+ bis 56 MHz
STM32L1: ARM Cortex M3 bis 32 MHz
STM32L4: ARM Cortex M4 bis 80 MHz mit FPU
STM32U3: ARM Cortex M33 bis 96 MHz mit FPU
STM32L5: ARM Cortex M33 bis 110 MHz mit FPU
STM32U5: ARM Cortex M33 bis 160 MHz mit FPU

Mainstream Familie

STM32F0: ARM Cortex M0 bis 48 MHz
STM32C0: ARM Cortex M0+ bis 48 MHz
STM32G0: ARM Cortex M0+ bis 64 MHz
STM32F1: ARM Cortex M3 bis 72 MHz (sehr alt)
STM32F3: ARM Cortex M4 bis 72 MHz mit FPU
STM32G4: ARM Cortex M4 bis 170 MHz mit FPU

High Performance Familie

STM32F2: ARM Cortex M3 bis 120 MHz
STM32F4: ARM Cortex M4 bis 180 MHz mit FPU
STM32H5: ARM Cortex M33 bis 250 MHz mit FPU
STM32F7: ARM Cortex M7 bis 216 MHz mit FPU
STM32H7: ARM Cortex M7 bis 400 MHz mit FPU
STM32N6: ARM Cortex M55 bis 800 MHz mit FPU

Wireless Familie

STM32WL:   für LoRaWAN, Sigfox, W-MBUS, mioty, Wi-Sun
STM32WB0: für Bluetooth LE
STM32WB:   für Bluetooth LE, Zigbee, Thread, und Matter
STM32WA:   für Bluetooth LE, IEEE 802.15.4, Zigbee, Thread and Matter
Hinter der Modellnummer:
Anzahl der Pins
D = 14 pins
E = 25 pins
F = 20 Pins
G = 28 pins
K = 32 Pins
T = 36 Pins
C = 48 Pins
R = 64 Pins
V = 100 Pins
Z = 144 Pins
Flash Größe
3 = 8 kB
4 = 16 kB
6 = 32 kB
8 = 64 kB
B = 128 kB
Z = 192 kB
C = 256 kB
D = 384 kB
E = 512 kB
F = 768 kB
G = 1024 kB
Gehäuse
H,I = BGA
T = QFP
U = QFN
Y = CSP
P = TSOP
Temperaturbereich
6 = -40 bis +85 °C
7 = -40 bis +105 °C

Jede dieser Serien hat zahlreiche Modelle. Der Product selector soll bei der Auswahl eines geeigneten Modells helfen.

Beispiel zum Namens-Schema:
Ein Modell aus der F3 Serie ist der STM32F303. Es gibt ihn in unterschiedlichen Gehäusen, zum Beispiel als STM32F303CBT6. Die Buchstaben stehen für: 48 Pins, 128 kB Flash, QFP Gehäuse und -40 bis +85 °C

Unabhängig von der Gehäuseform werden alle STM32F303 Varianten gleich programmiert und haben auch die gleichen technischen Daten. Dieses Muster zieht sich durch die gesamte STM32 Palette durch.

Software

Tools

Entwicklungsumgebungen (du brauchst nur eine):

Weitere Software:

Bibliotheken

Die Basis-Library für alle ARM Cortex-M Mikrocontroller heisst CMSIS Core. Sie enthält Definitionen für die Register vom ARM Kern (Adressen und Bits), sowie ein paar Hilfsfunktionen zu dessen Konfiguration. Die CMSIS wird von allen Chip Herstellern um sogenannte "device files" ergänzt. Darin befinden sich Definitionen für die Peripherie, die der Chip-Hersteller um den ARM Kern herum gebaut hat.

Download: CMSIS Paket für alle STM32 MCU (außer wireless, extrahiert aus den STM32Cube MCU Paketen)

Darauf aufbauend stellt die Firma ST ihr proprietäres Cube HAL Framework bereit, das die Wiederverwendbarkeit von Code beim Wechsel auf andere STM32 Modelle erleichtern soll. Dazu gehört das Programm Cube MX (welches in die Cube IDE integriert wurde), womit man Quelltext-Projekte einschließlich Code zur Konfiguration der I/O Funktionen und Taktversorgung erzeugt.

Download: STM32Cube MCU Pakete (die Cube IDE und Cube MX laden es automatisch herunter)

Der Arduino Core für STM32 legt eine weitere Abstraktions-Schicht über Cube HAL, um die Programmierung unterschiedlicher Mikrocontroller von vielen Herstellern zu vereinheitlichen.

Download: Arduino Core for STM32 (wird für die Arduino IDE benötigt)

⚠️ Ich empfehle am Anfang ohne Frameworks zu lernen. Mit der CMSIS und dem Referenzhandbuch lernt man Grundlagen, womit man die knapp dokumentierten Frameworks später viel besser versteht.

Newlib

Der arm-gcc Compiler bringt standardmäßig zwei C-Bibliotheken mit: Newlib ist die Standard-Bibliothek von Linux, während die newlib-nano für Mikrocontroller optimiert wurde. Du kannst eine Menge Speicherplatz sparen, indem du auf die kleinere Version der Library wechselst. Dazu dient das Linker-Flag -specs=nano.specs, welches die Cube IDE bei neuen Projekten automatisch einstellt.

Wenn man Ein-/Ausgabe Funktionen benutzt müssen entsprechende Low-Level Funktionen implementiert werden. Zum Beispiel beruhen die C Funktionen puts(), printf() und putchar() intern alle auf _write(). Letztere bestimmt, wohin die Ausgabe geschrieben wird und ist natürlich projektspezifisch. Diese Funktionen sind in System Calls dokumentiert. Jetzt gibt es drei Möglichkeiten, damit umzugehen:

  1. Man programmiert alle benötigten System Calls selber. Der Linker teilt dir mit, welche das sind, z.B.: "undefined reference to _write".
  2. Man benutzt zusätzlich das Linker-Flag -specs=nosys.specs. Dadurch wird die Bibliothek libnosys.a eingebunden, die für alle System Calls eine minimale Implementierung enthält. Du musst dann nur noch die Funktionen überschreiben, die mehr als "nichts" tun sollen.
    Der Linker gibt evtl. Warnungen wie "_close is not implemented and will always fail" aus.
  3. Die Cube IDE generiert die Dateien syscalls.c und sysmem.c mit minimalen Implementierungen. Sie zu editieren ist selten nötig, denn _read() und _write() kannst du in einer eigenen Datei überschreiben.
Die Cube IDE richtet 2 und 3 gleichzeitig ein, was eigentlich doppelt gemoppelt ist, aber es funktioniert.

Beispiel für eine eigene _write() Funktion, die Textausgabe mittels puts(), printf() und putchar() ermöglicht:

int _write(int file, char *ptr, int len) {
    for (int i=0; i<len; i++) {
        char c=*ptr++;
        // hier das Zeichen c irgendwo ausgeben
        // z.B. auf einer seriellen Schnittstelle
    }
    return len;
}

Der RAM Bedarf von printf() ist unabhängig von der Anzahl der Argumente und Formatier-Optionen. Wenn weniger als 1468 Bytes Heap zur Verfügung stehen, belegt die Library stattdessen nur 436 Bytes und gibt dann jedes Zeichen einzeln mit _write() aus. Wenn weniger als 436 Bytes Heap zur Verfügung stehen, bricht die Funktionen mit einer HardFault Exception ab.

Bei der Ausgabe von Text mit printf() und putchar() ist zu beachten, dass die Zeichen in einem Puffer gesammelt werden, bis dieser entweder voll ist oder ein Zeilenvorschub (\n) erfolgt. Mit fflush(stdout) kann man erzwingen, dass die Ausgabe sofort erfolgt. puts() ist nicht betroffen, weil es immer einen Zeilenvorschub anhängt.

Wenn man Fließkommazahlen ausgeben möchte, muss man bei der newlib-nano zusätzlich die Option -u _printf_float angeben. Das kostet zusätzlich rund 9 kB Flash Speicher. Für das Parsen von Fließkommazahlen benötigt man die Option -u _scanf_float.

Bei Modellen mit FPU muss diese eingeschaltet werden, bevor die erste Funktion mit float Variablen oder Funktionen aufgerufen wird, sonst bricht das Programm mit einer "Hardware fault exception" ab.

Programmier- und Debug-Schnittstellen

SWJ Debug Port

Mit dem SWJ Debug Port übertragt man das fertige Programm in den Mikrocontroller und kann es im laufenden Betrieb untersuchen. Zum Beispiel kann man das Programm jederzeit anhalten und dann den Inhalt des Speichers anschauen. Wenn es hängt, kann der Debugger anzeigen, wo das passiert. Der Debugger kann sogar melden, wenn ausgewählte Variablen verändert werden.

Die SWJ Schnittstelle ist nach dem Reset standardmäßig aktiviert, kann jedoch per Software deaktiviert werden, um mehr freie Pins zu erhalten. Die Schnittstelle funktioniert nicht im Stop, Standby und Sleep Modus!

Die SWJ Schnittstelle unterstützt zwei Übertragungsprotokolle: JTAG und SWD. Das neuere SWD Protokoll wird bevorzugt, da es schneller ist und nur drei Leitungen benötigt: GND, SWDIO und SWCLK.

Der dazu passende Programmiersadapter von ST heisst "ST-Link". Man kann ihn in verschiedenen Versionen kaufen, benötigt wird mindestens Version 2.0. Obwohl SWD und JTAG von ARM standardisiert sind, kann der ST-Link nur Mikrocontroller von ST programmieren. Eine flexiblere Alternaive sind die J-Link Produkte von Segger.

Man kann den ST-Link Adapter von Nucleo-64 Boards abschneiden, um damit andere STM32 Mikrocontroller zu programmieren. Oder man zieht die beiden Jumper ab, wodurch die Leitungen SWCLK und SWDIO unterbrochen werden. Auch dann kann man damit andere STM32 Mikrocontroller programmieren:

ST-Link vom Nucleo64 Board abgeschnitten

Er ist folgendermaßen mit dem Mikrocontroller verbunden:

ST-Link CN4 Mikrocontroller Beschreibung
Pin 1 VDD Misst die Spannungsversorgung der Zielschaltung, optional
Pin 2 SWCLK PA14 Serial Wire Clock
Pin 3 GND Common Ground (Masse)
Pin 4 SWDIO PA13 Serial Wire Data In and Out
Pin 5 NRST Reset Signal, optional siehe Verbindungsoptionen in der Cube IDE
Pin 6 SWO PB3 Serial Wire Output, optional siehe Trace Meldungen ausgeben

Außerdem enthalten diese ST-Link Platinen auch einen USB-UART Adapter mit den Anschlüssen Tx und Rx.

Viele Windows Programme benötigen den libusb-win32 Treiber, um den ST-Link anzusteuern. Falls er fehlt oder nicht richtig geladen wird, siehe hier.

Die Cube Programme verlangen oft nach einem Firmware-Update, was auch auf chinesischen Nachbauten in der Regel problemlos klappt. Die anderen oben genannten Programme sind hingegen mit älteren Firmware Versionen zufrieden.

Bei den gezeigten ST-Link v2 Sticks empfehle ich, die Rückseite der Platine innen mit Pappkarton abzudecken, damit kein Kurzschluss zum Aluminium-Gehäuse entsteht. Man kann sie einfach auseinander ziehen. Der Reset-Ausgang dieser Sticks funktioniert nur mit STM8! Wer eine ruhige Hand hat, kann sich eine SWO Leitung nachrüsten:

ST-Link v2 Sick feine Lötarbeit

Trace Meldungen ausgeben

Mikrocontroller mit Cortex-M3 und höher können auf dem SWO Pin Diagnose Meldungen auszugeben. Diese Schnittstelle ist effizienter als USART, weil ihre Bitrate höher ist (nämlich 1/4 der CPU Taktfrequenz) und weil sie über einen kleinen FIFO Puffer (typisch 10 Bytes) verfügt.

SWO wird mit dem ST-Link Adapter aktiviert und empfangen. Dann ist der Pin vorübergehend ein Ausgang mit Ruhe-Pegel High. Während der Datenübertragung liefert er Low-Impulse.

Die CMSIS Funktion ITM_SendChar() gibt ein Zeichen auf der SWO Leitung aus. ITM bedeutet "Instrumentation Trace Message":

#include <stdint.h>
#include "stm32f3xx.h"

// Delay loop
void delay(uint32_t msec) {
    for (uint32_t j=0; j < msec * 2000; j++) {
        __NOP();
    }
}

// Output a trace message
void ITM_SendString(char *ptr) {
    while (*ptr) {
        ITM_SendChar(*ptr);
        ptr++;
    }
}

int main() {
    while (1) {
        ITM_SendString("Hello World!\n");
        delay(500);
    }
}

Siehe auch meine Hinweise zur Cube IDE, wie man die Meldungen damit anzeigt.

Boot Loader

Neben der SWJ Schnittstelle haben alle STM32 auch einen unveränderlichen Bootloader, über den man Programme hochladen kann. Er ermöglicht Zugriff auf den Flash Speicher, das RAM und die Option Bytes. Zum Debuggen eignet er sich jedoch nicht.

Der Bootloader unterstützt mindestens eine serielle Schnittstelle. Je nach Modell auch I²C, SPI, CAN und USB. Er wird aktiviert, indem man Boot0 beim Start auf High setzt. Bei den meisten STM32 ist Boot0 ein physischer Pin an dem üblicherweise ein Jumper oder Taster angeschlossen wird. Bei manchen STM32 Modellen ist Boot0 ein "Option Byte" (auch bekannt als "Fuse"), das man mit dem Programmieradapter einstellt.

Mehr dazu in den spezifischen Artikeln für STM32L0, STM32F1 und STM32F3 und in der Application Note AN2606.

GCC Optionen

Keil MDK verwendet einen anderen Compiler, für den dieses Kapitel nicht passt.

Assembler Listing

Wenn du sehen willst, welchen Assembler-Code der Compiler erzeugt, benutze die Compiler-Optionen -g -Wa,-adhlns="$(@:%.o=%.lst)".

Du findest dann für jede Quell-Datei eine *.lst Datei im Verzeichnis Debug oder Release.

Optimierungen

In den Einstellungen des Compilers kann man beeinflussen, mit welcher Strategie der Compiler das Programm optimiert.

Option Beschreibung
-O0 Keine Optimierung. Der Assembler-Code entspricht genau dem C-Code. Aber das Programm läuft viel Langsamer, als mit Optimierung.
-Og Das Programm wird ein bisschen optimiert, ohne den Debugger zu beeinträchtigen.
-O1 Nur einfache Optimierungen, der Assembler-Code entspricht strukturell weitgehend dem Quelltext.
-Os Möglichst geringe Code-Größe.
-O2 Gute Geschwindigkeit.
-O3 Beste Geschwindigkeit, unter Umständen wird der Code aber viel größer.

Optimierter Code stimmt stellenweise nicht mehr mit dem C-Quelltext überein, was die Benutzung des Debuggers beeinträchtigt. Zum Beispiel kann der Debugger nur Variablen anzeigen, die eine Adresse im RAM haben, aber der optimierende Compiler bevorzugt CPU Register. Der Compiler ersetzt manchmal ganze Prozeduren durch inline Code, und Schleifen durch völlig anderen Code, der das gleiche Ergebnis produziert.

Damit der Debugger funktioniert, braucht man die Option -g. Sie veranlasst den Compiler dazu, Informationen für den Debugger in die *.elf Datei zu schreiben. Auf die Geschwindigkeit und Größe im Flash hat -g keinen Einfluss.

Ich bevorzuge -O1 -g weil der so erzeugte Code nur marginal langsamer ist, als in den höheren Stufen. Der Code lässt sich dennoch weitgehend debuggen und die Assembler Listings sind überschaubar. Weniger gut finde ich die Vorgabe der IDE, wo zwischen Debug- und Release-Modus unterschieden wird. Ich möchte nicht wochenlang eine Debug-Version des Programm testen, um am Ende eine andere Release-Version abzuliefern. Es geht dabei nicht nur darum dem Compiler zu vertrauen, sondern dass feine Unterschiede im Timing manchmal Probleme (Race Conditions) auslösen, die man gerne möglichst früh schon während der Entwicklung bemerken will

Die vollständige Liste der Optimierungen befindet sich hier.

Startup-Code

Die Cube IDE generiert eine Assember Datei namens startup_stm32.s, welche die Interrupt-Vektor Tabelle und den ersten Code enthält, der nach einem Reset ausgeführt wird. Normalerweise ist es nicht nötig, sie zu editieren.

Dieser Startup-Code führt eine Funktion namens SystemInit() aus, bevor statische Objekte konstruiert werden und bevor main() ausgeführt wird. Bei großen C++ Projekten kann es sich lohnen, diese Funktion zu überschreiben, um dort die Erhöhung der Taktfrequenz unter zu bringen, denn dann startet das Programm schneller. Dies ist auch ein guter Platz, um die FPU einzuschalten (falls vorhanden):

void SystemInit() {

    // Switch the FPU on
    SCB->CPACR = 0x00F00000;
    
}

Speicher-Struktur

Obwohl der Prozessor in Harvard Architektur gestaltet ist, benutzt er einen gemeinsamen Adress-Raum für Programm, Daten und I/O Register. Dadurch können alle I/O Register über Zeiger angesprochen werden und der Prozessor kann Code sowohl aus dem RAM als auch aus dem (Flash-) ROM ausführen. Adressen und Zeiger sind 32 Bit groß.

Die Befehle sind teilweise 16 Bit und teilweise 32 Bit groß.

Daten werden als 8, 16 oder 32 Bit geladen. Sie müssen nicht zwingend an der 32 Bit Wortgröße ausgerichtet sein. Aber man erreicht bessere Geschwindigkeit, wenn 16 Bit Daten an 16 Bit Adressen und 32 Bit Daten an 32 Bit Adressen ausgerichtet sind. Der Compiler kümmert sich automatisch darum.

Die Register für I/O Funktionen sind überwiegend 32 Bit breit.

Schreibzugriffe auf die Register finden asynchron statt. So kann die CPU zum Beispiel ein langsames Register am APB1 Bus beschreiben und noch bevor dies fertig ist ein anderes Register am AHB oder APB2 Bus ansprechen. Mit dem Befehl __DSB() zwischen zwei Register-Zugriffen stellt man sicher, dass sie ohne Überlappung nacheinander stattfinden.

Im Gegensatz zu AVR Mikrocontrollern liegen bei ARM norle konstante Zeichenketten im Flash Speicher, nicht im RAM.

Funktionsaufrufe

Bei Funktionsaufrufen werden bis zu 4 Parameter durch Register übergeben. Dabei ist es vorteilhaft, sie als 32 Bit Typ zu deklarieren, um Konvertierungen zu vermeiden. Bei mehr als 4 Parametern wird das RAM zur Übergabe benutzt, dann sind 8 Bit, 16 Bit und 32 Bit Typen gleich langsam.

Der Rückgabewert einer Funktion wird ebenfalls in einem Register übermittelt und sollte daher 32 Bit groß sein, falls Geschwindigkeit wichtig ist.

Stack

Der Stapel speichert ausschließlich 32 Bit Werte. Bei jedem PUSH wird der Stapelzeiger (SP) zuerst um 4 verringert und dann wird das Wort an diese Adresse abgelegt. Der Stapelzeiger zeigt also immer auf die zuletzt belegte Adresse im RAM.

Es gibt zwei Stapelzeiger MSP und PSP, zwischen denen man umschalten kann. Der Prozessor startet mit dem MSP, welcher durch das erste Wort in der Interrupt-Vektor Tabelle (an Adresse 0) initialisiert wird. Der alternative Stapelzeiger PSP wird von Betriebssystemen genutzt, um den Programmen separate Stapel zuzuweisen. Unter dem Namen SP spricht man immer den Stapelzeiger an, der durch das SPEL Bit im CONTROL Register ausgewählt wurde (0=MSP (default), 1=PSP).

Relevante CMSIS Funktionen:

Interrupt-Vektoren

Interrupt-Signale werden von interner oder externer Hardware ausgelöst, wenn bestimmte Ereignisse auftreten. Sie können dazu genutzt werden, das laufende Programm vorübergehend zu unterbrechen und stattdessen eine besondere Funktion auszuführen, die Interrupt-Handler oder Interrupt-Service-Routine (ISR) genannt wird.

Der Flash-Speicher beginnt immer mit der Interrupt-Vektor Tabelle. Jeder Eintrag ist eine 32 Bit Sprungadresse. Diese ist im Referenzhandbuch Kapitel "Interrupt and exception vectors" dokumentiert. Der Quelltext dazu befindet sich in der Datei startup_stm32.s, welche von der Cube IDE beim Anlegen des Projektes generiert wird. Dort findest du die vorgegebenen Namen der C-Funktionen. Die ersten Einträge der Tabelle heissen "ARM Processor Exceptions" und sind bei allen STM32 Modellen gleich:

Address ARM Exception Nr. CMSIS Interrupt Nr. ISR Handler Function Description
0x0000 Initial value for the stack pointer MSP.
0x0004 Reset_Handler Initial value for the program counter, points to assembler startup code.
0x0008 2 -14 NMI_Handler() Non maskable interrupt. The RCC Clock Security System (CSS) is linked to the NMI vector.
0x000C 3 -13 HardFault_Handler() Hardware fault. Can optionally be splitted into the next 3 entries by configuration register SCB->SHCSR:
0x0010 4 -12 MemManage_Handler()
  • Memory protection fault, doe snot exist on Cortex-M0(+)
0x0014 5 -11 BusFault_Handler()
  • Pre-fetch or memory access fault
0x0018 6 -10 UsageFault_Handler()
  • Undefined instruction, illegal unaligned access, invalid state, division by zerot
0x001C 7 -9 reserved
0x0020 8 -8
0x0024 9 -7
0x0028 10 -6
0x002C 11 -5 SVC_Handler() Supervisor Call, triggered by the SVC command (formerly known as SVI). Can be used to call operating system services.
0x0030 12 -4 reserved
0x0034 13 -3
0x0038 14 -2 PendSV_Handler() Pendable Request for System Service. Triggered by the operating system by writing to the ICSR register, to switch the context.
0x003C 15 -1 SysTick_Handler() Called when the systick timer reaches 0

Danach folgen Modell-spezifische Interrupt-Vektoren, die ich in den Artikeln für STM32L0, STM32F1 und STM32F3 aufliste.

Interrupt Controller

Der Interrupt-Controller NVIC steuert die Verarbeitung von Unterbrechungs-Signalen. Er ist Bestandteil des ARM Kerns.

Laufende Interrupt-Handler können durch höher priorisierte Interrupts unterbrochen werden. Reset, NMI und HardFault haben immer die höchste Priorität (noch höher als 0), bei allen anderen kann man die Priorität einstellen. Die CMSIS enthält Funktionen zur Konfiguration des Interrupt-Controllers:

Über das SCB->VTOR Register kann man den Ort der Liste verändern um sie z.B. ins RAM zu verschieben.

Um Unterbrechungen temporär zu verbieten (zum Beispiel für exklusiven Zugriff auf Daten oder Schnittstellen), wird folgende Vorgehensweise empfohlen:

uint32_t backup = __get_PRIMASK();
__set_PRIMASK(1);
... do some work ...
__set_PRIMASK(backup);

Normale Unterbrechungen sind durch Pegel gesteuert. Wenn das Signal auf High steht, wird der zugeordnete Handler möglichst bald ausgeführt. Während der Interrupt-Handler läuft, muss das Signal wieder auf Low zurück zurück gehen, sonst wird der Interrupt-Handler nach seinem Ende gleich wieder aufgerufen.

Wenn das Signal zu früh verschwindet, während der Interrupt-Controller nach einer Gelegenheit sucht, den Interrupt-Handler auszuführen, geht es verloren. Der Interrupt-Handler wird dann nicht ausgeführt.

Einige Interrupt-Signale durchlaufen einen erweiterten Schaltkreis, der zusätzliche Konfiguration erfordert. Man erkennt sie am Stichwort EXTI. Dazu habe ich spezifische Artikel für STM32L0, STM32F1 und STM32F3 geschrieben.

SysTick Timer

Alle Cortex-M Prozessoren enthalten einen 24bit Timer, mit dem man die Systemzeit misst. Der Timer zählt die Taktimpulse des Prozessors herunter und löst bei jedem Überlauf einen Interrupt aus. Der Funktionsaufruf SysTick_Config(SystemCoreClock/1000) sorgt dafür, dass jede Millisekunde ein SysTick Interrupt ausgelöst wird. Ein Anwendungsbeispiel:

#include <stdint.h>
#include "stm32f3xx.h"

// The current clock frequency
uint32_t SystemCoreClock=8000000;

// Counts milliseconds
volatile uint32_t systick_count=0;

// Interrupt handler
void SysTick_Handler() {
    systick_count++;
}

// Delay some milliseconds.
void delay_ms(int ms) {
    uint32_t start=systick_count;
    while (systick_count-start < ms);
}

int main() {
    // Initialize the timer for 1 ms intervals
    SysTick_Config(SystemCoreClock/1000);
    
    // Delay 2 seconds
    delay_ms(2000);  
    ...
}

Wenn der Prozessor beim Debuggen angehalten wird, hält auch dieser Timer an. Im WFI und WFE Sleep Modus (siehe Power Management) läuft der SysTick Timer weiter.

Power Management

Indem man Taktsignale für I/O Funktionen deaktiviert oder verlangsamt, spart man bereits eine Menge Strom. Darüber hinaus gibt es die folgenden besondere Zustände für den ARM Kern:

Modus Eintritt Aufwachen Beschreibung
WFI Sleep __WFI() Interrupt Warte auf Interrupt. Nur die CPU wird angehalten.
WFE Sleep __WFE() Interrupt oder Ereignis Warte auf Ereignis. Nur die CPU wird angehalten.
Stop PDDS=1, LPDS=1, SLEEPDEEP=1 + __WFI() oder __WFE() EXTI Leitung, configuriert im EXTI Register. Taktsignale von HSI/HSE sind deaktiviert. Die I/O Pins, Register und RAM bleiben unverändert. Debugging ist nicht möglich.
Standby PDDS=1, LPDS=0, SLEEPDEEP=1 + __WFI() oder __WFE() Steigende Flanke an WKUP Pin, RTC Alarm, externer Reset am NRST, IWDG Reset. Taktsignale von HSI/HSE sind deaktiviert. RAM Inhalte gehen verloren. Nur die Backup Register bleiben erhalten. Die I/O Pins werden hochohmig, außer TAMPER, WKUP und NRST. Debugging ist nicht möglich. Zum Aufwachen muss der Controller neu starten.

Wenn der WFI/WFE Sleep innerhalb einer Interruptroutine aktiviert wurde, kann er nur durch einen höher priorisierten Interrupt aufgeweckt werden. Wenn gerade ein Ereignis ansteht, während __WFE() augerufen wird, wird nur das Ereignis gelöscht und kein Sleep Modus aktiviert.

Durch Setzen von Bit 1 (SLEEPONEXIT) im Register SCB->SCR aktiviert man die "Sleep on exit" Funktion. Diese bewirkt, dass der Prozessor nach Abarbeitung jeder Interruptroutine automatisch in den WFI Sleep Modus geht.

Wenn im SCB->SCR Register das Bit 4 (SEVONPEND) gesetzt ist, löst ein anstehender Interrupt zugleich ein Ereignis aus, selbst wenn der Interrupt nicht freigeschaltet ist.

Startseite