Hallo ARM C

Die erste Übung in jedem Programmierkurs ist das berühmte „Hallo Welt“. Damit wird versucht, dem Lernenden ein motivierendes „AHA-Erlebnis“ zu vermitteln. OK mal sehen, ob wir das auch hin bekommen. Bei der Programmierung von eingebetteten Systemen besteht oft das Problem, dass kein Bildschirm oder Display zur Textausgabe angeschlossen ist. Dann stehen für das „sich bemerkbar machen“ dem System nur LEDs zur Verfügung. Also leuchten und blinken eingebettete Systeme somit ihre Botschaft in die Welt.

Die erste Übung soll das typische LED einschalten sein. Dazu nutzen wir die blaue LED auf dem STM32F4 Discovery Board. Die blaue LED ist bereits fest mit dem Pin PD15 verbunden.

d15anschalten.jpg

Aus dem Datenblatt des STM32F4xx ( schauen Sie auf Seite 18) können wir entnehmen, das dieser über drei AHB verfügt. Für uns ist erst mal nur der AHB1 interessant. An AHB2 und AHB3 hängen spezielle Geräte, wie die Kameraschnittstelle oder ein externer Speicher. Über AHB1 erreichen wir die GPIO Ports und die zwei Peripheriebusse APB1 und APB2. Die digitalen Eingabe- und Ausgabeleitungen hängen direkt an AHB1 und sind zu 9 Ports (A bis I) mit jeweils 16 Leitungen (Bit 0 bis 15) zusammengefasst. Digitale Ein- und Ausgabe sind die primären Funktionen der Pins und im Pin-Out entsprechend als Pin-Namen gekennzeichnet.

Die Aufgabe besteht also darin:

  1. über den AHB1 Bus den GPIO Port D zu aktivieren, indem dieser mit einem Taktsignal versorgt wird
  2. das Bit 15 des GPIOD als Ausgang zu konfigurieren
  3. und das Pin auf High zu schalten

Falls das Tutorial-Projekt nicht mehr offen ist, öffnen Sie dies. Legen Sie bitte ein neues kleines Programm an und laden das Grundgerüst ARM C++ Anwendung. Beachten Sie die Einstellungen für die Zielplattform STM32F4-Discovery.

halloarm1.jpg

Erstellen Sie die Programmkopfdokumentation. Übersetzen und übertragen Sie das noch leere Programm auf den Controller, um die Verbindung zu testen.

//----------------------------------------------------------------------
// Titel     : Beispiel Hallo Welt mit SiSy ARM
//----------------------------------------------------------------------
// Funktion  : schaltet die blaue LED an
// Schaltung : blaue LED an GPIO Port D15
//----------------------------------------------------------------------
// Hardware  : STM32F4 Discovery
// Takt      : 168 MHz
// Sprache   : ARM C++
// Datum     : heute
// Version   : 1
// Autor     : ich
//----------------------------------------------------------------------

Als Erstes diskutieren wir kurz die nötigen Lösungsschritte. Wie bereits ausgeführt, sind nach dem RESET alle Peripheriegeräte ausgeschaltet. Demzufolge muss der I/O-Port, an dem die LED angeschlossen ist, erst mal eingeschaltet werden. Jetzt schauen wir uns das Blockbild zum STM32F4 zum Beispiel im Datenblatt Seite 18 oder dieses vereinfachte Blockbild an. Man erkennt, dass der RCC-Unit (Reset & Clock Control ) mitgeteilt werden muss, dass GPIOD über den AHB1 mit Takt zu versorgen ist. Dazu benutzen wir die Funktion RCC_AHB1PeriphClockCmd aus den STM32F4-Peripherie-Treibern. Die Hilfe zu der Funktion können wir uns über den Editor, rechte Maustaste, Hilfe STM32 anschauen.

Die Funktion RCC_AHB1PeriphClockCmd benötigt zwei Parameter. Parameter eins bestimmt das Gerät und Parameter zwei den neuen Status des Gerätetaktes. Daraus ergibt sich folgende Befehlszeile:

