Gliederung


NUCLEO-F103RB Board


"Blue Pill" Board aus China

Startseite

Notizen zu ARM Cortex M3 Mikrocontrollern der STM32F1 Serie

Auf dieser Seite habe ich für Anfänger Informationen zum Einstieg mit ARM Cortex M3 Mikrocontrollern gesammelt, konkret am Beispiel des Nucleo-F103RB Boardes von der Firma STMicroelectronics, sowie dem rechts abgebildeten "Blue Pill" Board aus China.

Wer noch keine Erfahrung mit Mikrocontrollern hat, dem empfehle ich, mit kleineren 8bit Controllern (z.B. Arduino Nano) anzufangen. Die haben nämlich weniger Funktionen und ihre Datenblätter sind daher leichter zu verstehen.

Hardware

Nucleo-F103RB

Das Nucleo-F103RB Board (alias Nucleo-64 mit STM32F103RBT6) ist mit seinem Preis um 20€ ein hochwertiges und dennoch preisgünstiges Probier-Set. Es hat folgende Eigenschaften:

Blue Pill Board

Das rechts abgebildete namenlose Board aus China kostet etwa 5€. Manche Leute nennen es "Blue Pill". Es hat folgende Ausstattung: Für die Programmierung benötigt man einen St-Link oder einen USB-UART Adapter.

Elektrische Daten

Alle STM32F1 Chips kann man mit 2,0 bis 3,6 Volt betreiben. Der ADC benötigt allerdings mindestens 2,4 Volt.

Viele I/O Pins sind 5V tolerant. Bei allen STM32F103 Modellen sind das: PA8-15, PB2-4, PB6-15, PC6-12, PD0-15, PE0-15, PF0-5, PF11-15, PG0-15

Die Ausgänge sind einzeln mit 20mA belastbar, gültige Logikpegel sind bis 8mA garantiert. Jedoch dürfen alle Ausgänge zusammen nur mit maximal 150mA belastet werden. Im open-drain Modus dürfen die 5V toleranten Ausgänge durch externe Widerstände auf 5V gezogen werden.

Die internen Pull-Up und Pull-Down Widerstände haben typischerweise 40k Ohm. Alle I/O Pins haben typischerweise 5pF Kapazität.

Bei den 3,3V I/O Pins darf man die ESD Schutzdioden einzeln bis zu 5mA belasten, alle zusammen jedoch maximal 25mA.

Bei den 5V toleranten Pins darf man sie nur durch negative Spannungen einzeln bis zu 5mA belasten, alle zusammen jedoch mit maximal 25mA. Stromfluss durch Überspannung über 5,5V ist nicht zulässig.

Besondere Ausnahmen

Die Pins PC13, PC14 und PC15 dürfen bei High keinen Gleichstrom liefern und bei Low nur 3mA aufnehmen. Außerdem dürfen sie nur mit 2Mhz getaktet werden und mit maximal 30pF belastet werden.

Die ESD Schutzdioden von PA4, PA5, PC13, PC14 und PC15 darf man nicht belasten.

Dokumentationen

Ich empfehle, die folgenden Dokumente zu lesen: Optional:

Software

Nach einigen Versuchen mit anderen Alternativen bin ich bei dieser Windows Software angelangt: Optional: Außerdem habe ich auch ein bisschen mit der Arduino IDE herumgespielt, siehe weiter unten.

Bei der Cube HAL handelt es sich um eine Abstraktions-Library, die den Zugriff auf die Funktionen des Mikrocontrollers erleichtern soll. Die HAL ist Basis für zusätzliche Libraries, zum Beispiel RTOS (Real Time Operating System), USB, TCP/IP, Filesysteme, usw. Die Firma ST erweitert das Angebot fortlaufend.

Mit dem Programm CubeMX kann man sich HAL-basierte Projekte einschließlich Initialisierungs-Routine für I/O Pins und Takterzeugung zusammen klicken. Man sollte allerdings bedenken, dass dieses Konstrukt ausschließlich auf STM Controllern läuft.

Die Standard Peripheral Library habe ich hier verlinkt, weil sehr viele Beispielprogramme auf dieser älteren Variante der Hardware-Abstraktion basieren. Sie kann hilfreich sein, wenn man von alten Programmen abguckt. Für Neuentwicklungen soll man sie allerdings nicht mehr benutzen.

