Einführung in die Programmierung

Vorweg ein Hinweis: Die STM32 bieten extrem viele Funktionen, die mitunter nur schlecht Dokumentiert sind. Aus leidvoller Erfahrung kann ich nur jedem Raten sich das Datenblatt, Reference Manual und Application Notes des entsprechenden Controller und den zu verwendenden Funktionen gut durch zu lesen und vor allem auch Fußnoten und Randnotizen zu beachten. Häufig genug kommt es vor, das bestimmte Limitationen oder Voraussetzungen nur am Rande erwähnt werden. Zudem immer das Errata Sheet lesen und schauen, ob es nicht Probleme mit irgendwelchen Modulen gibt.
Das ist kein Tutorial für Programmieranfänger oder Neueinsteiger in die Welt der Mikroprozessoren/Controller. Das lernen einer Programmiersprache ist auf dem Computer mit weniger Frust verbunden, wer mit Hardware spielen will ist mit einem Arduino oder ähnlichem Board erstmal besser aufgehoben :-)

Entwicklungstools

Als erstes benötigt man einen Compiler/Toolchain. Davon gibt es einige, die aber von anderen schon aufgelistet wurden, daher nur der Verweis darauf: Auflistung Compiler und IDEs bei Mikrocontroller.net
Dann gibt es 2 Frameworks, die zum Programmieren notwendig und hilfreich sind. Das wären einmal die CMSIS, das von ARM stammt um ein einheitliches Prozessorinterface zu schaffen(was aber leider nicht gut umgesetzt ist) und die SPL, die „Standard Peripheral Library“ von ST. Die SPL ist ein Treiberinterface, das einem das Konfigurieren und Nutzen der unterschiedlichen Peripherie erleichtern soll. Viele der Beispiele von ST basieren auf Funktionen der SPL. Die SPL sorgt aber leider auch dafür, dass triviale Konfiguration unheimlich viel Text produziert und Code damit, in den Augen einiger Entwickler, unnötig aufbläht, wobei das Geschmackssache ist.
Einige Hardware Treiber sind jedoch nicht in der SPL, so zum Beispiel der Ethernet Treiber, den man erst zusätzlich Herunterladen muss.
Viele IDEs, wie zum Beispiel EmBlocks liefern die SPL und CMSIS für die meisten STM32 Prozessoren schon mit, sodass man sich nicht darum kümmern muss.
Zum Flashen/Programmieren der STM32 benötigt man noch ein geeignetes Tool, das kann entweder ein Programmer/JATG-Interface der bekannten Hersteller sein, oder sofern der Bootloader vorhanden ist die serielle Schnittstelle, ein Flash-Tool sowie einige Hinweise zur Hardware gibt es hier: STM32 Flash Tool
Zum Debuggen ist ein entsprechender Debugger erforderlich, den man in der Regel schon mit eingeplant hat um den STM32 zu Programmieren/Flashen.

Grundlegendes zur Firmware

Die STM32 werden wie alle auf den Cortex-M Kernen basierenden Controller/Porzessoren ausschließlich über Register konfiguriert. Fuses wie bei den AVRs oder spezifische Konfiguration wie bei PICs gibt es nicht.
Auch bieten die STM32 flexible Möglichkeiten für den Systemtakt. Man sollte sich daher als erstes darum kümmern, dass der Systemtakt dem entspricht, was man haben möchte. Dabei muss man beachten, dass die Systemtakt Einstellungen in der Funktion

SystemInit();

vorgenommen werden und in der Regel nach Aktivierung nicht verändert werden können. Das macht aber nichts, denn die Einstellungen sind in der Datei „system_stm32f2xx.c“(Oder ähnlich) konfigurierbar.
Um Energie zu sparen wird generell keine Peripherie mit einem Taktsignal versorgt, sodass diese inaktiv ist. Daraus ergibt es sich, dass zur Nutzung einer Peripherie erst der Takt aktiviert werden muss. Für einen STM32F207 kann das so aussehen:

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