/* GPIOD Takt einschalten  */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);

Jetzt ist der GPIO Port D angeschaltet. Das ist eine wichtige Voraussetzung, damit wir diesem Initialisierungskommandos senden können. Die Initialisierung eines Gerätes, selbst eines einfachen I/O-Ports des 32Bit ARM Cortex, ist um einiges aufwendiger, als beim kleinen 8 Bit AVR. Zur Vereinfachung gibt es für den Programmierer die Möglichkeit, Geräte über Strukturen und Treiberfunktionen zu initialisieren. Diese abstrahieren die Hardware und fassen alle nötigen Einstellungen kompakt zusammen. Die STM32 Peripherie-Treiber und auch CMSIS haben in der Regel für jedes Gerät mindestens eine Initialisierungsstruktur und zwei korrespondierende Funktionen. Die erste Funktion initialisiert eine angelegte leere Initialisierungsstruktur mit den Grundeinstellungen für das Gerät und die zweite führt die Initialisierung nach den Vorgaben des Entwicklers aus. Damit ergibt sich folgendes Strickmuster zur Initialisierung von Geräten:

  1. Takt einschalten, RCC_xxxClockCmd
  2. Initialisierungsstruktur anlegen, xxx_InitTypDef initStruct
  3. Struktur mit Standardwerten füllen, xxx_StructInit (&initStruct)
  4. Anwendungsspezifische Anpassungen vornehmen, initStruc.xxx_Mode = wert
  5. Gerät initialisieren, xxx_Init(xxx, &initStructure)

Der Takt für Port D ist bereits aktiviert. Demzufolge ist als Nächstes die Initialisierungsstruktur anzulegen und mit Standardwerten zu füllen. Strukturen und Funktionen finden sich in der Hilfe im Abschnitt des jeweiligen Gerätes. Der entsprechende Quellcode für den GPIO Port D sieht wie folgt aus:

GPIO_InitTypeDef  GPIO_InitStructure;
GPIO_StructInit (&GPIO_InitStructure);

Nun müssen die anwendungsspezifischen Einstellungen angegeben werden. Das erfolgt durch Zuweisung der entsprechenden Werte zu den einzelnen Elementen der Initialisierungsstruktur. Die möglichen Strukturelemente und Werte sind wiederum der Hilfe entnehmbar.

Bei den Werten für die Strukturelemente handelt es sich um Aufzählungen bzw. Bitdefinitionen, welche als selbsterklärende Bezeichner deklariert wurden.

Strukturelement GPIO_Pin Die Werte können angegeben werden mit GPIO_Pin_0 bis GPIO_Pin_15. Hier handelt es sich um Bitdefinitionen. Diese können ODER-verknüft werden, um zum Beispiel mehrere Pins gleichzeitig anzusprechen.

Strukturelement GPIO_Mode Dabei handelt es sich um eine Aufzählung. Diese Werte können nicht kombiniert werden, sondern schließen sich gegenseitig aus.

  • GPIO_Mode_IN, GPIO Input Mode, der/die Pins werden als Eingang betrieben
  • GPIO_Mode_OUT, GPIO Output Mode, der/die Pins werden als Ausgang betrieben
  • GPIO_Mode_AF, GPIO Alternate function Mode, der/die Pins werden nicht als GPIO betrieben, sondern erhalten eine alternative Peripheriefunktion zugewiesen
  • GPIO_Mode_AN, GPIO Analog Mode, der/die Pins werden als Analogeingang betrieben

Strukturelement .GPIO_OType Dieser Wert bezieht sich auf den Output Mode. Die Einstellungen schließen einander aus.

  • GPIO_OType_PP, Push Pull, der Ausgangstreiber arbeitet als Gegentaktstufe, gibt definiert entweder High oder Low aus
  • GPIO_OType_OD, Open Drain, der Ausgangstreiber schaltet nur gegen Masse und ist ansonsten hochohmig, vgl. Open Collector, lässt sich mit PullUp Widerstand kombinieren