Sowohl Cube HAL als auch die StdPeriph Library bauen beide auf die Hersteller-unabhängige CMSIS Library auf. Dabei handelt es sich im Grunde genommen um einen Haufen Definitionen für alle Register, damit man sie mit Namen statt über Hexadezimal-Codes ansprechen kann. Außerdem enthält die CMSIS eine kleine Menge Hilfsfunktionen, um den ARM Prozessor zu konfigurieren. Die CMSIS Libraries gibt es für praktisch alle ARM Controller von allen Herstellern. Das heißt allerdings noch lange nicht, das alle ARM Controller gleich programmiert werden, denn jeder Hersteller baut seine eigene Peripherie um den ARM Kern herum. Da die CMSIS Library von STM leider nicht einzeln downloadbar ist, muss man sie von irgendeinem Cube HAL Projekt kopieren.

Projekt auf Basis von CMSIS erzeugen

Der Assistent in der IDE kann nur neue Projekte anlegen die entweder auf Cube HAL oder die alte StdPeriph Library benutzen. Programme auf Basis der CMSIS Library sind deutlich schlanker und auf ARM Controller anderer Hersteller portierbar. Aber der Assistent der IDE kann solche Projekte nicht direkt anlegen.

Man muss dazu folgendermaßen vorgehen:

  1. Lege mit dem Assistenten der IDE das eigentliche Arbeitsprojekt mit der Option "No Firmware" an.
  2. Kopiere das ganze CMSIS Verzeichnis aus diesem Beispielprojekt (oder aus irgendeinem Projekt mit Cube HAL) in dein Arbeitsprojekt.
  3. Füge die beiden Verzeichnisse (CMSIS/core und CMSIS/device) zur Projektkonfiguration hinzu. Das geht so: Rechte Maustaste auf den Projektnamen, dann Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Compiler/Includes/Include paths.
  4. In der Datei stm32f1xx.h habe ich unter dem Kommentar "Uncomment the line below according to the target STM32 device" (ungefähr Zeile 90) die Definition #define STM32F103xB auskommentiert. Für andere STM32 Modelle musst du stattdessen die dementsprechende Zeile aktivieren.
Beispiel für einen einfachen LED-Blinker mit CMSIS (src/main.c):
#include <stdint.h>
#include "stm32f1xx.h"


// delay loop for default 8Mhz clock 
void delay(uint16_t msec)
{
    for (uint32_t j=0; j<2000*msec; j++)
    {
        asm volatile ("nop");
    }
}


int main(void)
{
    // Enable Port A
    SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);

    // PA5 = Output
    MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF5 + GPIO_CRL_MODE5, GPIO_CRL_MODE5_0);

    while(1)
    {
        // LED On
        WRITE_REG(GPIOA->BSRR,GPIO_BSRR_BS5);
        delay(1000);

        // LED Off
        WRITE_REG(GPIOA->BSRR,GPIO_BSRR_BR5);
        delay(1000);
    }
}
Um das Programm in den Chip zu übertragen klickt man in der IDE mit der rechten Maustaste auf den Projektnamen, dann Target/Program Chip... wählen. Oder man verwendet das ST-Link Utility.

Die Delays realisiert man besser mit einem Timer. Ich wollte hier allerdings ein möglichst simples Programmbeispiel zeigen.

GCC Optionen

Newlib

Newlib ist die C-Standard-Bibliothek für Linux, während newlib-nano für Mikrocontroller optimiert wurde. Du kannst eine Menge Speicherplatz sparen, indem du auf die nano Version der Library wechselst. Trage dazu in das Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Linker/Miscellaneous/Linker Flags -specs=nano.specs ein.

Wenn das Programm die stdio.h Library benutzt, muss man entweder einige Funktionen zum Zugriff auf die Konsole implementieren oder durch die Option -specs=nosys.specs auf Dummy Funktionen zurückgreifen. Beispiel siehe weiter unten.

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

Assembler Listing

Wenn du sehen willst, welchen Assembler-Code der Compiler erzeugt, dann trage in in das Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Compiler/Miscellaneous/Other Flags -Wa,-adhlns="$(@:%.o=%.lst)" ein.

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

Optimizer

Unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Compiler/Optimization kann man beeinflussen, mit welcher Strategie der Compiler das Programm optimiert (umstrukturiert).
OptionZielBeschreibung
-O0DebuggingKeine Optimierung. Der Assembler Code entspricht 1:1 dem C-Code, was für den Debugger optimal ist. Aber das Programm läuft viel Langsamer, als mit Optimierung..
-O1CompilierzeitDer Compiler wendet nur einfache Optimierungen an, die er schnell umsetzen kann. Nicht empfehlenswert.
-O2GeschwindigkeitBessere Performance, von ARM als Standardeinstellung empfohlen.
-O3GeschwindigkeitBeste Performance, unter Umständen wird der Code aber viel größer. Diese Stufe ist weniger erprobt.
-OsCode-GrößeGeringe Code-Größe auf Kosten der Performance.
-OgDebuggingDas Programm wird so weit wie möglich auf Geschwindigkeit optimiert, ohne den Debugger zu beeinträchtigen.
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 Optimizer bevorzugt CPU Register für Variablen. Unter Umständen entfernt der Optimizer ganze Prozeduren und ersetzt sie durch anderen Code an den Stellen, wo die Prozeduren benutzt wurden. Wiederholschleifen werden teilweise durch eine längere Sequenz von Code ersetzt, wenn dadurch die Performance besser wird.

Die Vollständige Liste der Optimierungen befindet sich hier.

Startup-Code

Im Gegensatz zu allen mir bekannten Compilern für 8bit Mikrocontroller befindet sich der Startup-Code und die Interrupt-Vektor Tabelle in editierbaren Dateien (sysmem.c und startup_stm32.s). Der Projekt-Assistent in der IDE legt diese Dateien automatisch an. Für erste Versuche kann man sie unverändert benutzen.

Falls vorhanden, führt der Startup-Code eine Funktion mit folgender Signatur aus, bevor statische Objekte konstruiert werden und bevor main() ausgeführt wird:

void SystemInit(void) {}
Bei großen C++ Projekten kann es sich lohnen, die Erhöhung der Taktfrequenz dort unterzubringen, denn dann startet das Programm schneller.

Taktgeber

Nach einem Reset wird der STM32F103RB Mikrocontroller mit seinem internen 8Mhz R/C Oszillator (ohne Vorteiler) getaktet. Der System-Takt (SYSCLK) kann aus folgenden Quellen bezogen werden: Mehrere Vorteiler und ein PLL Multiplikator können kombiniert werden, um andere Taktfrequenzen zu erreichen. Am Besten schaut man sich das mal in CubeMX an, da kann man schön sehen, welche Parameter konfigurierbar sind und wie sie zusammen wirken. Das Programm CubeMX beginnt mit folgender Vorlage, sie entspricht allerdings nicht den Werten, die ein Hardware-Reset vorgibt:

Wenn man I/O Funktionen anspricht, ohne dass ihr Taktsignal eingeschaltet ist, hängt sich der Mikrocontroller auf.

Wenn man den Systemtakt über 24 Mhz erhöht, muss man für den Flash Wait-States einstellen. Bei mehr als 36Mhz muss man außerdem einen Vorteiler für die I/O Baugruppe am APB1 Bus einstellen.

Der folgende Beispielcode ändert den Systemtakt auf 64 Mhz mit dem internen HSI Oszillator:

// This variable is used by some CMSIS functions
uint32_t SystemCoreClock=8000000;

// Called by Assembler startup code
void SystemInit(void)
{
    // Flash latency 2 wait states
    MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_1);

    // 64Mhz using the HSI oscillator divided by 2 with 16x PLL, lowspeed I/O runs at 32Mhz
    WRITE_REG(RCC->CFGR, RCC_CFGR_PLLMULL16 + RCC_CFGR_PPRE1_DIV2);

    // Enable PLL
    SET_BIT(RCC->CR, RCC_CR_PLLON);

    // Wait until PLL is ready
    while(!READ_BIT(RCC->CR, RCC_CR_PLLRDY)) {}

    // Select PLL as clock source
    MODIFY_REG(RCC->CFGR, RCC_CFGR_SW, RCC_CFGR_SW_PLL);

    // Update variable
    SystemCoreClock=64000000;
}
Die obige Delay Schleife läuft danach allerdings nicht 8x schneller, sondern nur 6x schneller. Der Grund dafür ist, dass der Flash jetzt mit 2 Waitstates betrieben werden muss und der Prefetch-Buffer (der dies ausgleicht) nur direkt aufeinanderfolgende Befehle optimiert. Bei jeden Rücksprung in der Schleife wird der Prefetch-Buffer geleert.

