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:
- STM32L0 Low Power mit EEPROM, Nachfolger der F0
- STM32F1 Mainstream Serie ohne FPU, die allerersten STM32
- STM32F3 Mainstream Serie mit FPU, Nachfolger der F1
Weitere Anleitungen:
- Einblick in die moderne Elektronik ohne viel Theorie (von mir, mit STM32F103 und F303)
- STM32 tutorials (Prof. Laurent Latorre, mit STM32F072)
- A bare metal programming guide (Sergey Lyubka, mit STM32F429)
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 =
T = U = Y = P = |
|
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):
- STM32 Cube IDE basiert auf Eclipse, Hinweise zur IDE
- Arduino IDE für das Arduino Framework
- Keil MDK kostenpflichtig, für Profis (nur Windows)
- VisualGDB Plugin für Visual Studio (nur Windows)
- stm32-for-vscode Plugin für Visual Studio Code
Weitere Software:
- STM32 Cube Programmer zum Flashen über JTAG, SWD (ST-Link Adapter), UART, USB, I²C, SPI, und CAN
- STM32 Cube MX Code-Generator für das Cube HAL Framework
- ST-Link Utility zum Anzeigen von Trace Meldungen und Flashen mit ST-Link Adapter (veraltet, nur Windows)
- Flash Loader Demonstrator zum Flashen über den seriellen Bootloader (veraltet, nur Windows)
- DfuSe zum Flashen über den USB Bootloader (veraltet, nur Windows)
- ST-Link Tools Open-source Alternative zum ST-Link Utility
- STM32 Flash für den seriellen Bootloader
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:
- Man programmiert alle benötigten System Calls selber. Der Linker teilt dir mit, welche das sind, z.B.: "undefined reference to _write".
- 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. - 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.
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.
ST-Link Adapter
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:
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:
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:
- __get_MSP()
- __set_MSP(addr)
- __get_PSP()
- __set_PSP(addr)
- __get_CONTROL()
- __set_CONTROL(value)
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() |
|
0x0014 | 5 | -11 | BusFault_Handler() |
|
0x0018 | 6 | -10 | UsageFault_Handler() |
|
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:
- NVIC_SetPriority(CMSIS Interrupt Nr, Priorität) Stelle die Priorität ein.
- STM32L0: 0=höchste, 3=niedrigste
- STM32F1: 0=höchste, 15=niedrigste
- STM32F3: 0=höchste, 15=niedrigste
- NVIC_EnableIRQ(CMSIS Interrupt Nr) Erlaube Hardware Interrupt
- NVIC_DisableIRQ(CMSIS Interrupt Nr) Sperre Hardware Interrupt
- NVIC_SetPendingIRQ(CMSIS Interrupt Nr) Löse einen Interrupt aus
- NVIC_SystemReset() Löse einen System Reset aus
- __disable_irq() Sperre alle Interrupts
- __enable_irq() Hebe die Interrupt-Sperre auf
- __set_PRIMASK(1) Sperre normale Interrupts (ausgenommen: NMI und HardFault)
- __get_PRIMASK() Lese die aktuelle Prioritäten-Maske aus
Ü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.