Strukturelement GPIO_Speed Gibt an, mit welcher Zykluszeit die Register des Ports aktualisiert werden, also wie schnell zum Beispiel Änderungen zwischen Register und Treiberstufen durchgeschaltet werden.

  • GPIO_Speed_2MHz
  • GPIO_Speed_25MHz
  • GPIO_Speed_50MHz
  • GPIO_Speed_100MHz

Strukturelement GPIO_PuPd Der STM32 verfügt über interne PullUp und PullDown Widerstände. Diese können wahlweise aktiviert werden. Die Werte schließen sich gegenseitig aus.

  • GPIO_PuPd_NOPULL, kein PullUp oder PullDown aktiviert
  • GPIO_PuPd_UP, PullUp Widerstand aktivieren
  • GPIO_PuPd_DOWN, PullDown Widerstand aktivieren

Für unsere LED ergibt sich, dass diese an Pin15 angeschlossen ist, dieser als Ausgang betrieben werden soll und keine PullUp oder PullDown benötigt werden. Der mögliche Quellcode sieht wie folgt aus:

GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

Damit sind alle relevanten Einstellungen in der Initialisierungsstruktur vorbereitet. Jetzt kann die eigentliche Initialisierung erfolgen. Das geschieht mit der Funktion GPIO_Init. Diese efordert die Angabe des GPIO-Port und der vorbereiteten Initialisierungsstruktur.

GPIO_Init(GPIOD, &GPIO_InitStructure);

Für das An- oder Ausschalten von GPIO-Pins stehen die Funktionen GPIO_SetBit und GPIO_ResetBit zur Verfügung. Diese Funktionen erwarten den Port und die Pins, welche zu schalten sind.

GPIO_SetBits(GPIOD,GPIO_Pin_15);

Es wird wohl deutlich, dass selbst die Initialisierung eines einfachen Ausgangs, um eine LED einzuschalten, recht aufwendig ist. Dabei war dies nur das Minimum im Umgang mit einem GPIO-Port. Der ARM gibt dem Anwendungsentwickler noch viel umfangreichere Möglichkeiten.

Gewöhnen wir uns gleich daran einigermaßen systematisch vorzugehen. Bevor wir die Befehle in unseren Code wild hineinhacken, schreiben wir erst die Kommentare, was wir an dieser oder jener Stelle im Code tun wollen.

//----------------------------------------------------------------------
// Titel     : Beispiel Hallo Welt mit SiSy STM32
//----------------------------------------------------------------------
// Funktion  : schaltet die blaue LED an
// Schaltung : blaue LED an GPIO Port D15
//----------------------------------------------------------------------
// Hardware  : STM32F4 Discovery
// Takt      : 168 MHz
// Sprache   : ARM C++
// Datum     : heute
// Version   : 1
// Autor     : Alexander Huwaldt
//----------------------------------------------------------------------
#include <stddef.h>
#include <stdlib.h>
#include "hardware.h"
 
void initApplication()
{
	SysTick_Config(SystemCoreClock/100);
 
	// GPIOD Takt einschalten 
	// Konfiguriere GPIO Port D15 für die LED
 
}
 
int main(void)
{
	SystemInit();
	initApplication();
	do{
 
		// LED anschalten,
 
	} while (true);
	return 0;
}
 
extern "C" void SysTick_Handler(void)
{
    // Application SysTick default 10ms
}

Jetzt nehmen wir die Finger von der Tastatur, atmen tief durch und schauen noch mal in Ruhe über unseren Entwurf. Dann kann es los gehen.

Ergänzen Sie den Quellcode des Beispiel HalloARM wie folgt: Nutzen Sie die Codevervollständigung des Editors. Die in den Treibern systematisch festgelegten Bezeichner folgen einem einfach einzuprägenden Muster:

Gerät_TeilKomponente_Was

Der Bezeichner beschreibt einen Pfad vom Allgemeinen (dem Gerät) zum Speziellen (z.B. einer konkreten Funktion oder einem Bit). Zum Beispiel finden Sie alle Funktionen zur Reset and Clock Control Unit unter *RCC_* .

Nach drei zusammenhängenden Buchstaben springt die Codevervollständigung an und listet alle Bezeichner fortlaufend gefiltert nach dem Stand der Eingabe. Wählen sie jetzt die Taste CUD (Cursor/Pfeil nach unten), können Sie in der Liste rollen und per Enter einen Eintrag auswählen. Schauen Sie sich dieses kurze Video an.

Also dann, viel Erfolg bei den ersten richtigen Programmierschritten.

//----------------------------------------------------------------------
// Titel     : Beispiel Hallo Welt mit SiSy STM32
//----------------------------------------------------------------------
// Funktion  : schaltet die blaue LED an
// Schaltung : blaue LED an GPIO Port D15
//----------------------------------------------------------------------
// Hardware  : STM32F4 Discovery
// Takt      : 168 MHz
// Sprache   : ARM C++
// Datum     : heute
// Version   : 1
// Autor     : Alexander Huwaldt
//----------------------------------------------------------------------
#include <stddef.h>
#include <stdlib.h>
#include "hardware.h"
 
void initApplication()
{
	SysTick_Config(SystemCoreClock/100);
	// weitere Initialisierungen durchführen
 
	/* GPIOD Takt einschalten  */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
 
	/* Konfiguriere GPIO Port D15 */
	GPIO_InitTypeDef  GPIO_InitStructure;
	GPIO_StructInit (&GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOD, &GPIO_InitStructure);
 
}
int main(void)
{
	SystemInit();
	initApplication();
	do{
		GPIO_SetBits(GPIOD,GPIO_Pin_15);
	} while (true);
	return 0;
}
 
extern "C" void SysTick_Handler(void)
{
    // Application SysTick default 10ms
}

Übersetzen Sie das Programm. Korrigieren Sie ggf. Schreibfehler. Übertragen Sie das lauffähige Programm in den Programmspeicher des Controllers.

  1. Kompilieren
  2. Linken
  3. Brennen

Gratulation! Sie haben Ihre erste Ausgabe realisiert. Die blaue LED auf dem STM32F4 Discovery leuchtet jetzt.

Videozusammenfassung

Fassen wir nochmal kurz zusammen, was es sich einzuprägen gilt:

Initialisierungssequenz für Geräte:

  1. Takt einschalten, RCC_xxxClockCmd
  2. Initialisierungsstruktur anlegen, xxx_InitTypDef initStruct
  3. Struktur mit Standardwerten füllen, xxx_StructInit (&initStruct)
  4. Anwendungsspezifische Anpassungen vornehmen, initStruc.xxx_Mode = wert
  5. Gerät initialisieren, xxx_Init(xxx, &initStructure)

Initialisierungsstruktur für Digitalports: GPIO_InitTypeDef initStruct;

  • initStruct.GPIO_Pin = GPIO_Pin_0..15;
  • initStruct.GPIO_Mode = GPIO_Mode_OUT|IN|AF;
  • initStruct.GPIO_OType = GPIO_OType_PP|OD;
  • initStruct.GPIO_Speed = GPIO_Speed_2..100MHz;
  • initStruct.GPIO_PuPd = GPIO_PuPd_NOPULL|UP|DOWN;

wichtige Funktionen für Digitalports:

  • RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE|DISABLE);
  • GPIO_StructInit (&initStruct);
  • GPIO_Init(GPIOD, &initStruct);
  • GPIO_SetBits(GPIOD,GPIO_Pin_x);
  • GPIO_ResetBits(GPIOD,GPIO_Pin_x);

Und hier diesen Abschnitt wiederum als Videozusammenfassung.

Nächstes Thema