Durch die Angabe __attribute__((section(".data"))) könnte man Funktionen etwas schneller aus dem RAM ausführen, da dieser ohne Waitstates arbeitet.

Das folgende Beispiel erzeugt 48Mhz Systemtakt mit einem externen 8Mhz Quarz (HSE Oszillator):

// This variable is used by some CMSIS functions
uint32_t SystemCoreClock=8000000;

// Called by Assembler startup code
void SystemInit(void)
{
    // Flash latency 1 wait state
    MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_0);
    
    // Enable HSE oscillator
    SET_BIT(RCC->CR, RCC_CR_HSEON);

    // Wait until HSE oscillator is ready
    while(!READ_BIT(RCC->CR, RCC_CR_HSERDY)) {}

    // 48Mhz using the 8Mhz HSE oscillator divided by 1 with 6x PLL, lowspeed I/O runs at 24Mhz
    WRITE_REG(RCC->CFGR, RCC_CFGR_PLLSRC + RCC_CFGR_PLLMULL6 + RCC_CFGR_PPRE1_DIV1);

    // Enable PLL
    SET_BIT(RCC->CR, RCC_CR_PLLON);

    // Wait until PLL is ready
    while(!READ_BIT(RCC->CR, RCC_CR_PLLRDY)) {}

    // Select PLL as clock source
   MODIFY_REG(RCC->CFGR, RCC_CFGR_SW, RCC_CFGR_SW_PLL);

    // Update variable
    SystemCoreClock=48000000;
}

Boot Modi

Über zwei Eingänge legt man fest, von welcher Quelle der Mikrocontroller booten soll. Die Pins werden beim Reset und beim Aufwachen aus dem Standby Modus gelesen. Die Einstellung beeinflusst auch, welcher Speicher an Adresse 0x000 0000 eingeblendet wird.
Boot0Boot1Boot von
egalLowNormaler Flash Speicher, ab Adresse 0x0000 0000
LowHighSerieller Bootloader an USART1
HighHighInternes RAM, ab Adresse 0x2000 0000
Der serielle Bootloader ist ab Werk in einem separaten "Systemspeicher" vorinstalliert. Beim STM32F105 und STM32F107 unterstützt der Bootloader auch USB. Die Modelle STM32F101 und STM32F103 mit >512kB Flash können mittels "remapping" auch von USART2 booten.

Programmier- und Debug-Schnittstellen

Neben der herkömmlichen JTAG Schnittstelle haben alle Cortex-M Controller die serielle SWD Schnittstelle, welche beim Nucleo-64 Board zum Programmieren und Debuggen verwendet wird. Es sind nur drei Verbindungen nötig: Wenn man beim ST-Link Adapter die beiden als "ST-Link" beschrifteten Jumper (neben dem Quarz) entfernt, kann man den Adapter benutzen, um einen anderen STM32 Mikrocontroller zu programmieren. Zum Beispiel das rechts abgebildete "Blue Pill" Board aus China.

Wenn NRST nicht verbunden ist, muss man den Reset-Knopf drücken, um die SWD Schnittstelle zu aktivieren, und man muss ihn nach dem Flashen nochmal drücken, um das Programm zu starten.

Trace Meldungen über SWO ausgeben

Man kann den SWO bzw. TRACESWO Ausgang des Mikrocontrollers dazu benutzen, Diagnose Meldungen an den Computer zu senden. Beim Nucleo-64 Board ist SWO mit dem ST-Link Adapter verbunden.

Die Funktion ITM_SendChar(char) gibt ein Zeichen auf der SWO Leitung aus. Das folgende Beispielprogramm zeigt, wie man die SWO Leitung als Konsole benutzt:

#include <stdio.h>
#include "stm32f1xx.h"

int _write(int file, char *ptr, int len)
{
    for (int i=0; i<len; i++)
    {
        ITM_SendChar( *ptr++ );
    }
    return len;
}

int main(void)
{
  while (1)
  {
      printf("Hello World!\n");
  }
}
Damit das Programm compilierbar ist, trage in das Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Linker/Miscellaneous/Linker Flags -specs=nano.specs -specs=nosys.specs ein.

