NUCLEO-F103RB Board

Startseite

Erste Schritte mit ARM Cortex-M3 Mikrocontrollern STM32F1xx

Ich lerne gerade die Programmierung von 32bit ARM Controllern, konkret mit dem Nucleo-F103RB Board von der Firma STMicroelectronics. Auf dieser Seite sammle ich meine Erkenntnisse - für mich und für andere Anfänger, die Hilfe suchen.

Ich empfehle, zuerst den Umgang mit kleineren 8bit Mikrocontrollern (zum Beispiel AVR) zu lernen. Die ARM Controller sind nämlich erheblich komplexer. Ich schätze, dass Anfänger ohne Vorkenntnisse mit der umfangreichen Dokumentation überfordert sind.

Nucleo-F103RB

Das Nucleo-F103RB Board (alias Nucleo-64 mit STM32F103RBT6) ist ein äußerst preisgünstiges Entwicklung-Kit mit folgenden Eigenschaften: Zum Betrieb benötigt man ein USB Kabel mit Mini-USB Stecker.

Dokumentationen

Ich empfehle, die folgenden Dokumente zu lesen:

Software

Nach einigen Versuchen mit anderen Alternativen bin ich bei dieser Windows Software angelangt: Bei der Cube HAL handelt es sich um eine Abstraktions-Library, die den Zugriff auf die Funktionen des Mikrocontrollers erleichtern soll. Sie stellt Funktion wie HAL_GPIO_Read_Pin() und HAL_TIM_PWM_Start() bereit. 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.

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 CMSIS Library von ARM 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. Man kann sie allerdings nicht einzeln downloaden.

Projekt auf Basis von CMSIS erzeugen

Der Assistent in der IDE kann nur neue Projekte anlegen die entweder auf Cube HAL oder der alten StdPeriph Library aufbauen. Ein einfacher LED-Blinker mit HAL belegt dabei schon ca. 4 kB Flash Speicher. Programme auf Basis der CMSIS Library sind deutlich schlanker, aber der Assistent der IDE kann solche Projekte nicht direkt anlegen. Man muss dazu folgendermaßen vorgehen:
  1. Lege zuerst ein temporäres Projekt mit dem Assistenten in der IDE an, es soll die Cube HAL Library enthalten.
    • Dadurch erhält man ein Verzeichnis mit dem Namen CMSIS, dass die benötigte CMSIS Library enthält.
  2. Lege mit dem Assistenten der IDE das eigentliche Arbeitsprojekt mit der Option "No Firmware" an.
  3. Kopiere das ganze CMSIS Verzeichnis von dem temporären Projekt in dein Arbeitsprojekt.
  4. Füge die beiden Verzeichnisse mit den *.h Dateien unter CMSIS 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.
    • Je nach Version ist das CMSIS Verzeichnis unterschiedlich strukturiert. In meinem Fall brauche ich die beiden Verzeichnisse:
      • CMSIS/core
      • CMSIS/device
  5. Editiere die Datei stm32f1xx.h. Unter dem Kommentar "Uncomment the line below according to the target STM32 device" (ungefähr Zeile 90) musst du die Definition #define STM32F103xB auskommentieren.
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 mit der rechten Maustaste auf den Projektnamen, dann Target/Program Chip... wählen.

Programmierung

Compiler 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. Das obige Blink-Programm reduziert sich so von ca. 2kB auf ca. 700 Bytes.

Trage dazu in das Textfeld unter Properties/C/C++ Build/Settings/Tool Settings/MCU GCC Linker/Miscellaneous/Linker Flags --specs=nano.specs ein.

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 _printf_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 Linker/Miscellaneous/Linker Flags -Wa,-adhlns="$(@:%.o=%.lst)" ein.

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

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 (system.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

Fallstricke

Wenn man I/O Funktionen anspricht, ohne dass sie einen Takt haben, 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.

Systemtakt

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:

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

void SystemInit(void)
{
    // 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);

    // Flash latency 2 wait states
    MODIFY_REG(FLASH->ACR, FLASH_ACR_LATENCY, FLASH_ACR_LATENCY_1);

    // 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);
}
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"))) kann man Funktionen etwas schneller aus dem RAM ausführen, das dieser ohne Waitstates arbeitet.

Elektrische Eigenschaften

Dieses Kapitel ist noch leer