Hier wird dem GPIO Modul A, dass an dem AHB1 Bus hängt der Takt freigegeben Auch muss man beachten, dass man auch den Takt für alle anderen notwendigen Peripheriemodule anschalten muss, so muss zum Beispiel auch der Takt für die I/Os aktiviert werden, wenn man ein PWM Signal herausgeben möchte. Auch muss man beachten, dass die einzelnen Module an unterschiedlichen internen Bussen hängen, genaues dazu findet man im Datenblatt.

Auch haben die GPIOs viel mehr Funktionalität und sind immer mehrfach mit Sonderfunktionen belegt, die aber erst aktiviert werden müssen. Will man also auf zwei Pins UART Rx und Tx legen, reicht es nicht wie bei z.B AVRs die Richtung und das UART Modul zu konfigurieren, sondern man muss auch die Entsprechende Sonderfunktionalität aktivieren:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
 
GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);  //Alternativ Funktionalität USART1 für Pin 9 an GPIOA Aktivieren
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;               //Pin auf Alternate Function setzen
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;             //Pin Typ Einstellen
GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;              //Pull-Up/Down Widerstände konfigurieren
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;         //Geschwindigkeit einstellen, dient zur Optimierung
GPIO_Init(GPIOA, &GPIO_InitStructure);                     //Pin Initialisieren
 
/* RX Pin */
GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

Dafür sind Funktionen auch häufiger mal auf mehrere Pins gelegt, sodass man sich aussuchen kann, welcher Pin dann am besten geeignet ist.
Beispiele für die Nutzung von Peripherie findet man in der SPL. Es ist dabei aber mitunter notwendig, die SPL direct von ST herunter zu laden, da viele IDEs auf die Beispiele verzichten.
Interrupts werden über eine Interrupt-Tabelle aufgerufen. Will man also eine Interrupt Service Routine implementieren, so muss man seine Funktion in die Tabelle eintragen. Um das zu vereinfachen wird in der Regel in der Startup-Assemblerdatei(z.B startup_stm32f2xx.S) Referenzen auf die Entsprechenden Funktionen gelegt, sodass man nur seine Funktion richtig benennen muss, damit der Linker die Adressen Richtig eintragen. In einer von EmBlocks generierten Startup-Datei könnte das z.B so aussehen:

               // External Interrupts
[...}
    .long    USART1_IRQHandler                 // USART1
    .long    USART2_IRQHandler                 // USART2
    .long    USART3_IRQHandler                 // USART3
    .long    EXTI15_10_IRQHandler              // External Line[15:10]s
    .long    RTC_Alarm_IRQHandler              // RTC Alarm (A and B) through EXTI Line
    .long    OTG_FS_WKUP_IRQHandler            // USB OTG FS Wakeup through EXTI line
    .long    TIM8_BRK_TIM12_IRQHandler         // TIM8 Break and TIM12
    .long    TIM8_UP_TIM13_IRQHandler          // TIM8 Update and TIM13
    .long    TIM8_TRG_COM_TIM14_IRQHandler     // TIM8 Trigger and Commutation and TIM14
    .long    TIM8_CC_IRQHandler                // TIM8 Capture Compare
[...]

Will man also auf einen USART Interrupt reagieren, so reicht es eine Funktion

void USART1_IRQHandler( void )

anzulegen und den Entsprechenden Interrupt zu Aktivieren. Wird ein Interrupt ausgelöst, für den man keine ISR angelegt hat, stürzt der Controller ab. Es ist daher ratsam, genau zu schauen, welche Interrupts man aktiviert hat, vor allem wenn man Code aus Beispielen kopiert. In der SPL sind die Interrupt Handler meistens in einer Datei „stm32f2xx_it.c“(Oder ähnlich) zusammengefasst und können mal schnell übersehen werden.
Man sollte immer zuerst die Hardware richtig Konfigurieren und sich Wrapper Funktionen schreiben, damit man möglichst wenig direkt mit den Prozessorregistern zu tun hat. Hat man das geschafft, ist es wie jedes C/C++ Projekt auch.