Die Ausgabe kannst du mit dem ST-Link Utility anzeigen: Und zwar mit dem Menüpunkt ST-LINK/Printf via SWO Viewer. Stelle die richtige Taktfrequenz ein und klicke dann auf Start.

Die System Workbench kann SWO Nachrichten leider nicht anzeigen, aber kommerzielle IDE's unterstützen diese Funktion.

Trace Meldungen über UART ausgeben

Alternativ kann man Meldungen über einen regulären seriellen Port ausgeben. Der ST-Link Adapter stellt dazu einen virtuellen COM-Port bereit, welcher mit USART2 des Mikrocontrollers verbunden ist: Das folgende Beispielprogramm gibt Meldungen auf USART2 aus (RxD wird nicht verwendet):
#include <stdio.h>
#include "stm32f1xx.h"

uint32_t SystemCoreClock=8000000;

int _write(int file, char *ptr, int len)
{
    for (int i=0; i<len; i++)
    {
        while(!(USART2->SR & USART_SR_TXE));
        USART2->DR = *ptr++;
    }
    return len;
}

int main(void)
{
    // Enable clock for Port A and USART2
    SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
    SET_BIT(RCC->APB1ENR, RCC_APB1ENR_USART2EN);
    
    // PA2 shall use the alternate function with push-pull
    MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF2 + GPIO_CRL_MODE2, GPIO_CRL_CNF2_1 + GPIO_CRL_MODE2_1);

    // Enable transmitter of USART2
    USART2->CR1 = USART_CR1_UE + USART_CR1_TE;

    // Set baudrate
    USART2->BRR = (SystemCoreClock / 115200);

    while (1)
    {
        printf("Hello World!\n");
    }
}
Damit das Programm compilierbar ist, trage in das Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Linker/Miscellaneous/Linker Flags -specs=nano.specs -specs=nosys.specs ein.

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 32bit groß.

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

Daten werden als 8, 16, 32bit geladen. Sie müssen nicht zwingend an der 32bit Wortgröße ausgerichtet sein. Aber man erreicht bessere Performance, wenn 16bit Daten an 16bit Adressen und 32bit Daten an 32bit Adressen ausgerichtet sind.

Stack

Der Stapel speichert ausschließlich 32bit 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 C Funktionen:

Interrupt Vektoren

Der Flash-Speicher beginnt immer mit der Interrupt-Vektor Tabelle. Der Quelltext dazu befindet sich in der Datei startup/startup_stm32.s. Dort findest du auch die Namen der vorgegebenen Exception-Handler Funktionen.

Jeder Eintrag in der Tabelle ist eine 32bit Sprungadresse. Bei allen Sprungadressen muss das Bit 0 gesetzt sein, es wird aber immer an die Adresse davor gesprungen. Der Wert 0x1001 führt zu einem Sprung nach Adresse 0x1000.

AdresseException Nr.CMSIS Interrupt Nr.KürzelC-FunktionBeschreibung
ARM Prozessor Exceptions
0x0000  MSP Startwert für den Haupt-Stapelzeiger MSP
0x00041 Reset Adresse des Startup-Codes, Priorität -3
0x00082-14NMINMI_HandlerNicht maskierbarer Interrupt, Priorität -2, wird vom RCC Clock Security System verwendet.
0x000c3-13HardFaultHardFault_HandlerWird bei allen Fehlern aufgerufen, Priorität -1
0x00104-12MemManageMemManage_HandlerWenn aktiviert, werden diese drei mehr spezifischen Exceptions anstelle von HardFault ausgelöst. Sie sind standardmäßig deaktiviert.
0x00145-11BusFaultBusFault_Handler
0x00186-10UsageFaultUsageFault_Handler
0x001creserviert
0x0020
0x0024
0x0028
0x002c11-5SVCallSVC_HandlerSupervisor Call, wird durch den SVC Befehl ausgelöst. Wird von Programmen genutzt, um Funktionen des Betriebssystems aufzurufen.
0x003012-4DebugDebugMon_HandlerDebug Monitor für softwarebasiertes Debugging (selten genutzt)
0x0034reserviert
0x003814-2PendSVPendSV_HandlerPendable Request for System Service. Wird durch Beschreiben des ICSR Registers ausgelöst. Betriebssysteme nutzen es beim Umschalten des Kontextes. Programme ohne Betriebsystem nutzen es, um Aktionen mit geringer Priorität durchzuführen, die ein bisschen verzögert werden dürfen.
0x003c15-1SYSTICKSysTick_HandlerSystemtimer
STM32 Hardware Interrupts
0x0040160WWDG Watchdog
0x0044171PVD EXTI Line Detection
0x0048182TAMPER Sabotage Signal vom Tamper Eingang
0x004c193RTC Echtzeituhr
0x0050204FLASH Schreibzugriff auf Flash wurde beendet
0x0054215RCC Wenn ein Oszillator oder die PLL bereit ist
0x0058226EXTI0 Externe Interrupt Leitungen
0x005c237EXTI1 
0x0060248EXTI2 
0x0064259EXTI3 
0x00652610EXTI4 
...Und so weiter. Die Komplette Liste ist in STM32F1 Reference Manual Kapitel: Interrupt and exception vectors)

Über das VTOR Register kann man den Ort der Vektortabelle verändern und sie z.B. ins RAM verschieben.

Reset, NMI und HardFault haben stets die allerhöchste Priorität. Bei allen anderen Interrupts kann man die Priorität einstellen. Interrupt-Routinen können nur durch höher priorisierte Interrupts unterbrochen werden.

Die wichtigsten Funktionen zur Konfiguration des Interrupt-Controllers sind:

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.

Anwendungsbeispiel:

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

uint32_t SystemCoreClock=8000000;
volatile uint32_t systick_count=0;

void SysTick_Handler(void)
{
    systick_count++;
}

int main(void)
{
    // Initialize the timer for 1ms intervals
    SysTick_Config(SystemCoreClock/1000);
    ...
    
    // Delay 2 seconds
    uint32_t start=systick_count;
    while (systick_count-start<2000);
    
    ...
}
Wenn der Prozessor beim Debuggen angehalten wird, hält auch dieser Timer an. Im WFI und WFE Sleep Modus läuft der SysTick Timer weiter.

Power Management

ModusEintrittAufwachenBeschreibung
Active Normalbetrieb
WFI Sleep__WFI()Interrupt*Warte auf Interrupt. Nur die CPU wird angehalten.
WFE Sleep__WFE()Interrupt* oder EreignisWarte auf Ereignis. Nur die CPU wird angehalten.
StopPPDS=1 + LPDS=1 + SLEEPDEEP=1 + __WFI() oder __WFE()EXTI Leitung, oonfiguriert im EXTI Register. Taktsignale sowie HSI und HSE Oszillatoren sind deaktiviert. Die I/O Pins, Register und RAM bleiben unverändert. Debugging ist nicht möglich.
StandbyPPDS=1 + SLEEPDEEP=1 + __WFI() oder __WFE()Steigende Flanke an WKUP Pin, RTC Alarm, externer Reset am NRST, IWDG Reset. Taktsignale sowie HSI und HSE Oszillatoren sind aus geschaltet. 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.
* 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 von Bit 1 (SLEEPONEXIT ) im Register SBC->SCR aktiviert man die "Sleep on exit" Funktion. Diese bewirkt, dass der Prozessor nach Abarbeitung jeder Interruptroutine automatisch in den WFI Sleep Modus geht.

Ereignisse können sowohl von Software (mittels __SEV() Funktion) als auch von Hardware ausgelöst werden.

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

Die Taktsignale für Peripherie können jederzeit verlangsamt oder ganz abgeschaltet werden, um Energie zu sparen.

USB Schnittstelle

In diesem Kapitel geht es darum, die USB Schnittstelle des STM32F1 Mikrocontrollers zu verwenden. Beim Nucleo-64 Board fehlt leider ein solcher Anschluss, deswegen habe ich die folgenden Schritte mit dem rechts oben abgebildeten "Blue Pill" Board ausprobiert.

Der USB Port ist nur verwendbar, wenn man die CAN Schnittstelle nicht benutzt. Die USB Schnittstelle muss mit 48Mhz aus einem Quarz (HSE Oszilator) betrieben werden. Der interne HSI Oszillator ist dazu nicht geeignet.

Virtueller COM-Port mit Cube HAL

Auf der Webseite von STM gibt es den "STM32 Virtual COM Port Driver" zum Herunterladen, aber den braucht man gar nicht.

Mit dem Programm CubeMX kann man sich ein Projekt mit USB Unterstützung zusammenklicken. Das geht so:

Compiliere das Programm ohne jegliche Änderung und übertrage es in den Mikrocontroller. Danach musst du das USB Kabel einmal abziehen und wieder anstecken. Der PC sollte jetzt im Gerätemanager einen neuen virtuellen COM Anschluss mit dem Namen "Serielles USB-Gerät (COMx)" anzeigen.

Wir wollen jetzt etwas Text vom Mikrocontroller an den PC senden. Füge dazu im Quelltext von main.c folgendes zwischen die genannten Markierungen ein:

/* USER CODE BEGIN Includes */

#include "usbd_cdc_if.h"

/* USER CODE END Includes */

...

  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */

        // LED On
        HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET);
        HAL_Delay(500);

        // LED Off
        HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);
        HAL_Delay(500);

        // Send data
        char msg[]="Hallo!";
        CDC_Transmit_FS( (uint8_t*) msg, strlen(msg));

  }
  /* USER CODE END 3 */

Compiliere das Programm erneut und übertrage es in den Mikrocontroller. Danach musst du das USB Kabel wieder abziehen und anstecken. Die LED blinkt im Sekundentakt. Starte ein Terminalprogramm (z.B. Hammer Terminal) und öffne den virtuellen COM-Port. Die Baudrate spielt keine Rolle. Du empfängst jetzt jede Sekunde den Text "Hallo!".

Arduino Umgebung

Alternativ kann man STM32 Mikrocontroller mit der STM32 Erweiterung für Arduino programmieren. Mit dem Arduino Framework ist das Programmieren viel einfacher, aber man kann nicht alle Funktionen des Chips ausreizen.

Installiere zuerst die aktuelle Arduino IDE. Danach muss man über Werkzeuge/Board/Boardverwalter die Unterstützung für Arduino SAM Boards hinzufügen, darin befindet der benötigte Compiler für ARM Cortex-M3 und M4 Mikrocontroller. Diese Erweiterung unterstützt allerdings nur das "Arduino Due" Board. Für weitere Boards installierst du jetzt noch die STM32 Erweiterung.

Optional kannst du dein Board mit dem Maple Bootloader ausstatten, damit der Chip auch über seinen USB Port programmierbar ist. Ohne den Bootloader müsste man entweder den seriellen Port mit externem USB-UART Adapter oder einen ST-Link benutzen. Der Bootloader erscheint im Windows Gerätemanager als "Maple 003" oder "Maple DFU". Der dazugehörige Windows Treiber befindet sich in der STM32 Erweiterung im Verzeichnis Arduino_STM32-master\drivers\win\maple-dfu. Für Linux braucht man keinen Treiber.

Solange kein Sketch installiert ist, bleibt der Bootloader ständig aktiv, was er durch eine schnell blinkende LED anzeigt. Wenn jedoch ein Sketch installiert ist, dann meldet sich der Bootloader schon eine Sekunde nach dem Reset ab und startet den Sketch. Der Bootloader verschwindet dann aus dem Windows Gerätemanager und stattdessen erscheint ein virtueller COM-Port.

Normalerweise kann die Arduino IDE den laufenden Sketch anhalten und auf den Bootloader zurück schalten, um erneut einen Sketch hochzuladen. Falls das mal nicht funktioniert, hilft folgende Prozedur:

Falls du einen besseren Texteditor für Arduino sucht, dann schau Dir mal diese Anleitung an. Dort geht es zwar um einen anderen Mikrocontroller, doch die Vorgehensweise ist sehr ähnlich.

Virtueller COM-Port mit Arduino

Der virtuelle COM-Port ist fester Bestandteil der STM32 Erweiterung für Arduino, deswegen ist er sehr einfach zu programmieren:
void setup() 
{
    // PC13 is connected to the status LED
    pinMode(PC13, OUTPUT);
}

void loop() 
{
    digitalWrite(PC13, LOW);
    Serial.println("Tick");
    delay(500);

    digitalWrite(PC13, HIGH);
    Serial.println("Tack");
    delay(500);
}
Die Ausgabe kannst du im Seriellen Port Monitor der Arduino IDE sehen. Falls du lieber das Hammer Terminal benutzt, musst du den "DTR" Knopf unterhalb des Ausgabefensters einschalten.