Laborpraktikum Embedded Systems

Laborpraktikum
Embedded Systems
Sommersemester 2016
Stand: 30.04.2016
Technische Hochschule Mittelhessen (THM)
Campus Friedberg
Fachbereich IEM
Wilhelm-Leuschner-Straße 13
61169 Friedberg
Vorwort
Zu den Lernzielen des Laborpraktikums "Embedded Systems":
a) Praktische Umsetzung
Praktische Umsetzung und Vertiefung der in der Vorlesung Embedded Systems theoretisch
vermittelten Kenntnisse mit Hilfe der bereitgestellten Lernumgebung.
b) Programmtests
Die entwickelten Programme zur Ansteuerung von Anwendungsplatinen sollen mit Hilfe eines
Hochsprachen-Debuggers selbstständig getestet und bei Auftreten von logischen Programmfehlern eigenständig korrigiert werden.
c) Ingenieurmäßiges Arbeiten
Durch die Teilnahme am Laborpraktikum soll ingenieurmäßiges Arbeiten geübt werden. Ähnlich
der Arbeit in einer Entwicklungsabteilung der Industrie sollen gestellte technische Aufgaben in
anforderungsgemäß arbeitende, klar strukturierte und kommentierte Programme umgesetzt
werden.
d) Teamarbeit
Die Aufgaben des Laborpraktikums „Embedded Systems“ sollen durch die Studierenden größtenteils alleine gelöst werden. Dies bedeutet jedoch nicht, dass Teamarbeit generell unerwünscht wäre. Eine gegenseitige Unterstützung bei der Lösung der Aufgaben ist also durchaus
erwünscht.
Weitere Hinweise:
Unterlassen Sie bitte das Kopieren von Programmen anderer Gruppen, denn ein Lernerfolg ist nur
durch eigenständige Arbeit möglich.
Zur Organisation des Laborpraktikums:
Für das Programmieren der Mikrocontroller-Boards (STM32 PerformanceStick) wird die Entwicklungsumgebung „HiTOP“ der Firma HiTex benötigt. Diese ist auf einem virtuellen Desktop-System
der THM vorinstalliert und steht darüber hinaus als kostenloses Download in Moodle zur Verfügung. Für das Laborpraktikum wird ein modifiziertes Project-Template verwendet, das auf dem virtuellen Desktop vorinstalliert und ebenfalls als Download in Moodle hinterlegt ist.
Virtueller Desktop Client
Auf den PCs in den Rechnerräumen ist die Software „VMware Horizon Client“
(kurz: „View-Client“) installiert, die den Desktop eines virtuellen PCs mit Windows 7 (32 Bit) und vorinstallierter HiTOP Entwicklungsumgebung (Version 5.3)
zur Verfügung stellt. Starten Sie den virtuellen Desktop über das Desktop-Icon!
Sie können den View-Client auch kostenlos auf Ihrem eigenen PC oder
MacBook installieren. Geben Sie dazu in Ihrem Webbrowser die URL https://view-ext.thm.de/ ein.
Nach dem Start des View-Clients klicken Sie auf den Server „view-int.thm.de“
(bzw. „view-ext.thm.de“ wenn Sie Ihren PC außerhalb des THM-Netzwerkes
verwenden.)!
Nach erfolgreichem Verbindungsaufbau erscheint das Anmeldefenster, in dem
Sie sich mit Ihren THM-Zugangsdaten anmelden müssen. Achten Sie darauf,
dass als Domäne „THM“ eingestellt ist!
Wählen Sie anschließend den virtuellen Desktop „Embedded Systems“ aus:
Je nach Konfiguration des Desktop-Clients haben Sie anschließend noch die
Möglichkeit, einen Zugriff von dem virtuellen Desktop auf die lokalen Dateien
zuzulassen.
Hardware mit dem virtuellen Desktop verbinden
Bevor Sie die HiTOP Entwicklungsumgebung auf dem virtuellen Desktop starten, muss zunächst
der STM32 PerformanceStick in einen freien USB-Port des lokalen PCs eingesteckt und mit dem
virtuellen Desktop verbunden werden. Bewegen Sie dazu den Mauszeiger an den oberen Rand
des Bildschirms, um das Menü des Desktop-Clients einzublenden. Dort wählen Sie dann „USBGerät verbinden“ und in dem Pull-down-Menü das Gerät „Hitex...STM32-PerformanceStick“ aus:
Achtung: Wenn Sie die virtuelle Maschine am Ende der Übung herunterfahren, wird diese wieder
in den ursprünglichen Zustand versetzt, d. h. alle Dateien, die Sie neu erstellt haben, werden gelöscht! Sichern Sie Ihre Dateien daher auf einem eigenen USB-Stick oder über Moodle!
HiTop Entwicklungsumgebung auf einem eigenen PC installieren
Um auch „offline“ arbeiten zu können, besteht alternativ die Möglichkeit, die Entwicklungsumgebung HiTop (Version 5.4) lokal auf Ihrem eigenen PC zu installieren. Die Setup-Datei ist in dem
Moodle-Kurs verfügbar, allerdings musste sie aufgrund der Größe als Multi-Volume-Zip-File auf
zwei Dateien aufgeteilt werden. Zum Entpacken des Multi-Volume-Archivs können Sie beispielsweise das kostenlose Programm „7Zip“ verwenden.
Zusätzlich zu der HiTop Entwicklungsumgebung benötigen Sie noch das angepasste Projekt-Template, das Sie ebenfalls in Moodle finden.
Inhaltsverzeichnis
1 Entwicklungssystem..................................................................................................................... 1
1.1 Hardware.............................................................................................................................. 1
1.2 Software................................................................................................................................ 3
2 Motivation..................................................................................................................................... 4
3 Nutzung der STM32-Peripherie....................................................................................................5
3.1 Einführung............................................................................................................................ 5
3.1.1 Interne Busse und Peripherie........................................................................................5
3.1.2 Manuelle Konfiguration..................................................................................................7
3.2 Laboraufgabe 1: LED manuell einschalten...........................................................................8
3.2.1 Manuelles Einschalten der LED mit Hilfe der Special Function Register.......................8
4 Programmierung des STM32......................................................................................................10
4.1 Programmiervorbereitung...................................................................................................10
4.1.1 Grundeinstellungen......................................................................................................11
4.1.2 Projekt erstellen...........................................................................................................11
4.1.3 Quellcode bearbeiten, kompilieren und testen.............................................................11
4.1.4 Debugging-Grundlagen...............................................................................................12
4.1.5 Design-Diagramme erstellen.......................................................................................14
4.2 Laboraufgabe 2: Addition (Debug-Übung)...........................................................................15
4.2.1 Aufgabenstellung.........................................................................................................15
4.2.2 Erläuterungen.............................................................................................................. 16
4.3 Laboraufgabe 3: Einschalten der LED mit einem C-Programm...........................................17
4.3.1 Vorgehensweise.......................................................................................................... 17
4.3.2 Optionale Erweiterung.................................................................................................17
5 Nutzung der STM32-Funktionsbibliothek....................................................................................18
5.1 Konfiguration durch die Firmware-Bibliothek.......................................................................18
5.2 Laboraufgabe 4a: Blinklicht.................................................................................................20
5.2.1 Vorgehensweise.......................................................................................................... 20
5.2.2 Erläuterungen.............................................................................................................. 20
5.3 Timer................................................................................................................................... 20
5.4 Laboraufgabe 4b: Blinklicht mit Timer.................................................................................21
5.4.1 Vorgehensweise.......................................................................................................... 21
6 Interrupt-Handler........................................................................................................................ 22
6.1.1 Funktionsweise eines Timer-Interrupts........................................................................22
6.2 Laboraufgabe 5: Lauflicht mit Timer-ISR.............................................................................23
6.2.1 Aufgabenstellung.........................................................................................................23
6.2.2 Hinweise...................................................................................................................... 23
6.2.3 Vorgehensweise.......................................................................................................... 23
7 Tasten......................................................................................................................................... 24
7.1 Laboraufgabe 6: Entprellung...............................................................................................25
7.1.1 Aufgabenstellung.........................................................................................................26
7.1.2 Vorgehensweise.......................................................................................................... 26
7.1.3 Schnittstelle von pushbuttons.h...................................................................................26
8 Assembler.................................................................................................................................. 27
8.1 Laboraufgabe 7a: ggT.........................................................................................................27
8.1.1 Vorgehensweise.......................................................................................................... 27
8.2 Laboraufgabe 7b: Bubblesort..............................................................................................27
8.2.1 Vorgehensweise.......................................................................................................... 28
9 Pulsweitenmodulation................................................................................................................. 29
9.1 Laboraufgabe 8: LED-Helligkeit mit PWM...........................................................................29
9.1.1 Augabenstellung.......................................................................................................... 29
9.1.2 Vorgehensweise.......................................................................................................... 29
10 LCD-Einführung........................................................................................................................ 30
10.1 SPI.................................................................................................................................... 30
10.2 Laboraufgabe 9a: SPI-Bus-Modul.....................................................................................32
10.2.1 Aufgabenstellung.......................................................................................................32
10.2.2 Schnittstelle von spi.h................................................................................................32
10.3 LCD-Ansteuerung............................................................................................................. 33
10.4 Laboraufgabe 9b: LCD-Ansteuerung................................................................................35
10.4.1 Aufgabenstellung.......................................................................................................35
10.4.2 Schnittstelle von lcd.h................................................................................................35
10.4.3 Schnittstelle von lcd_drawing.h.................................................................................36
10.4.4 Wichtige Befehle des LCD-Controllers......................................................................37
10.4.5 Schriftarten................................................................................................................ 38
1 - Entwicklungssystem
1
1 Entwicklungssystem
Ein Entwicklungssystem, das der Anwendungsprogrammerstellung für einen Mikrocontroller dient,
besteht aus spezifischen Hardware- und Software-Komponenten:
Als Hardware stehen für die einzelnen Mikrocontroller-Familien sowohl die meist teureren herstellerspezifischen Entwicklungsplatinen als auch einfachere, preiswertere Entwicklungsplatinen von
Drittanbietern zur Verfügung.
Als Software kommen diverse Editoren, hardwarespezifische Systembibliotheken sowie Compiler,
Linker und Debugger zum Einsatz.
1.1 Hardware
Die im Laborpraktikum eingesetzte Entwicklungshardware besteht aus einem industriell gefertigten
Development Kit und einer Anwendungsplatine, die speziell für das Embedded Systems-Labor an
der FH Gießen-Friedberg entwickelt worden ist. Das Development Kit hat einen STM32F103RBT6
der Firma ST Microelectronics als Kern, der auf dem ARM Cortex M3 basiert. Das Kit wurde als
USB-Dongle konzipiert und kann mit einer ansteckbaren Anwendungsplatine erweitert werden.
1
2
3
4
5
6A
6B
6C
6D
Steckverbinder für IO-Board
Grüne LED / Fototransistor
Rote LED
Grüne LED für USB-Status
USB-Steckverbinder
Pin PC0 (für Voltmeter, Zähler, Frequenzmesser)
Analog Ground (GNDA)
Pin PC13 (bspw. als Trigger für 6A)
Ground (GND)
1
2
3
4
Steckverbinder für PerformanceStick
Taster
LEDs, grün
Grafik-LCD mit Hintergrundbeleuchtung,
128x64 Pixel
Beschleunigungssensor (unter dem LCD)
A/D-Wandler-Erweiterung
SPI-Bus-Erweiterung
Abbildung 1.1: STM32-PerformanceStick
5
6
7
Abbildung 1.2: Anwendungsplatine
Die Erweiterungsplatine stellt einige Peripheriekomponenten zur Verfügung, die wir in diesem Labor nutzen werden. Sie muss an den PerformanceStick angesteckt werden, bevor dieser mit dem
PC verbunden wird.
Üblicherweise findet man auf Entwicklungsplatinen eine erheblich höhere Anzahl von ICs, die Peripherie darstellen oder den Anschluß von eben solcher ermöglichen. Da Cortex M3-basierte Chipdesigns jedoch bereits viele Schnittstellen integrieren und folglich wesentlich mehr als nur die CPU
beinhalten, werden derartige Mikrocontroller auch als System-on-Chip (SoC) bezeichnet:
1.1 - Hardware
2
Abbildung 1.3: Blockdiagramm des STM32
Wie im Diagramm ersichtlich, arbeitet die CPU mit einem maximalen Takt von 72MHz, den wir auf
dem PerformanceStick auch verwenden. Die dort verbaute SoC-Variante STM32F103RBT6 besitzt
20KB SRAM, 128KB internen Flash-Speicher und besitzt außer dem FSMC-Interface, dem SDIOInterface, dem I²S-Bus, der ETC und den DACs alle der dargestellten Komponenten.
Der PerformanceStick alleine beinhaltet also bereits alle nötigen Funktionen, die von einer modernen Entwicklungshardware erwartet werden: On-Chip Debugger, JTAG-Interface zum Übertragen
des Programmes auf den Chip, USB-Interface-Chip für besagtes JTAG-Interface sowie einige
LEDs als Beispielperipherie. Weitergehende Hardware kann üblicherweise direkt ange-schlossen
werden, was besonders für platzsparende Designs von Bedeutung ist.
Die CPU selbst ist eine 32-Bit-CPU, die mit ARMs Thumb-2-Befehlssatz arbeitet. Sie besitzt 16
Register, von denen 13 frei verwendet werden können, und einen Adressraum von 4GB. Da so viel
Speicher nicht vorhanden ist, werden bestimmte Bereiche dieses Adressraums für die Imple-mentierung diverser Funktionen (bspw. Bit Banding) und über Memory Mapped I/O für die An-steuerung der Peripherie genutzt.
Bit Banding beschreibt dabei ein Verfahren, bei dem jedem Bit eines wählbaren Speicherbereiches
eine eigene Speicheradresse zugewiesen wird. Auf diese Weise kann man effizient einzelne Bits
eines Wertes setzen und löschen, was durch den Thumb-2-Befehlssatz weniger elegant möglich
ist. Besonders attraktiv ist das Verfahren, wenn man berücksichtigt, dass die gesamte Peripherie
1.1 - Hardware
3
durch Memory Mapped I/O im Speicher abgebildet wird und man sehr oft nur einzelne Bits setzen
oder löschen möchte.
Zum Debuggen von Programmen ist es äußerst hilfreich, den Thumb-2-Befehlssatz zu kennen. Da
dieser sehr umfangreich ist, sei auf die offizielle ARM-Dokumentation verwiesen, die unter [Thumb2] zu finden ist.
Desweiteren sind unter [STM32DS] die Datenblätter von ST zu finden, die den Funktionsumfang
sowie die mechanischen und elektrischen Eigenschaften des verwendeten Chips ausführlich dokumentieren.
1.2 Software
Als Entwicklungsumgebung wird die zum STM32 PerformanceStick gehörende Anwendung HiTOP
Verwendung. Die IDE selbst besitzt keinen Compiler, weshalb ihr die GNU Compiler Collection
(GCC) zur Seite gestellt wird. Auf den virtuellen Desktops (s. o.) ist die Version 5.3 (mit Anpassungen für diese Lehrveranstaltung) installiert. Darüber hinaus steht im Downloadbereich der Lehrveranstaltung die Version 5.40 zur Verfügung, die auch auf 64 Bit-Windows lauffähig ist.
Die IDE enthält alles, was für das Erstellen und Debuggen von Programmen auf dem STM32 notwendig ist:
Ein Assembler, der aus hardwarespezifischem Assemblercode linkbare Objekte generiert.
• Ein Cross-Compiler für C/C++, der aus Quellcode ebenso linkbare Objekte generiert.
• Ein Linker, der nach entsprechenden Vorgaben mittels der linkbaren Objekte und einer hardwarespezifischen Bibliothek eine Binärdatei erzeugt, die der Zielprozessor ausführen kann.
• Ein Debugger, der mittels der vom Compiler/Linker erzeugten Debug-Informationen den Programmverlauf auf dem Prozessor mit Verweisen auf den C/C++-Quellcode nachvollziehbar
macht. Darüber hinaus erlaubt er einen direkten Eingriff in den Programmverlauf sowie die Inspektion/Überwachung von Speicher-, Register- und Variableninhalten.
•
Zum besseren Verständnis der Zusammenhänge folgt nun eine Übersicht über die in der Entwicklungsumgebung verwendeten Dateiarten, deren Beziehungen untereinander und die verwendeten
Werkzeuge:
C-Quelldatei
.c
Header-Datei
↓
.h
↓
Relocatable Object
Cross-Assembler (as)
↓
.o
.s
↓
C-Cross-Compiler (gcc)
↓
Assemblercode
Quellverweis
↓
.d
↓
Relocatable Object
.o
↓
↓
Linker-Skript
↓
.ld
↓
↓
↓
Linker (ld)
↓
Ausführbare Datei
↓
↓
.elf
Quellverweis
.d
↓
Debugger (in HiTOP integriert)
Debug-Symboltabelle
↓
.map
2 - Motivation
4
2 Motivation
Das Ziel dieses Labors ist es, die Besonderheiten und Anforderungen von eingebetteten Systemen
zu vermitteln. Um auch Studenten anzusprechen, die eher wenig Interesse an dieser Thematik haben, werden die gestellten Einzelaufgaben in den Rahmen einer konkreten Anwendung gestellt.
Jede dieser Aufgaben stellt somit einen notwendigen Baustein auf dem Weg zum Endprodukt dar.
Das Ergebnis der letzten Aufgabe des Labors wird somit eine Uhr sein, die sich sehen lassen
kann. Die Hardware, auf der sie basiert, besteht aus den folgenden Komponenten:
Grafikdisplay mit 128x64 Pixeln, monochrom
Hintergrundbeleuchtung für das Display
• 2 Taster
• 3D-Beschleunigungssensor
• 8 LEDs, grün
•
•
Die Liste mag zwar kurz erscheinen, diese Komponenten bieten jedoch zusammen mit den Fähigkeiten des STM32 derart viele Möglichkeiten, dass man den Umfang des Labors leicht verdoppeln
könnte. Eine Uhr passt allerdings gut in den Zeitrahmen von einem Semester und erlaubt es, die
wichtigsten Komponenten unseres eingebetten Systems kennen zu lernen:
Abbildung 2.1: Im Labor verwendete Hardware-Komponenten
Die folgenden Abschnitte beschäftigen sich jeweils mit bestimmten Teilen des Systems. Zu einigen
der Komponenten werden im Rahmen der Laboraufgaben Module entwickelt, die in späteren Abschnitten Verwendung finden. Die Aufgaben bauen somit aufeinander auf und gegen Ende des Labors werden alle Komponenten integriert und die Uhr fertiggestellt.
3 - Nutzung der STM32-Peripherie
5
3 Nutzung der STM32-Peripherie
3.1 Einführung
Vor noch nicht allzu langer Zeit wurden Mikroprozessoren ausschließlich mittels Assembler programmiert. Zu dieser Zeit war der Funktionsumfang eines solchen Prozessors nicht so umfangreich wie heute. Peripherie wurde also oft als externes Bauteil über einen allgemein verwendbaren
Bus angebunden. Durch diese allgemeine Verwendbarkeit der Mikroprozessoren war zwar einerseits eine hohe Flexibilität möglich, andererseits verstand der Prozessor aber nicht die Protokolle,
die notwendig sind, um mit der Peripherie zu kommunizieren.
Diese Situation machte es oft unumgänglich, sogenanntes „bit banging“ zu betreiben, also das verlangte Protokoll softwaretechnisch zu emulieren, indem mit Hilfe von Timern und Warteschleifen zu
möglichst gut definierten Zeitpunkten bestimmte Werte auf den Peripheriebus geschrieben wurden.
Man kann sich gut vorstellen, dass diese Vorgehensweise nicht nur komplex, sondern in gewissem
Sinne auch gefährlich ist, da beispielsweise durch Interrupts die durch das Protokoll vorgeschriebenen Zeiten überschritten werden können.
Mit dem Fortschreiten der Entwicklung auf Hardwareebene wurde es möglich, mehr und mehr
Komponenten auf dem Chip zu vereinen. Die Anzahl der Komponenten wurde mit der Zeit so hoch,
dass man für diese Architekturen mittlerweile den Begriff „System on Chip“ (SoC) bevorzugt, um
die Unterscheidung zu reinen Mikroprozessoren zu ermöglichen.
Viele SoCs bieten nun also Komponenten, die der Kommunikation mit Peripherie dienen. Sie implementieren die benötigten Protokolle in Hardware, was zum einen die Ansteuerung einfacher und
zum anderen das bit banging überflüssig macht.
Gleichzeitig vollzog sich auf Softwareseite ein ähnlicher Paradigmenwechsel, indem Assembler
abgelöst und durch Hochsprachen ersetzt wurde. Für zeitkritische oder sehr spezifische Algorithmen findet Assembler zwar auch heute noch Verwendung, derzeit trifft man allerdings hauptsächlich die Sprachen C oder C++ an.
Wie bereits im vorigen Abschnitt gesehen, werden wir den STM32 in diesem Labor ebenfalls in C
programmieren, was uns durch den Hersteller des SoC besonders einfach gemacht wird. Dazu jedoch später mehr.
3.1.1 Interne Busse und Peripherie
Die Komponenten, die sich auf einem SoC befinden, müssen in der Lage sein, Daten auszutauschen. Im Falle des STM32 geschieht dies über die folgenden Busse:
Abbildung 3.1: Die Busse im STM32
3.1 - Einführung
6
Dabei bedeutet...
und ermöglicht...
I-Bus
Instruction Bus
...Lesezugriff auf das im Prozessor ablaufende
Programm
D-Bus
Data Bus
...Lese-/Schreibzugriff auf RAM und Peripherie
System
System Bus
...Kontrolle über Systeminterne Komponenten
GP-DMA
General Purpose DMA
...schnelle Datentransfers zwischen RAM und
Peripherie, bei denen die CPU selbst nicht aktiv
werden muss und somit entlastet wird
AHB
Advanced High Speed Bus
...die Schnelle Anbindung von Peripherieeinheiten an die Bus-Matrix
APB1
Advanced Peripheral Bus 1
...Zugriff auf die etwas langsameren Teile der
Peripherie
APB2
Advanced Peripheral Bus 2
...Zugriff auf die schnellen Teile der Peripherie
Desweiteren gibt es noch den Arbiter und die Bridges, deren Aufgaben und Funktionsweisen jedoch sehr spezifisch sind und den Rahmen dieses Abschnittes sprengen würden.
Möchte man beispielsweise Daten von der Cortex-M3-CPU an den Controller USART1 schicken,
so müssen diese Daten über den D-Bus, die Bus-Matrix, den AHB und APB2 geschickt werden,
bevor sie das Zeil erreichen. Dabei kommt bei eingebetteten Systemen auch die Notwendigkeit
des Energiesparens ins Spiel.
Der STM32 besitzt daher vielfältige Möglichkeiten, den Stromverbrauch zu senken. Eine dieser
Möglichkeiten besteht darin, ungenutzte Komponenten effektiv in einen Ruhezustand zu versetzen,
indem diese vom Systemtakt getrennt werden. Um standardmäßig so energiesparend wie möglich
zu sein, sind nach einem Reset des STM32 alle nicht unbedingt nötigen Zusatzkomponenten ohne
Takt. Es ist also wichtig zu wissen, dass die Peripherie und diverse interne Komponenten nur
dann ansprechbar sind, wenn sie auch mit dem Systemtakt versorgt werden.
Bevor wir im folgenden Abschnitt lernen, wie wir die auf dem STM32 vorhandene Peripherie mittels
HiTOP direkt konfigurieren können, erfolgt eine Übersicht über die am weitest verbreiteten Peripherieeinheiten und deren Bezeichnungen im STM32:
Üblicher Name
Bezeichnung STM32
Zweck
Port
Port A..E
Gruppierung von I/O-Leitungen
GPIO (General Purpose I/O)
Px0..15 (x = Port A..E)
Frei verfügbare I/O-Leitung
ADC (A/D Converter)
ADC
Messwertaufnahme,
Digitalisierung
DAC (D/A Converter)
Watchdog
Signalerzeugung
IWDG / WWDG
Zustandsüberwachung,
Schutz bei Fehlfunktion
3.1 - Einführung
7
Timer
TIM1..4
Verzögerung, Taktung, PWM
RTC (Real Time Clock)
RTC
Bereitstellung einer Uhr
SPI (Serial Peripheral Interface)
SPI1..2
Kommunikation mit externer
Peripherie über den SPI-Bus
I²C (Inter-Integrated Cicruit)
I2C1..2
Kommunikation mit externer
Peripherie über den I²C-Bus
I²S (Integrated Interchip Sound)
Übermittlung von Sound-Samples
USART (Universal sync / async USART1..2
receiver transmitter)
Konvertierung serieller Daten zu
parallelen Daten und umgekehrt
USB (Universal Serial Bus)
USB
Breitbandige Kommunikation mit
externen Komponenten
CAN (Controller area Network)
CAN
Kommunikation mit externen
Komponenten, insbesondere in
Fahrzeugen
JTAG (Joint Test Action Group)
JTAG
Schnittstelle zur Programmierung
und Fehlerbehebung
DMA (Direct Memory Access)
DMA
Durchführung von Datentransfers
innerhalb des SoC ohne
Mitwirkung der CPU
SDIO (Secure Digital I/O)
Anschluss von Komponenten mit
hohem Datendurchsatz, z.B.
WLAN, Bluetooth oder SD-Karten
NOR / NAND
Schnittstelle für externen NORoder NAND-Speicher
Flash
Flash
Zugriff auf internen oder externen
Flash-Speicher
Es gibt noch weitere Peripherieeinheiten, diese sind aber weniger stark verbreitet und somit auf
spezielle Anforderungen zugeschnitten. Im Verlauf dieses Labors werden Sie von den oben gelisteten Peripherieeinheiten mit den GPIOs, den Timern, einer der beiden SPI-Schnittstellen und
eventuell der RTC arbeiten. Sollten Sie aus Interesse weitere Komponenten in Ihre Lösungen einbauen, kann diese freiwillige Erweiterung zu Bonuspunkten führen (vgl. Abschnitt Fehler: Referenz
nicht gefunden).
3.1.2 Manuelle Konfiguration
In der folgenden Aufgabe soll die rote LED (Nummer 3 in Abbildung 1.1) von Ihnen zum Leuchten
gebracht werden. Die LED ist mit einem digitalen Ein-/Ausgang (GPIO) des STM32 Mikrocontrollers verbunden.
Man könnte nun ein Programm schreiben, das die LED ansteuert. Im Folgenden wird jedoch eine
einfachere Möglichkeit beschrieben.
3.1 - Einführung
8
In HiTOP gibt es ein Fenster, das Sie eventuell bereits entdeckt haben. Es trägt den wenig aussagekräftigen Titel „SFR Window“, der für „Special Function Register Window“ steht. Das Fenster ermöglicht die komfortable Beobachtung und Manipulation derjenigen Speicherinhalte, die die Peripherie kontrollieren. Wählen Sie den Menüpunkt „View → SFR window“, um es zu öffnen. Auf der
linken Seite des Fensters ist eine Baumstruktur zu sehen, die die einzelnen Komponenten auflistet.
Rechts davon kann man die in der gewählten Komponente verfügbaren Parameter auswerten und
modifizieren. Es ist dabei egal, ob der STM32 mitten in der Programmausführung ist oder gerade
über ein Reset zurückgesetzt worden ist - alle Einstellungen, die im SFR-Fenster vorgenommen
werden, treten sofort in Kraft.
3.2 Laboraufgabe 1: LED manuell einschalten
Bevor Sie die rote LED auf dem STM32 PerformanceStick einschalten können, müssen Sie herausfinden, an welchem GPIO-Port und welchem Pin dieses Ports die LED angeschlossen ist. Außerdem benötigt man die Information, ob Anode oder Kathode der LED mit dem Port-Pin verbunden ist. Diese Informationen können Sie dem Schaltbild des STM32 PerformanceStick entnehmen:
Auf Seite 3 des Schaltbilds finden Sie die rote
LED (Abkürzung „rt“ für „rot“). Sie sehen dort,
dass die LED mit der Anode an PB5 angeschlossen ist. „PB5“ steht hier für Port B (also GPIO B),
Pin 5 (Bit 5). Dadurch, dass diese I/O-Leitung mit
der Anode der LED verbunden ist und die Kathode der LED auf „GND“ (0V) liegt, ergibt sich,
dass die LED über eine positive Spannung (logisch „1“) an der I/O-Leitung eingeschaltet wird.
Schaltbild-Auszug vom
STM32 Performance Stick
(Seite 3)
3.2.1 Manuelles Einschalten der LED mit Hilfe der Special Function Register
Wählen Sie GPIOB im Baum. Sie werden feststellen, dass die meisten GPIOs des Ports als Typ
„Floating Input / General purpose Open-drain“ im Modus „Input mode“ konfiguriert sind:
Dies bedeutet , dass die GPIOs als unbeschaltete Eingänge konfiguriert sind und keine Funktion
besitzen. Somit können eventuell angeschlossene externe Komponenten auf keinen Fall Schaden
nehmen.
Die Konfiguration sorgt gleichzeitig aber auch dafür, dass unsere LED nicht mit Strom versorgt
werden kann. Um dies zu ändern, setzen Sie den Typ von Leitung 5 auf „Analog input mode /
General purpose output push-pull“.
3.2 - Laboraufgabe 1: LED manuell einschalten
9
Sie werden feststellen, dass ihre Wahl zunächst ignoriert wird. Dies liegt daran, dass GPIOB eine
Peripherieeinheit ist, die explizit mit dem Systemtakt versorgt werden muss, damit sie arbeiten
kann. Um dies zu erledigen, müssen wir uns kurz um die zentrale Steuerung für System- und Bustakte kümmern. Sie befindet sich in der Komponente „Reset and Clock Control“, kurz RCC.
Setzen Sie dort den Parameter „I/O port B clock“ auf „Enabled“, wodurch Port B Systemtakt bekommt:
Gehen Sie nun zurück zu den Parametern von Port B und versuchen Sie erneut, Leitung 5 auf
„Analog input mode / General purpose output push-pull“ zu setzen. Wie erwartet gelingt es
jetzt, weshalb wir nun auch den Modus auf „Output mode“ setzen können - die Frequenz ist erst
einmal egal. Ausgang B5 ist nun konfiguriert und einsatzbereit.
Scrollt man nach unten, schreibt eine 1 in das Feld neben „Port output data 5“ und drückt Enter,
so leuchtet die LED. Wird eine 0 eingetragen, verlischt sie wieder.
Auf diese Weise kann man sämtliche Parameter der gesamten STM32-Hardware einstellen, während der Fehlersuche inspizieren oder im Betrieb eventuell auftretende Fehlersituationen simulieren. Es ist allerdings sinnvoll, diese Konfigurationen im Programm selbst vorzunehmen, so dass
sie dauerhaft gespeichert und automatisch nach dem Systemstart wiederhergestellt werden.
4 - Programmierung des STM32
10
4 Programmierung des STM32
4.1 Programmiervorbereitung
Obwohl die von Ihnen im Laborpraktikum zu erstellenden Programme relativ kurz sein werden,
sollten bei der Programmerstellung immer folgende Punkte beachtet werden:
a) Erstellen Sie für jede Aufgabe ein eigenes Projekt.
b) Machen Sie sich gut mit der Aufgabenstellung vertraut, damit Sie genau wissen, was durch Ihr
Programm erreicht werden soll. Verschaffen Sie sich ausreichende Informationen über technische Daten, Verfahren, Programmierbibliotheken, etc.
c) Die Aufgabe, die das Programm lösen soll, ist so in Teilaufgaben zu splitten, dass diese Teilaufgaben unabhängig voneinander erstellt werden können. Dies erfordert eine saubere Schnittstellenklärung.
d) Bereiten Sie Ihr Programm durch die Erstellung von Struktogrammen (gemäß DIN 66261; auch
„Nassi-Shneiderman-Diagramme“ genannt), von Programmablaufplänen oder von geeigneten
UML-Diagrammen vor.
e) Das Programm ist zu modularisieren, sodass nach schrittweiser Verfeinerung der Problemstellung überschaubare, kurze Programmteile (z.B. C-Funktionen) entstehen. Programm-Module
sollen nur einen Eingang und einen Ausgang haben. Jedes Modul soll eine abgeschlossene
Funktionseinheit bilden, d.h. ein anderes Modul ist entweder vollständig darin gekapselt (geschachteltes Programm) oder es ist davon komplett unabhängig.
f) Änderungen, Anpassungen und Erweiterungen müssen jederzeit schnell und sicher ohne Beeinflussung anderer Programmteile durchgeführt werden können. Module sollen einzeln testbar
sein.
g) Aus der Gliederung des Programms muss dessen dynamischer Ablauf klar erkennbar sein, sodass auch eine andere Person, die nicht an der Programmentwicklung beteiligt war, den Programmablauf ohne großen Aufwand nachvollziehen kann.
h) Für eine ausreichende und aktuelle Dokumentation durch ausführliche Kommentierung des Programms ist stets zu sorgen, damit ein lesbares und wartungsfreundliches Programm entsteht.
Dies beinhaltet die Erstellung von Dateiköpfen, die den Namen des Programms, den Inhalt der
Datei (z.B. Kurzbeschreibung eines Moduls), die Autoren und das Erstellungsdatum beinhalten.
Ein solcher Dateikopf könnte etwa wie folgt aussehen:
1 /********************************************************************
2 * Project:
Laboraufgabe 1
3 * File:
main.c
4 *
5 * System:
STM32-PerformanceStick (Cortex M3 )
6 * Compiler:
GNU GCC 4.3.0
7 *
8 * Date:
2008-10-06
9 * Authors:
Olli Dittrich
10 *
Ester Schweins
11 *
Mirko Nontschew
12 *
13 * Rights:
All rights reserved
14 * License:
n/a
15 ********************************************************************
16 * Description:
17 * Contains the main program and implements the core functionality
18 ********************************************************************/
Listing 1: Beispiel-Dateikopf
4.1 - Programmiervorbereitung
11
Der Dateikopf kann natürlich wahlweise auch in Deutsch verfasst werden. In größeren Unternehmen wird jedoch üblicherweise die englische Sprache vorgezogen.
4.1.1 Grundeinstellungen
Nach dem Hochfahren des Hostrechners (Benutzername „Student“, Passwort „student“) wird durch
Anklicken des Icons „HiTOP52“ die Entwicklungsumgebung gestartet. Stecken Sie nun den
STM32-PerformanceStick in eine freie USB-Buchse.
Da HiTOP zur Arbeit an einem Projekt mit dem STM32-PerformanceStick in Verbindung stehen
muss und Sie diese Voraussetzung gerade erfüllt haben, kann nun ein Projekt erzeugt oder geladen werden.
4.1.2 Projekt erstellen
Um ein neues Projekt zu erstellen, gehen Sie bitte wie folgt vor:
1) Wählen Sie den Menüpunkt „Project → New“ und wählen Sie den GNU C Compiler.
2) Klicken Sie auf „Next“ und wählen Sie den PerformanceStick aus der Liste.
3) Nach einem erneuten Klick auf „Next“ können Sie den Namen Ihres Projekts eingeben. Stellen
Sie sicher, dass für „Project location“ das Verzeichnis C:\STM32 eingetragen ist, sofern Sie die
Projekte nicht auf einem mitgebrachten USB-Stick speichern möchten. Wird dies gewünscht,
müssen Sie den Pfad natürlich entsprechend anpassen.
4) Wählen Sie im nächsten Schirm den STM32-PerformanceStick als Zielplattform.
5) Nach Bestätigung der Wahl mit „Next“ müssen Sie evtl. die Seriennummer ihres PerformanceSticks eingeben. Diese finden Sie auf der Unterseite des Sticks.
6) Klicken Sie dann auf „Connect“, so wird HiTOP die Verbindung zum PerformanceStick aufbauen, das Projekt erzeugen, auf den STM32 flashen, das Programm bis zum Eintritt in die
Reset()-Funktion ausführen und es dann stoppen.
4.1.3 Quellcode bearbeiten, kompilieren und testen
Die HiTOP-Entwicklungsumgebung kennt zwei Betriebsmodi, zwischen denen unterschieden werden muss. Zum einen gibt es den Debugmodus, in dem sich das Programm standardmäßig befindet. Ist er aktiv, lässt sich das kompilierte Programm ausführen und der Programmablauf im
STM32 verfolgen. Man kann den Quelltext jedoch nicht verändern. Hat man die Arbeit im Debugger abgeschlossen und möchte am Programm weiterarbeiten, muss man in den Editiermodus
wechseln. Dies geschieht, indem man im Quelltextfenster rechts klickt und die Option „Switch to
Edit Mode“ wählt oder die Tastenkombination STRG-T drückt.
Auf die selbe Art und Weise kann man auch wieder in den Debugmodus wechseln. Üblicherweise
wird man jedoch nach Änderung des Programms dessen Funktion testen wollen, wozu das Programm kompiliert und geflasht werden muss. Durch das Flashen schaltet HiTOP allerdings automatisch in den Debugmodus, was die Benutzung des Editors ein wenig angenehmer gestaltet.
Hinweis: Wenn Sie manuell vom Editor-Modus in den Debugmodus umschalten, dann sollten Sie
sich darüber im Klaren sein, dass das sich im STM32 befindliche Programm nicht automatisch geändert worden ist und Ihre Änderungen am Quellcode nicht nur unberücksichtigt sind, sondern sogar zu ungewollten Fehlinterpretationen des Debuggers führen können.
Haben Sie an Ihrem Programm Änderungen vorgenommen, die Sie testen möchten, so müssen
Sie das Projekt kompilieren, linken und flashen. Dies geschieht durch den Menüpunkt „Project →
Build“. Sie können alternativ dazu aber auch einfach F7 drücken.
4.1 - Programmiervorbereitung
12
Schlägt die Kompilierung einmal fehl, können Sie über
das Fenster „Output →
Build“ nachvollziehen, wo
Fehler aufgetreten sind. Über
einen Doppelklick auf die
Fehlermeldung gelangen Sie
zur entsprechenden Zeile im
Quelltext.
Gelingt die Kompilierung und
wurde das Programm im
STM32-Flash somit auf den
neuesten Stand gebracht, Abbildung 4.1: Das Build Log in HiTOP
können Sie es ausführen. Im
Regelfall werden jedoch die Register noch alte Werte haben, weshalb Sie den Menüpunkt „Debug
→ Reset&Go“ verwenden sollten. Er sorgt dafür, dass der STM32 zurückgesetzt und danach das
Programm im Flash neu gestartet wird. Analog zum Kompiliervorgang kann auch hier ein entsprechendes Tastaturkürzels (STRG-R) verwendet werden.
4.1.4 Debugging-Grundlagen
Abbildung 4.2: HiTOP im Debugmodus
4.1 - Programmiervorbereitung
13
Läuft ein Programm auf dem STM32, können Sie es mit dem in HiTOP integrierten Debugger beobachten und analysieren. Sehr wertvoll dabei ist die Möglichkeit des Setzens von Breakpoints bei
unterbrochener Programmausführung, wobei zwischen permanenten und temporären Breakpoints
unterschieden wird:
Temporäre Breakpoints sorgen dafür, dass die CPU das Programm sofort weiter ausführt und
erst dann unterbricht, wenn die Stelle erreicht worden ist, die der Breakpoint markiert. Danach
wird der Breakpoint automatisch gelöscht.
• Permanente Breakpoints werden erst dann aktiv, wenn der Benutzer dem Debugger sagt, dass
das Program weiter ausgeführt werden soll. Dann jedoch unterbrechen sie das Programm immer dann, wenn die betroffene Programmzeile als nächstes ausgeführt werden würde.
•
Das Setzen von Breakpoints kann bspw. mit einem Rechtsklick im Quelltext-Editor über die Menüpunkte „Run to Cursor Line“ und „Set Breakpoint“ erfolgen. Vorzugsweise wird der Pro-grammfluß
jedoch über ein etwas intuitiveres Interface beeinflusst: die graue Spalte auf der linken Seite des
Quelltext-Editors.
Einen temporären Breakpoint
setzen Sie, indem Sie auf den
blauen Balken derjenigen Zeile
Ihres Quelltextes klicken, bis zu
welcher Anweisung das Programm ausgeführt werden soll.
Daraufhin wird diese Zeile grün
hinterlegt, was auf einen Breakpoint hinweist. Auch steht nun
der gelbe Pfeil auf der Zeile - er
zeigt an, welche Anweisung als
nächstes ausgeführt wird.
Permanente Breakpoints halten
das Programm immer dann an,
bevor die Anweisung ausgeführt
wird, auf der ein Breakpoint gesetzt worden ist.
Sie werden durch einen Klick
links neben den blauen Balken
erzeugt und hinterlegen die Zeile
im Editor rot.
Hinweis: Beachten Sie, dass das Setzen eines temporären Breakpoints die sofortige Ausführung
des Programms zur Folge hat.
Neben den Breakpoints stehen auch die nachfolgend beschriebenen Debug-Funktionen zur Verfügung. Ob Sie die Befehle über die Icons, mit Hilfe der Tastaturkürzel oder über das Pull-Down-Menü ausführen, bleibt der Gewohnheit überlassen.
4.1 - Programmiervorbereitung
Icon
14
Tastaturkürzel
Menüeintrag
Bedeutung
F5
Go
Startet das Programm oder setzt die Ausführung fort.
Wird ein Breakpoint angetroffen, hält das Programm an.
STRG-F5
Go (disable)
Startet das Programm oder setzt die Ausführung fort.
Alle gesetzten Breakpoints werden ignoriert.
F11
Step Into
Führt den nächsten Befehl aus. Ist dies ein Funktionsaufruf,
stoppt das Programm in der ersten Zeile der Funktion.
F9
Step Instruction
Führt den nächsten Befehl aus und hält danach an.
F10
Step Over
Führt den nächsten Befehl aus. Ist dies ein Funktionsaufruf,
wird die Funktion abgearbeitet und danach angehalten.
STRG-F11
Step Out
Setzt die Ausführung fort, bis die aktuelle Funktion verlassen
wird.
SHIFT-F10
Go until...
Führt das Programm bis zum Eintritt in eine Funktion aus,
deren Namen Sie im Dialogfenster angeben müssen.
STRG-F10
Go to Cursor
Führt das Programm bis zur aktuellen Cursorposition aus.
SHIFT-F5
Stop
Hält den Programmablauf an.
ALT-R
Reset
Versetzt die CPU in den Ursprungszustand, es werden also alle
Register- und Speicherinhalte gelöscht.
STRG-R
Reset&Go
Versetzt die CPU in den Ursprungszustand und beginnt danach
mit der Ausführung des im Flash gespeicherten Programms.
Breakpoints...
Ermöglicht die übersichtliche Verwaltung von gesetzten Breakpoints. Diese können hier auch an- und abgeschaltet werden.
Set/Remove Breakp.
Erzeugt bzw. löscht einen Breakpoint in der Zeile, in dem sich
der Cursor befindet.
Show PC Location
Macht diejenige Zeile im Quelltext-Editor sichtbar, die als
nächstes ausgeführt wird, wenn das Programm weiterläuft.
STRG-B
Die in diesem Abschnitt besprochenen Möglichkeiten stellen die Basis dar, die von den allermeisten Debuggern zur Verfügung gestellt werden. Der HiTOP-Debugger bietet darüberhinaus noch
mehr Funktionen, von denen einige im Rahmen der Übungen näher erläutert werden.
4.1.5 Design-Diagramme erstellen
Wie Sie der Liste der Programmiervorbereitungen entnehmen konnten, müssen für die einzelnen
Versuche Diagramme erstellt werden, die das Design des von Ihnen entwickelten Programms verdeutlichen. Daher bietet es sich an, noch einmal kurz auf diese Thematik einzugehen. Zuerst müssen Sie entscheiden, welchen der nachfolgend angegebenen Diagrammtypen Sie einsetzen möchten:
Struktogramm
• Progammablaufplan
• UML-Diagramme (Aktivitätsdiagramm, Sequenzdiagramm, Zustandsdiagramm)
•
Wie Sie wissen umfasst der UML-Standard zwar 13 Diagrammtypen, allerdings sind nur die hier
4.1 - Programmiervorbereitung
15
erwähnten zur Abbildung der in diesem Labor auftretenden Problemstellungen geeignet. Sollten
Sie weitergehende Informationen zu diesen Diagrammtypen wünschen, finden Sie diese auf Wikipedia unter [UMLDiag]. Ebenso auf Wikipedia finden Sie unter [UMLTools] eine Liste von UML-Werkzeugen, die Sie zur Erstellung der Diagramme verwenden können.
Besonders empfohlen werden die folgenden Programme:
• ArgoUML
(UML, http://argouml.tigris.org)
• BOUML
(UML, http://bouml.free.fr/)
• Vips
(Struktogramm, http://partheil.com/vips/)
• Structorizer
(Struktogramm, http://structorizer.fisch.lu/)
• PapDesigner
(PAP, http://www.gso-koeln.de/papdesigner/)
4.2 Laboraufgabe 2: Addition (Debug-Übung)
4.2.1 Aufgabenstellung
1)
2)
3)
4)
Folgen Sie den Anweisungen des Abschnitts 4.1 auf Seite 10ff..
Erzeugen Sie ein neues Projekt in HiTOP.
Fügen Sie Listing 2 (Seite 16) an passender Stelle in die Datei main.c ein.
Erstellen Sie das Programm und beheben eventuell vorhandene Fehler, bis es fehlerfrei ist und
somit automatisch geflasht wird.
5) Wenden Sie den „Go to Cursor“-Befehl auf Zeile 11 in Listing 2 an.
6) Am unteren Bildschirmrand sehen Sie eine Reiterleiste. Wechseln Sie zum Fenster „Locals“,
falls es nicht schon aktiv ist. Sie merken, dass dort alle lokalen Variablen der Funktion main()
aufgelistet werden. In jeder Zeile findet sich auch der jeweilige Wert der Variablen. Da diese jedoch noch undefiniert sind, steht überall ein Fragezeichen als Wert.
7) Führen Sie das Programm schrittweise aus (bspw. mittels „Step Into“), während Sie die Fenster
„Locals“ und „Disassembly“ beobachten.
8) Haben Sie die Endlosschleife erreicht, setzen Sie das Programm zurück und führen Sie es bis
zum Anfang von main() aus.
9) Setzen Sie auf Zeile 17 des Listings einen Breakpoint.
10) Auf der linken Seite der Entwicklungsumgebung finden Sie einen Reiter namens „Callstack“.
Klicken Sie ihn an und führen Sie das Programm aus.
11) Wird das Programm am Breakpoint gestoppt, beobachten Sie das „Callstack“-Fenster und führen das Programm schrittweise fort, bis Sie wieder in main() angekommen sind.
4.2 - Laboraufgabe 2: Addition (Debug-Übung)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
16
int add(int a, int b)
{
int result;
}
result = a + b;
return result;
int main(void)
{
int a, i, x, y, z;
x = 3;
y = 4;
z = add(x, y);
a = 0;
for (i=0; i<z; i++)
{
a += z;
}
while (1);
}
Listing 2: Programm zu Laboraufgabe 1
4.2.2 Erläuterungen
Im Programmlisting wird Ihnen aufgefallen sein, dass sich am Ende eine Endlosschleife befindet.
Diese ist nicht etwa problematisch, sondern ist sogar zwingend erforderlich. Der Grund dafür besteht darin, daß nach dem Durchlaufen von main() das Programm schlicht aufhört, die CPU jedoch
fleissig weiter Instruktionen aus dem Speicher liest und ab-arbeitet. Im Normalfall werden diese Instruktionen Funktionen des Programms sein, die dann in komplett ungültige Zustände geraten (da
kein Stack initialisiert, keine Parameter übergeben, etc.), es könnten aber auch Daten sein, die gar
keine Instruktionen sind. Sie sehen also, dass main() unter keinen Umständen verlassen werden
darf!
Abschließend noch ein paar Bemerkungen zu den benutzten Fenstern:
Das „Locals“-Fenster, mit dem Sie Bekanntschaft gemacht haben, ist eine spezielle Variante des
benachbarten Fensters „Watch1“ - die Einträge in diesen Listen werden in der Regel als Watches
bezeichnet. Der Begriff stammt daher, daß man auf diese Weise diese Variablen überwacht, quasi
also „anschaut“. Der Unterschied zwischen den beiden Fenstern besteht darin, daß in „Locals“ nur
die lokalen Variablen der aktuellen Funktion angezeigt werden, während das reguläre Watch-Fenster nur diejenigen Watches anzeigt, die man selbst definiert hat.
Hinweis: Ist der Programmablauf angehalten, können die Werte der Watches in den Fenstern
auch beliebig geändert werden, um bestimmte Situationen hervorzurufen bzw. zu testen.
Das mit „Callstack“ bezeichnete Fenster beinhaltet wie Sie festgestellt haben die aktuelle Hie-rarchie der aufgerufenen Funktionen. Es lässt sich insbesondere dazu nutzen, um festzustellen, welche Funktionen eine andere Funktion aufrufen. Dies erreicht man dadurch, indem man in der aufgerufenen Funktion einen Breakpoint setzt.
4.2 - Laboraufgabe 2: Addition (Debug-Übung)
17
Zusammen mit den Einzelschrittbefehlen bilden Breakpoints, Watches und der Call Stack das
Grundgerüst, mit dem alle modernen Debugger ausgerüstet sind. Sie werden davon im Laufe ihrer
beruflichen Karriere noch oft Gebrauch machen, wenn Sie Software entwickeln möchten. Nutzen
Sie also die Gelegenheit und sammeln Sie Erfahrungen im Umgang mit dem Debugger.
4.3 Laboraufgabe 3: Einschalten der LED mit einem C-Programm
4.3.1 Vorgehensweise
1) Legen Sie ein neues Projekt in HiTOP an. Beachten Sie dabei die Hinweise aus Abschnitt 4.1!
2) Öffnen Sie das „Reference Manual“ zum STM32-Mikrocontroller und suchen Sie in der Memory
Map die Basisadresse der RCC Register (Reset and Clock Control). Definieren Sie ein Makro
(#define) in dem Quelltext Ihres Projekts (main.c) mit dieser Basisadresse.
3) Suchen Sie in der Memory Map die Basisadresse für die Register des GPIOB und halten diese
ebenfalls als Makro in Ihrem Quelltext fest.
4) Gehen Sie in dem Reference Manual zum Abschnitt mit der Beschreibung der RCC-Register.
Suchen Sie dort den Offset für das „APB2 peripheral clock enable register“ und definieren Sie
in Ihrem Quelltext wiederum ein Makro für diesen Wert.
5) Lesen Sie aus der Beschreibung des Registers ab, welches Bit für GPIOB zuständig ist. Auch
diesen Wert sollten Sie als Makro festhalten.
6) Gehen Sie in dem Reference Manual zum Abschnitt mit der Beschreibung der GPIO-Register
und lesen Sie den Offset für das „Port configuration register low“ ab. Notieren Sie sich, welche
Bits in diesem Register für die I/O-Leitung Nr. 5 zuständig sind. (MODE5 und CNF5).
7) Bestimmen Sie anhand der Beschreibung der MODE- und CNF-Bits, welchen Wert diese Bits
haben müssen, um die I/O-Leitung in den Modus „General purpose output push-pull“ und „Output mode 2 MHz“.
8) Weiter unten in dem Reference Manual finden Sie den Offset für das „Port output data register“
und das „Port bit set/reset register“. Auch für diese beiden Offsets sollten Sie jeweils ein Makro
in Ihrem Quelltext definieren.
9) Ergänzen Sie in der Funktion main() Ihres Quelltextes zwischen der NVIC-Configuration und
der Endlosschleife die erforderlichen Code-Zeilen, um GPIOB mit Takt zu versorgen und anschließend I/O-Pin 5 zu konfigurieren und die LED einzuschalten. Verwenden Sie dafür zunächst das „Port output data register“. Nach erfolgreichem Test des Programms sollten Sie den
Code so ändern, dass die LED mit Hilfe des „Port bit set/reset register“ eingeschaltet wird.
4.3.2 Optionale Erweiterung
Lassen Sie die LED blinken, indem Sie sie in der Endlosschleife abwechselnd ein- und ausschalten. Rufen Sie zwischen dem Ein- und Ausschalten jeweils die Funktion busy_wait(Wartezeit)
auf. Probieren Sie aus, wie groß der Wert für die Wartezeit gewählt werden muss, damit die LED
mit etwa 2 Hz blinkt.
5 - Nutzung der STM32-Funktionsbibliothek
18
5 Nutzung der STM32-Funktionsbibliothek
5.1 Konfiguration durch die Firmware-Bibliothek
Vor noch nicht allzu langer Zeit war es üblich, dass Hardwarehersteller ihren Kunden lediglich ausführliche Datenblätter und Beschreibungen über die Steuerung und das Verhalten der einzelnen internen Komponenten übergaben. Diese wurden dann mittels Assembler angesprochen. Mittlerweile fand durch den Wechsel von Assembler zu C jedoch auch hier eine Veränderung statt. Hauptsächlich aus Gründen der Entwicklungsgeschwindigkeit bieten die Hersteller nun zusätzliche Firmware-Bibliotheken an, mit denen sich die Funktionen der vorhandenen Hardware deutlich bequemer ausnutzen lassen.
Der von uns verwendete STM32 wird von STMicroelectronics produziert. Man findet die Firmwarebibliothek daher unter [STM32Files]. Das Paket dort beinhaltet nicht nur den Quellcode der Bibliothek in C, sondern auch einige Beispiele, um den Einstieg zu erleichtern. (Für das Praktikum ist es
allerdings nicht notwendig, das Paket herunterzuladen.)
Wichtig ist jedoch die Dokumentation der Bibliothek, die als Dokument UM0427 verfügbar war
(neue Versionen sind nur als minderwertige CHM-Dateien vorhanden). Sie erläutert alle vorhandenen Befehle und dient Ihnen somit während der Softwareentwicklung als erster Anlaufpunkt bei
Problemen und Unklarheiten.
Es ist nun das Ziel, die Konfiguration der STM32-Peripherie über ein Programm durchzuführen, so
dass die LED an PB5 aufleuchtet. Erstellen Sie dazu ein neues Projekt nach Abschnitt 4.1.2 auf
Seite 11. Ist das Projekt erstellt, findet man auf der linken Seite von HiTOP einen Dateibaum, der
die Dateien anzeigt, die zum aktuell geladenen Projekt gehören. Standardmäßig sind dort einige
Basisdateien eingetragen, die die Grundinitialisierung der Hardware durchführen und die Firmware-Bibliothek einbinden.
Klicken Sie auf das „+“-Zeichen neben der Kategorie „Library Sources“. Die nun angezeigte Liste
beinhaltet diejenigen C-Dateien, die für die Steuerung der im Projekt verwendeten Komponenten
verantwortlich sind.
In dem von Ihnen neu erzeugten Projekt sind nun die Dateien,
die die Komponenten RCC, Flash und NVIC verfügbar machen,
sichtbar. Außerdem findet sich in der Liste die Datei
stm32f10x_lib.c, die ebenso wie ihr Pendant stm32f10x_lib.h
selbst keine Funktionalität bereitstellt, jedoch zentrale Konstanten und Variablen für die anderen Teile der Bibliothek vorhält.
Um also die GPIOs ansteuern zu können, muss man die Datei
stm32f10x_gpio.c zu den „Library Sources“ hinzufügen. Dies
gelingt, indem man mit der rechten Maustaste auf den Kategorienamen klickt und „Add Files To Folder“ wählt.
Abbildung 5.1: Datei hinzufügen In dem Verzeichnes des Projektes befindet sich ein Verzeichnis
namens „Library“, das die Header und Quellcodes der Bibliothek beinhaltet. Navigieren Sie dorthin und fügen Sie die Datei stm32f10x_gpio.c in das Projekt
ein.
Dadurch werden zwar rein formell alle Voraussetzungen erfüllt, um die in stm32f10x_gpio.c vorhandenen Funktionen zu nutzen, der Inhalt wird jedoch nur dann vom Compiler beachtet, wenn
eine passende Compilerdirektive definiert ist. Diese befindet sich zusammen mit den restlichen Direktiven der Bibliothek in der Datei stm32f10x_conf.h, die bereits in das Projekt eingebunden ist.
5.1 - Konfiguration durch die Firmware-Bibliothek
19
Öffnen Sie diese Datei mit einem Doppelklick auf den Dateinamen im Projektdateibaum und führen
Sie die folgenden Änderungen durch:
→
Damit werden die GPIO-bezogenen Funktionen nicht länger ignoriert und sind verwendbar. Diese
werden in Kapitel 10 (Seite 166) in der Referenz zur Firmware-Bibliothek (UM0427) beschrieben schauen Sie es sich dort einmal an.
Sollten Ihnen dabei die Typen der Variablen Schwierigkeiten bereiten, finden Sie die entsprechenden Definitionen in Kapitel 1.3.1 (Seite 38). Dort sind auch weitere wichtige Typen und Konstanten
gelistet, die man für die Programmierung der Bibliothek kennen sollte. Ein Blick lohnt also!
Durch Studium und Anwendung der Kapitel über die RCC und die GPIOs könnte nun beispielsweise das folgende Programm entstehen, um PB5 anzusteuern:
1 int main(void)
2 {
3
GPIO_InitTypeDef GPIO_InitStructure; // required by GPIO_Init()
4
5
/* System Clocks Configuration **************************************/
6
RCC_Configuration();
7
8
/* NVIC Configuration ***********************************************/
9
NVIC_Configuration();
10
11
/* Enable GPIO clock for port B */
12
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
13
14
/* Set output mode for pin 5 of port B */
15
GPIO_InitStructure.GPIO_Pin
= GPIO_Pin_5;
16
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
17
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
18
GPIO_Init(GPIOB, &GPIO_InitStructure);
19
20
/* PB5 = 1 */
21
GPIO_SetBits(GPIOB, GPIO_Pin_5);
22
23
while (1);
24 }
Listing 3: Verwendung der STM32-Library zum Setzen von PB5
Welche Bedeutungen RCC_Configuration() und NVIC_Configuration() im Detail haben ist hier zunächst noch nicht wichtig. Es sei jedoch kurz erwähnt, dass die erste Funktion die Takte hochsetzt
(CPU: 72MHz, Busse: 36/72MHz) und die zweite Funktion den „Nested vectored interrupt controller“ (NVIC) an das laufende Programm anpasst.
5.2 - Laboraufgabe 4a: Blinklicht
20
5.2 Laboraufgabe 4a: Blinklicht
5.2.1 Vorgehensweise
1)
2)
3)
4)
Folgen Sie den Anweisungen des Abschnitts 4.1 auf Seite 10.
Erzeugen Sie ein neues Projekt in HiTOP.
Fügen Sie den Quellcode aus Listing 3 in Ihr Programm ein.
Erweitern Sie das Programm so, dass die LED an PB5 zum Blinken gebracht wird. Erstellen
Sie dazu ein Struktogramm, einen PAP oder ein passendes UML-Diagramm, bevor Sie mit der
Codierung beginnen.
5) Verwenden Sie die in main.c vorhandene Funktion busy_wait(Wartezeit) und probieren Sie aus,
bei welchem Wert für die Wartezeit die LED etwa im Sekundentakt (1 Hz) blinkt.
6) Ziehen Sie die Firmware-Referenz unter [STM32LibDoc] zu Rate!
5.2.2 Erläuterungen
In main.c befindet sich die Funktion busy_wait(), die nichts anderes macht, als die CPU eine bestimmte Anzahl von leeren Schleifen durchlaufen zu lassen. Diese Art von Ausführungsverzögerung nennt man „busy loop“, „busy waiting“ oder „spinning“. Sie ist zwar schell geschrieben, hat jedoch erhebliche Nachteile:
die Verzögerungszeit ist nur umständlich berechenbar;
die Verzögerungszeit hängt vom Compiler ab;
• die Verzögerungszeit wird durch auftretende Interrupts beeinflusst;
• die Verzögerungszeit ist direkt vom Takt abhängig, also auch vom Energiesparmodus des SoCs.
•
•
Das schwerwiegendste Problem ist jedoch, dass ein optimierender Compiler feststellen wird, dass
in der Schleife keine Arbeit erfolgt und somit durch Optimierung keinen Code erzeugen wird. Entwickelt und testet man also ein Programm mit abgeschalteter Optimierung und schaltet diese nach
erfolgter Arbeit ein, um ein möglichst gutes Endergebnis zu erzielen, wird das Programm plötzlich
nicht mehr funktionieren, da sämtliche Verzögerungsschleifen wegfallen!
Hinweis: Der Vollständigkeit halber sei erwähnt, dass man auch optimierende Compiler dazu bringen kann, busy loops nicht zu verwerfen. Eine Möglichkeit dazu bietet das C-Schlüsselwort „volatile“, das an dieser Stelle aber nicht weiter behandelt werden soll.
5.3 Timer
Die Nachteile von busy waiting können durch den Einsatz von Timern vermieden werden. Sie sind üblicherweise fester Bestandteil der Hardware und benötigen
zur Verrichtung der Arbeit keine CPU-Ressourcen. Aus
diesem Grund sind sie nicht nur unabhängig von dem,
was die CPU macht, sondern bieten darüberhinaus vielfältige Funktionen und hohe Präzision.
Der hier verwendete STM32 hat insgesamt 4 Timer. Dabei ist TIM1 ein Timer mit erweiterten Funktionen, während TIM2, TIM3 und TIM4 für allgemeine Zwecke gedacht sind. Das bedeutet jedoch nicht, dass die Timer
2..4 nicht viel zu bieten haben. Auch sie besitzen bereits
viele verschiedene Betriebsmodi.
Abbildung 5.2: Aufbau eines Timers
5.3 - Timer
21
Alle Timer haben im Grunde den gleichen Aufbau, der vereinfacht dargestellt aus Controller, Prescaler (Vorteiler) und Counter (Binärzähler) besteht.
Die äußere Taktgenerierung ist komplex. Es reicht daher zu wissen, dass die Timer in unserem Fall
mit 36 MHz getaktet werden. Für Details sei auf [STM32DS], Seite 17, und die Funktion
RCC_Configuration() in main.c verwiesen.1
Der 36 MHz-Takt wird nun durch einen Prescaler um einen frei wählbaren Faktor von 1 bis 65536
heruntergesetzt und dem Zähler zugeführt. Dieser wiederum erhöht oder erniedrigt den internen
Zählerstand um 1, wenn der Prescaler einen Takt generiert. Hat der Zähler einen Unter- oder Überlauf der 16 bit breiten Zählstufe erkannt, signalisiert er dies dem durch Setzen des „Update“-Flags.
Zum Abfragen des Update-Flags stellt die STM32-Funktionsbibliothek eine entsprechende Funktion zur Verfügung:
1
if (TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == SET)
{
...
}
Listing 4: Abfrage des Update-Flags von Timer 2
Das Update-Flag muss anschließend durch das Programm gelöscht werden, um den nächsten
Zählerüberlauf wieder erkennen zu können. Für den Timer TIM2 geschieht das Löschen des Flags
mit dem folgenden Befehl:
1
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
Listing 5: Löschen des Update-Flags von Timer 2
Benutzt man einen Timer, möchte man natürlich genau festlegen, mit welcher Frequenz der Zählerüberlauf stattfindet. Um dies zu erledigen, muss man dem Vorteiler und dem Zähler passende
Werte zuweisen. Hierzu ein Rechenbeispiel:
TIM2CLK = 36MHz = 36.000.000 Hz
Prescaler = 2.250
Period = 80
36.000.000 Hz / 2.250 = 16.000 Hz
16.000 Hz / 80 = 200 Hz
In diesem Beispiel wird also 200 mal pro Sekunde (200 Hz) das Update-Flag gesetzt.
5.4 Laboraufgabe 4b: Blinklicht mit Timer
Im Gegensatz zu Augabe 4a soll nun der Timer 2 für die Zeitverzögerung zwischen dem Ein- und
Ausschalten der LED verwendet werden, um ein präzises Timing zu erzielen. Ziel ist es, dass die
LED exakt mit 1 Hz und einem Tastverhältnis von 50% blinkt, also 1x pro Sekunde für 0,5 Sekun den ein- und 0,5 Sekunden ausgeschaltet wird.
5.4.1 Vorgehensweise
1)
2)
3)
4)
Verwenden Sie das Projekt aus Laboraufgabe 4a.
Aktivieren Sie in der Datei „stm3210x_conf.h“ zusätzlich die Makros _TIM und _TIM2.
Fügen Sie bei den Library-Sources die Datei „stm32f10x_tim.c“ hinzu.
Ergänzen Sie in Ihrem Programm den Funktionsaufruf, um Timer 2 mit Takt zu versorgen.
1 Hinweis: HCLK = AHB clock; PCLK1 = Low Speed APB = APB1; PCLK2 = High Speed APB = APB2
5.4 - Laboraufgabe 4b: Blinklicht mit Timer
22
5) Definieren Sie eine strukturierte Variable für die Konfiguration der Timers mit Hilfe der Funktion
TIM_TimeBaseInit() und initialisieren Sie diese Struktur mittels TIM_TimeBaseStructInit().
6) Stellen Sie die Elemente TIM_Period und TIM_Prescaler der strukturierten Variable so ein,
dass alle 0,5 Sekunden ein Zählerüberlauf stattfindet. Zusätzlich können Sie noch die Zählrichtung über TIM_CouterMode auf TIM_CounterMode_Up2 einstellen, wobei die Zählrichtung für
diese Laboraufgabe unbedeutend ist, da wir den Zählerstand nicht auslesen. Konfigurieren Sie
den Timer 2 mit Hilfe dieser strukturierten Variable und der Funktion TIM_TimeBaseInit().
7) Aktivieren Sie den Timer 2 mittels TIM_Cmd() (→ ENABLE).
8) Fragen Sie in der Endlosschleife das Update-Flag ab (siehe Listing 4) und wechseln Sie den
Zustand der LED bei jedem Zählerüberlauf. (Die Abfrage des aktuellen Zustands kann über die
Funktion GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_5) erfolgen.) Denken Sie daran, nach
jedem Zählerüberlauf das Update-Flag wieder gemäß Listing 5 zu löschen!
6 Interrupt-Handler
Es gibt eine Vielzahl von Interrupts, denen auf dem STM32 ein Handler bzw. eine ISR zugewiesen
werden kann. Sie alle sind bereits in der Datei interrupt.c vorhanden, jedoch leer. Soll ein Handler
nun eine bestimmte Aufgabe verrichten, schreiben Sie den entsprechenden Code einfach hinein.
Beachten Sie jedoch, dass es nicht ausreichend ist, eine ISR mit Code zu füllen und den Interrupt
in der entsprechenden Komponente (bspw. TIM2) zu aktivieren. Der Interrupt würde in diesem Fall
zwar den Interrupt Controller erreichen, dieser würde ihn jedoch ignorieren und der Handler würde
nie aufgerufen werden.
Um dies zu ändern, muss der gewünschte Interrupt auch beim NVIC aktiviert werden, was sich
zum Beispiel mit NVIC_Init()3 bewerkstelligen lässt. Um die Konfiguration des NVIC zu vereinfachen, benutzen Sie bitte die Funktion NVIC_StructInit() zur Initialisierung der strukturierten Variablen mit Default-Werten für die Preemption Priority und die Sub Priority. Weitere Details dazu entnehmen Sie bitte der Bibliotheks-Referenz.
6.1.1 Funktionsweise eines Timer-Interrupts
Hat ein Timer einen Unter- oder Überlauf erkannt, sendet er ein Signal an eine Leitung des Interrupt Controllers (NVIC), der die CPU unterbricht und die für diese Leitung konfigurierte Interrupt
Service Routine (ISR) aufruft. Das Update-Flag signalisiert in diesem Fall der ISR, dass der Zählerstand die Ursache für die Auslösung des Interrupts war. Das Update-Flag muss von der ISR gelöscht werden, um die erfolgreiche Bearbeitung zu signalisieren. Geschieht dies nicht, wird der Interrupt unabhängig vom Zählerstand so lange ausgelöst, bis die Bearbeitung erfolgt ist.
Sie sollten darauf achten, Ihre Interrupt-Handler so kurz wie möglich zu halten. Der Hauptgrund
dafür ist der, dass der Interrupt-Handler eventuell gerade andere wichtige Programmteile unterbricht und Sie somit zeitkritische Arbeiten verzögern könnten. Das Resultat davon wären dann
Fehler, beispielsweise in der Einhaltung von Protokoll-Timings (Stichwort: bit banging).
Desweiteren könnte der Handler des Timers wiederum von noch wichtigeren Interrupts unterbrochen werden, was am Ende dazu führen kann, dass der Timer einen Interrupt auslöst, während der
entsprechende Handler den vorigen Interrupt noch bearbeitet.
2 Hier ist ein Fehler auf Seite 343 des UM0427: Es muss TIM_CounterMode_Up heißen.
3 In dem Programmbeispiel auf Seite 228 des UM0427 steht fälschlicher Weise NVIC_InitStructure()
6.2 - Laboraufgabe 5: Lauflicht mit Timer-ISR
23
6.2 Laboraufgabe 5: Lauflicht mit Timer-ISR
6.2.1 Aufgabenstellung
Die Anwendungsplatine mit dem LCD verfügt über 8 grüne LEDs, siehe Abbildung 1.2. Diese LEDs
sollen als „Lauflicht“ angesteuert werden, d. h. es soll immer nur eine der acht LEDs leuchten, angefangen mit LED1, dann LED2, bis LED8. Anschließend soll das Ganze wieder von vorn beginnen. Ein „Durchlauf“ soll dabei genau 1 Sekunde dauern. Das Timing soll dabei mit Hilfe einer Interrupt-Service-Routine (ISR) erfolgen.
6.2.2 Hinweise
Die LEDs auf der Anwendungsplatine sind mit dem GPIO-Port C verbunden. Dabei muss beachtet
werden, dass die erste LED an PC5 und die achte LED an PC12 angeschlossen ist:
PC15
PC14 PC13 PC12 PC11 PC10
32768 16384 8192
4096 2048
1024
PC9
PC8
PC7
PC6
PC5
PC4
PC3
PC2
PC1
PC0
512
256
128
64
32
16
8
4
2
1
Bisher wurden die Zustände von GPIO-Ausgängen mittels GPIO_SetBits() und GPIO_ResetBits()
gesetzt. Da GPIOs aber in 16er-Gruppen zu Ports zusammengefasst sind, ist es möglich, die Werte aller 16 Pins eines Ports gleichzeitig auszulesen oder zu setzen:
1
2
current_value = GPIO_ReadOutputData(GPIOC);
GPIO_Write(GPIOC, new_value);
Listing 6: Lesen und Setzen des gesamten GPIO-Ports C
Beim Schreiben eines neuen Wertes in das Port-Register sollen jedoch nur die Ausgänge für die 8
LEDs (PC5...12) verändert werden. Die restlichen Portleitungen sollen ihren alten Wert behalten!
Verwenden Sie dazu das Read-Modify-Write-Prinzip!
Für die Lauflichtfunktion ist der Operator „<<“ in „C“ sehr hilfreich!
6.2.3 Vorgehensweise
1) Legen Sie ein neues Projekt an, schalten Sie den Timer 2 und den GPIOC in der Datei
stm32f10x_conf.h frei. Binden Sie die Library-Sources für Timer und GPIO in das Projekt ein!
2) Konfigurieren Sie die Leitungen 5 bis 12 des GPIO-Ports C auf Output Push-Pull 2MHz. Denken Sie auch an die Taktversorgung des GPIOC!
3) Konfigurieren Sie den Timer 2 analog zur Laboraufgabe 4b. Dabei soll die Update-Frequenz jedoch auf 8 Hz eingestellt werden.
4) Der Interrupt-Ausgang eines verwendeten Timers muss durch Aufruf von TIM_ITConfig() explizit aktiviert werden, bevor der jeweilige Clock Controller ein Signal an den NVIC abschickt. Als
Interrupt-Quelle ist lediglich TIM_IT_Update zu aktivieren.
5) Denken Sie daran, den Timer mittels TIM_Cmd() zu aktivieren!
6) Der Interrupt Controller muss über den Befehl NVIC_Init() initialisiert werden, sodass er die
vom gewählten Timer verursachten Interrupts bearbeitet.
7) Fügen Sie Ihre ISR in der Datei interrupts.c in der Funktion TIM2_IRQHandler() ein!
8) In der ISR muss mit Hilfe von TIM_ClearFlag() das Update-Flag zurückgesetzt werden!
7 - Tasten
24
7 Tasten
Eingebettete Systeme verrichten ihre Arbeit zwar oft autonom, ein sehr großer Anteil benötigt für
die Arbeit jedoch Benutzereingaben. Dies trifft auch auf die in diesem Laborpraktikum zu entwickelnde Uhr zu, deren Platine mit zwei Tastern ausgestattet ist. Ziel dieses Abschnittes ist es, ein
Modul für die beiden Taster zu entwickeln, das für die Uhr später wiederverwendet werden kann.
Wie aus dem Schaltplan ersichtlich ist, sind die Taster direkt mit den GPIOs PB8 und PB9 verbunden. In Abschnitt 3.1.2 konnte bereits beobachtet werden, dass die GPIOs sich nach einem Reset
in einem Zwischenzustand befinden, der im Englischen als „floating“ bezeichnet wird. Das bedeutet, dass der entsprechende Pin keinen definierten Pegel hat und er sich so verhält, als wäre er
nicht verbunden und hinge frei in der Luft. Es muss also auch für die GPIOs der Taster eine Konfiguration erfolgen, bevor diese genutzt werden können - diesmal jedoch nicht als Ausgang, sondern
als Eingang:
1
2
3
4
5
6
7
GPIO_InitTypeDef Pin_Config;
Pin_Config.GPIO_Pin
= GPIO_Pin_8 | GPIO_Pin_9;
Pin_Config.GPIO_Mode = GPIO_Mode_IPD;
Pin_Config.GPIO_Speed = GPIO_Speed_2MHz; // doesn't matter!
GPIO_Init(GPIOB, &Pin_Config);
Listing 7: Konfiguration der GPIOs 8 und 9 als Eingänge mit Pull-down-Widerstand
Die Konstante GPIO_Mode_IPD steht dabei für die
Konfiguration als Eingang (Input) mit pull down-Widerstand (PD). Analog dazu wird der interne pull upWiderstand benutzt, wenn GPIO_Mode_IPU verwendet wird.
Im Bild rechts ist leicht zu erkennen, dass durch
Schließen von SPU (pull up) bzw. SPD (pull down)
festgelegt wird, welcher Pegel am Eingang anliegt,
wenn kein externes Bauteil einen anderen Pegel
vorgibt. Die Widerstände sind dabei so gewählt,
dass selbst bei Kurzschluss nur ein minimaler Strom
über sie fliesst.
Auf der Uhrenplatine sind die Taster gegen die Betriebsspannung VCC geschaltet, was beim Durchschalten eine logische Eins darstellen soll. Da der
linke Schaltkontakt im unbetätigten Fall in der Luft
Abbildung 7.1: GPIO-Eingangsbeschaltung
hängt, sorgt der durch die Konfiguration des GPIOs
gewählte pull down-Widerstand dafür, dass der Pegel am Eingang eine stabile logische Null ergibt.
Wenn die beiden GPIOs einsatzbereit sind, kann man deren Zustand beispielsweise über die
Funktion GPIO_ReadInputDataBit() erfahren. Sie liefert entweder Bit_SET oder Bit_RESET:
1
2
3
4
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_8) == Bit_SET)
{
...
}
Listing 8: Abfrage eines GPIO-Eingangs
7.1 - Laboraufgabe 6: Entprellung
25
7.1 Laboraufgabe 6: Entprellung
Während des Tests der vorhergehenden Aufgabe haben Sie sicherlich beobachtet, dass das Lauflicht nicht immer wie gewünscht auf die Eingaben reagiert. Der Grund dafür ist kein Fehler im Programm, sondern eine Erscheinung, die als „Prellen“ bezeichnet wird. Dabei prallen die beiden
Schaltkontakte einers Tasters oder Schalters durch die mechanische Krafteinwirkung derart aufeinander, dass der Kontakt während einer kurzen Zeitspanne weder stabil als „offen“, noch stabil als
„geschlossen“ angesehen werden kann. Beispielhaft ist dies im folgenden Bild zu sehen, auf dem
der Prellvorgang eines sich öffnenden Tasters dargestellt ist:
Es lässt sich leicht erkennen, dass durch solche Prellvorgänge ein angeschlossenes digitales Bauteil mehrere Wechel zwischen Eins und Null registriert. Da der
Taster jedoch nur einen einzelnen Wechsel von Eins
nach Null verursachen soll, ist der Effekt äußerst unerwünscht.
Abhilfe schaffen kann man auf mehrere Arten, beispielsweise mit Hilfe von Schmitt-Triggern oder RSFlip-Flops. Bauteile kosten jedoch Geld, weshalb man
heute im Allgemeinen eine Softwarelösung vorzieht.
Die für dieses Laborpraktikum verwendete Lösung beruht auf einem Algorithmus, der sich in festen IntervalAbbildung 7.2: Prellvorgang
len den Eingangszustand merkt. Ist dieser Zustand
über mehrere Intervalle konstant, wird dieser Zustand als stabil angenommen und an andere Programmteile weitergereicht. Da es nach Betätigung des Tasters einige Intervalle dauert, bis sich ein
stabiler Zustand eingestellt hat, wird das Signal um eine gewisse Zeit verzögert. Diese Zeit hängt
auch davon ab, wie viele Zustände identisch sein müssen, bis der Algorithmus den Zustand als
stabil ansieht.
Ist die Verzögerung zu kurz, so ist die Entprellung unzuverlässig. Ist sie hingegen zu lang, empfinden Benutzer das System als langsam und träge. Es gilt also, einen Mittelweg zu finden.
Der gewählte Algorithmus benutzt eine Variable vom typ „unsigned char“, die somit 8 Bit besitzt.
Folglich lassen sich in dieser Variablen 8 einzelne binäre Zustände speichern. Diese sollen in jedem Zeitintervall um eine Bit-Stelle verschoben werden, so dass sich eine zeitliche Abfolge der Zustände abbilden lässt:
Operation
27
26
25
24
23
22
21
20
Z7
Z6
Z5
Z4
Z3
Z2
Z1
Z0
<< 1
| Zx
∕
∕
∕
∕
∕
∕
∕
Z6
Z5
Z4
Z3
Z2
Z1
Z0
|
|
|
|
|
|
|
Z6
Z5
Z4
Z3
Z2
Z1
Z0
0
Zx
Der zeitlich gesehen neueste Zustand wird dabei durch das niederwertigste Bit abgebildet, während der älteste Zustand durch das höchstwertigste Bit dargestellt wird. Beim Verschieben der Bits
um eine Stelle nach links fällt der älteste Zustand weg und Bit 0 wird auf Null gesetzt. Durch die
7.1 - Laboraufgabe 6: Entprellung
26
anschließende Oder-Operation wird dieses Bit dann mit dem Wert des aktuellen Zustands Z x belegt.
Hat die 8-Bit-Variable also den Wert 0 angenommen, so hat der Eingang über einen Zeitraum von
8 Intervallen keine Signaländerung vollzogen - er ist also stabil. Das selbe gilt, wenn der Wert 255
angenommen wird.
Verzögerungszeiten von 100ms werden von Menschen gerade noch nicht wahrgenommen. Um
diese Grenze nicht zu überschreiten, ist es daher zweckmäßig, den Algorithmus alle 80 ms / 8 = 10
ms aufzurufen. Der Timer, der dies erledigt, muss also auf eine Frequenz von 100 Hz eingestellt
werden.
7.1.1 Aufgabenstellung
Das Programm aus Laboraufgabe 5 („Lauflicht“) soll so erweitert werden, dass durch Drücken der
Taste S1 die Richtung des Lauflichts gewechselt werden kann.
7.1.2 Vorgehensweise
1) Fügen Sie zum Projekt aus Laboraufgabe 5 die Dateien pushbuttons.c und pushbuttons.h in
die Dateikategorien „Source Files“ bzw. „Header Files“ des Projekts hinzu.
2) Versehen Sie die Funktionen in pushbuttons.c mit Inhalt, der die in Abschnitt 7.1.3 erläuterten
Aufgaben erfüllt. Die Prototypen in pushbuttons.h dürfen dabei nicht verändert werden.
3) Verwenden Sie TIM2 für das Lauflicht und TIM4 für den periodischen Aufruf des Entprellalgorithmus. Timer TIM4 wird von nun an für diese Aufgabe reserviert sein.
4) Verwenden Sie eine globale Variable in main.c für die die Laufrichtung. Deklarieren Sie diese
Variable in main.h mit Hilfe der Speicherklasse „extern“. Dadurch können Sie den Wert der
Variablen auch innerhalb der ISR in der Datei interrupt.c abfragen.
5) Das Abfragen der Taste und das Umschalten der Laufrichtung soll innerhalb der Endlosschleife
in main() erfolgen. Erstellen Sie für diesen Code-Teil zunächst ein Struktogramm oder Flussdiagramm!
7.1.3 Schnittstelle von pushbuttons.h
void BTN_Init(void)
Diese Funktion initialisiert die Schnittstelle zum Abfragen der Tasten S1 und S2, indem sie Timer
TIM4, die GPIOs PB8/9 und den NVIC entsprechend konfiguriert.
void BTN_UpdateButtonStates(void)
Der Interrupt-Handler von TIM4 soll diese Funktion aufrufen. Sie muss die Tasten abfragen und
den Entprell-Algorithmus implementieren. Tritt dabei eine Zustandsänderung einer der beiden Tasten auf, muss die entsprechende Variable (BTN_State1 bzw. BTN_State2) aktualisiert werden.
BTN_State BTN_GetButtonState(BTN_Identifier ButtonID)
Über diese Funktion kann ein anderer Programmteil den aktuellen Zustand eines bestimmten Tasters erfahren. Die Funktion nimmt dabei die Werte BTN_ID_S1 und BTN_ID_S2 entgegen. Als Ergebnis liefert sie entweder BTN_State_Pressed oder BTN_State_Released zurück.
Beachten Sie, dass in dieser Funktion keine andere Funktion aufgerufen werden muss. Sie arbeitet lediglich mit den Werten der Variablen BTN_State1 und BTN_State2.
8 - Assembler
27
8 Assembler
8.1 Laboraufgabe 7a: ggT
Es soll eine Funktion in Assembler geschrieben werden, die den größten gemeinsamen Teiler
(ggT) von zwei Zahlen (Datentyp int) berechnet. Diese Funktion soll aus dem C-Programm heraus
aufgerufen werden.
1
2
3
4
5
6
7
8
9
10
11
ggt(int a, int b)
{
while (a != b)
{
if (a > b)
a -= b;
else
b -= a;
}
return a;
}
Listing 9: Berechnung des größten gemeinsamen Teilers in „C“ (Euklidischer Algorithmus)
8.1.1 Vorgehensweise
1) Erstellen Sie ein Assembler-geeignetes Flussdiagramm aus dem C-Code in Listing 9!
2) Legen Sie ein neues Projekt an. Fügen Sie den Quelltext asm_functions.s in das Projekt ein!
3) Fügen Sie die notwendigen Assembler-Befehle gemäß Ihres Flussdiagramms an der vorgesehenen Stelle in den Quelltext asm_functions.s ein. Beachten Sie, dass die Funktionsparameter
in den Registern R0 und R1 übergeben werden und der Rückgabewert in R0 stehen muss.
4) Fügen Sie in den Quelltext main.c die Funktionsdeklaration ein:
int ggt(int a, int b);
5) Fügen Sie in der Funktion main() einen Funktionsaufruf von ggT() ein und prüfen Sie mit Hilfe
des Debuggers, ob die Funktion korrekt arbeitet (→ Breakpoints, lokale Variablen prüfen)!
8.2 Laboraufgabe 7b: Bubblesort
Schreiben Sie eine Funktion in Assembler, die ein Feld von Zahlen (Datentyp int) sortiert. Die
Funktion soll als ersten Parameter die Feldvariable (Adresse des ersten Feldelements) und als
zweiten Parameter die Feldgröße erwarten:
1
2
3
4
5
6
7
8
9
10
11
12
void bubblesort(int array[], int count) {
int i, tmp1, tmp2;
for (count--; count>0; count--)
for (i=0; i<count; i++) {
tmp1 = array[i];
tmp2 = array[i+1];
if (tmp1 > tmp2) {
array[i+1] = tmp1;
array[i] = tmp2;
}
}
}
Listing 10: Bubblesort in „C“
8.2 - Laboraufgabe 7b: Bubblesort
28
8.2.1 Vorgehensweise
1) Erstellen Sie ein Assembler-geeignetes Flussdiagramm aus dem C-Code in Listing 10!
2) Fügen Sie die notwendigen Assembler-Befehle gemäß Ihres Flussdiagramms an der vorgesehenen Stelle in den Quelltext asm_functions.s ein.
3) Fügen Sie in den Quelltext main.c die Funktionsdeklaration ein:
void bubblesort(int array[], int count);
4) Fügen Sie in der Funktion main() einen Funktionsaufruf von bubblesort() ein und prüfen Sie mit
Hilfe des Debuggers, ob die Funktion korrekt arbeitet (→ Breakpoints, lokale Variablen prüfen)!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@ --------------------------------------------@ mode of assembly
@ --------------------------------------------.syntax unified
.thumb
.arch armv7m
@ --------------------------------------------@ allocation
@ --------------------------------------------.text
@ --------------------------------------------@ function definition
@ --------------------------------------------.global my_function @ make 'my_function' visible
.thumb_func
my_function:
@ <- your code goes here!
bx
lr
@ return from subroutine
@ --------------------------------------------.end
Listing 11: Datei „asm_functions.s“
9 - Pulsweitenmodulation
29
9 Pulsweitenmodulation
Eine sehr einfache Möglichkeit, analoge Signale zu erzeugen, bietet die Pulsweitenmodulation.
Zwar besitzt der STM32 Mikrocontroller bereits einen Digital-Analog-Wandler, aber bei der folgenden Laboraufgabe soll die Helligkeit einer LED an einem Digitalausgang gesteuert werden.
9.1 Laboraufgabe 8: LED-Helligkeit mit PWM
9.1.1 Augabenstellung
Es soll ein Programm für den erstellt werden, das die Helligkeit der roten LED (PB5) mit Hilfe einer
Pulsweitenmodulation mit einer Frequenz von 1kHz steuert. Dabei soll die Helligkeit durch Drücken
(und Halten) der Taste S1 (PB8) an der Anwendungsplatine erhöht und mit der Taste S2 (PB9) verringert werden können.
9.1.2 Vorgehensweise
1) Erstellen Sie in der HiTOP IDE ein neues Projekt und fügen Sie in dem „Workspace - FileView“
zu den Library Sources die Datei „stm32f10x_gpio.c“ und „stm32f10x_tim.c“ hinzu.
2) Öffnen Sie das STM32 Reference Manual und schlagen Sie dort im Abschnitt „9.3.7 Timer alternate function remapping“ nach, welcher der Timer 2 bis 7 so konfiguriert werden kann,
dass er die rote LED (PB5) ansteuert. (Timer Nummer? Timer Channel? Remapping?)
3) Öffnen Sie die Datei „stm32f10x_conf.h“ und aktivieren Sie dort die Makros _GPIO, _GPIOB,
_AFIO, _TIM und _TIMx (x = Nummer des unter Punkt 2 gewählten Timers).
4) Öffnen Sie die Datei „main.c“ und ergänzen Sie dort den Funktionsaufruf zum Einschalten der
Taktversorgung von GPIOB und Timer 2, sowie den Takt für die „alternate function“ der
GPIOs.
5) Konfigurieren Sie GPIOB Pin 8 und 9 als Input mit Pull-Down-Widerstand. Verwenden Sie dazu
die Funktion GPIO_Init() gemäß der Dokumentation (Bandbreite: 2 MHz).
6) Berechnen Sie den erforderlichen Wert für den Vorteiler (Prescaler) mit dem eine Zählerobergrenze (Period) von 100 insgesamt eine Frequenz von 1 kHz ergibt.
7) Verwenden Sie die Funktionen TIM_TimeBaseStructInit() und TIM_TimeBaseInit(), um den
Timer zu konfigurieren (1 kHz, Modus: count up), und schalten Sie den Timer mit der Funktion
TIM_Cmd() ein.
8) Verwenden Sie die Funktionen TIM_OCStructInit() und TIM_OCxInit(), wobei Sie das 'x' durch
die Kanalnummer (Channel) ersetzen müssen, die Sie unter Punkt 2 ausgewählt haben. Modus: PWM1, Output State: Enable, Pulse: 50 (= halbe Helligkeit), Polarity: high.
9) Stellen Sie das GPIO-Remapping mit Hilfe der Funktion GPIO_PinRemapConfig() gemäß
Punkt 2 ein, so dass die rote LED (Port B, Pin 5) von dem Timer angesteuert wird.
10) Konfiguration für GPIOB, Pin 5 als „Alternate Function Push-Pull“, damit die LED von dem
Timerausgang angesteuert wird.
11) Fügen Sie in die Endlosschleife am Ende von main() die Abfrage der Tasten mit Hilfe von
GPIO_ReadInputDataBit() ein. 10. Ergänzen Sie dort den erforderlichen Code, mit dem bei
gedrückter Taste S1 mit Hilfe der Funktion TIM_SetComparex() (x = Nr. des Kanals gemäß
Punkt 2) das PWM-Tastverhältnis um 1 (bis max. 100) erhöht wird und bei gedrückter Taste S2
um 1 verringert wird (solange > 0). In beiden Fällen soll anschließend mit busy_wait(50000)
eine kurze Zeit gewartet werden.
10 - LCD-Einführung
30
10 LCD-Einführung
Flüssigkristallanzeigen (LCDs) sind aus unserer heutigen Welt nicht mehr wegzudenken. Sie dienen als Anzeigen in vielfältigen Anwendungsbereichen und sind durch ihre niedrigen Produktionskosten interessante Alternativen zu LED-Anzeigen. Die Erweiterungsplatine zum STM32-PerformanceStick, mit der die Uhr erstellt wird, besitzt ebenfalls ein LCD. In diesem Abschnitt wird nun
gezeigt, wie man es anspricht und welche Besonderheiten zu beachten sind.
Die Displays selbst benötigen mehrere Betriebsspannungen und eine präzise Ansteuerung, damit
sie keinen Schaden nehmen. Aus diesem Grund muss man zwischen reinen LCDs und LCD-Modulen unterscheiden. Letztere sind bereits mit einem passenden Controller ausgestattet, wodurch
die Anbindung an den Rest der Schaltung enorm vereinfacht wird. Die Controller erzeugen die benötigten Spannungen/Impulse, stellen aber auch Schnittstellen für die Ansteuerung bereit. Am häufigsten anzutreffen sind dabei Schnittstellen mit 4- und 8-Bit-Bussen, I²C und SPI. Das im Labor
eingesetzte LCD-Modul wird im SPI-Modus betrieben, weshalb dessen Grundlagen erarbeitet werden müssen, bevor das LCD-Modul besprochen werden kann.
10.1 SPI
Der SPI-Bus wird primär zum Anschluss von Peripheriekomponenten an eine CPU/ein SoC benutzt. Das Kürzel SPI steht dabei für „Serial Peripheral Interface“, was bereits erkennen lässt, dass
die Datenübertragung seriell erfolgt. Die Anbindung erfolgt in Sterntopologie, wodurch relativ hohe
Geschwindigkeiten ermöglicht werden. Da alle am Bus angeschlossenen Komponenten die gleiche
Taktleitung benutzen, kann immer nur ein Busteilnehmer den Takt vorgeben. Aus diesem Grund
muss jeder Komponente eine Rolle als Master (stellt Takt bereit) oder Slave (benutzt Takt) zugeteilt
werden.
Der SPI-Bus hat üblicherweise drei Leitungen:
SCK - Serial Clock
• MISO - Master in, Slave out
• MOSI - Master out, Slave in
•
Diese Leitungen werden von allen Komponenten gleichermaßen verwendet - es ist somit einleuchtend, dass
zu jedem Zeitpunkt immer nur eine als Slave arbeitende
Peripherieeinheit mit dem Master kommunizieren kann.
Um zu bestimmen, welche Komponente den Bus benutzen darf, gibt es daher noch zusätzliche „Chip Select“Leitungen, die vom Master verwaltet werden.
Auf der Uhrenplatine befinden sich zwei Slave-Komponenten, die fast genau so an den STM32 angeschlossen sind, wie auf dem Bild rechts zu sehen ist: das LCD
und der Beschleunigungssensor. Das LCD sendet je- Abbildung 10.1: Beispiel SPI-Bus
doch keine Daten aus, sodass die MISO-Leitung hier
keine Verwendung findet. Auf der Platine gibt es somit
eine Chip Select-Leitung für das LCD (SPI1_CS_LCD), eine für den Sensor (SPI1_CS_Accel) und
eine weitere für Erweiterungen (SPI1_CS_Plug), die momentan unbenutzt ist.
Zuerst soll das LCD angesteuert werden. Damit es Daten empfangen kann, müssen aufgrund der
Funktionsweise des Busses die folgenden Bedingungen erfüllt sein:
• Der STM32 muss als Master konfiguriert sein;
• Die Chip Select-Leitungen müssen so geschaltet sein, dass nur das Display angesprochen wird.
10.1 - SPI
31
Um diese zu erfüllen, muss der STM32 entsprechend konfiguriert werden. Dies geschieht auf die
gleiche Weise wie bei allen Hardwarekomponenten des SoCs. Hinzu kommt jedoch die Besonderheit, dass die SPI-Schnittstelle mit GPIO-Pins verbunden ist, die nicht frei wählbar sind. Die Pins
PA5..7 müssen also ebenfalls aktiviert und konfiguriert werden, damit sie für die Schnittstelle arbeiten können. Da die Funktion als SPI-Schnittstelle eine Sonderfunktion darstellt, muss den betroffenen GPIOs mitgeteilt werden, dass eben diese Sonderfunktion genutzt werden soll. Dies geschieht, indem ihr Modus auf GPIO_Mode_AF_PP gesetzt wird (AF = alternate function):
1
2
3
4
5
6
GPIO_InitTypeDef Pin_Config;
Pin_Config.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
Pin_Config.GPIO_Mode = GPIO_Mode_AF_PP;
Pin_Config.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &Pin_Config);
Listing 12: Konfiguration der GPIOs PA5..7 für SPI1
Die Reihenfolge zur Initialisierung von SPI1 sieht demnach wie folgt aus:
1) Takt für GPIOA und SPI1 über RCC_APB2PeriphClockCmd() bereitstellen
2) Die als Chip Select-Leitungen verwendeten GPIOs SPI1_CS_LCD, SPI1_CS_Accel und
SPI1_CS_Plug im Modus GPIO_Mode_Out_PP initialisieren (GPIO Port A)
3) Alle drei Chip Select-Leitungen auf „low“ in negativer Logik setzen (= Ausgangsbit gesetzt)
4) Von SPI1 verwendete GPIOs PA5..7 mittels GPIO_Init() initialisieren
5) Konfiguration von SPI1 durch SPI_Init()
6) SPI_Cmd() verwenden, um SPI1 zu aktivieren
Der Aufruf von SPI_Init() zur Konfiguration von SPI1 soll von Ihnen mit Hilfe der Firmware-Bibliotheks-Referenz erarbeitet werden. Schlagen Sie die benötigten Parameter dort nach und realisieren Sie die folgenden Einstellungen:
Vollduplex-Modus mit 2 Datenleitungen (Senden/Empfangen)
• STM32 ist Busmaster
• Pro Datenpaket werden 8 Bit übertragen
• Wenn keine Daten auf dem SPI-Bus übertragen werden, ist die SCK-Leitung „low“
• Die erste Flanke des Takts wird zur Auswertung eintreffender Daten verwendet
• Das (im Labor unbenutzte) NSS-Signal wird softwareseitig gesteuert
• Die Baudrate ergibt sich aus 72 MHz / 8 = 9 MHz 4
• Das höchstwertigste Bit wird zuerst übertragen 5
•
Damit wäre SPI1 selbst einsatzbereit, jedoch ist noch keine am Bus angeschlossene Peripherieeinheit als Empfänger gewählt. Dies lässt sich ändern, indem die zur entsprechenden Einheit gehörende GPIO-Leitung den Wert „high“ in negativer Logik annimmt, ihr zum GPIO-Port gehörendes
Bit also gelöscht wird. Die drei Chip Select-Leitungen sollten dabei durch die drei Konstanten
SPI1_CS_LCD, SPI1_CS_Accel und SPI1_CS_Plug ausgedrückt werden, damit eine gewisse Abstrahierung ermöglicht wird. Beispielhaft soll die Wahl der CS-Leitung für das LCD geschehen:
4 Achtung: Im UM0427, Seite 309, Tabelle 420 ist ein Tippfehler: statt SPI_FisrtBit_MSB muss es natürlich
SPI_FirstBit_MSB heißen.
5 Achtung: Im UM0427 Seite 308, Tabelle 419 ist ein Tippfehler: statt SPI_BaudRatePrescaler8 muss es
SPI_BaudRatePrescaler_8 heißen.
10.1 - SPI
1
2
32
GPIO_SetBits(GPIOA,
SPI1_CS_LCD | SPI1_CS_Accel | SPI1_CS_Plug);
GPIO_ResetBits(GPIOA, SPI1_CS_LCD);
Listing 13: Aktivierung der Chip Select-Leitung des LCDs
Die erste Zeile des Listings stellt dabei sicher, dass alle anderen Slaves deaktiviert sind, bevor die
gewünschte Peripherieeinheit aktiviert wird.
Jetzt ist auch das LCD empfangsbereit und wird die vom STM32 über den SPI-Bus gesendeten
Daten entgegennehmen. Da der STM32 jedoch über keinen Sende-/Empfangspuffer verfügt, muss
vor dem Senden jedes Bytes gewartet werden, bis die SPI-Schnittstelle zum Senden bereit ist. Danach kann dann mittels SPI_I2S_SendData() ein weiteres Byte auf den Bus geschickt werden. Auf
Empfängerseite müssen die Daten jedoch auch verarbeitet werden können, weshalb nach Versand
der Daten eine kleine Verzögerung angemessen sein kann. Dieses Vorgehen sei auch hier kurz
exemplarisch dargestellt:
1
2
3
4
5
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
SPI_I2S_SendData(SPI1, Data);
for (i=0; i<10; i++);
Listing 14: Senden eines Bytes via SPI
Die Verwendung eines busy loops ist in diesem Fall übrigens gerechtfertigt. Würde man für diese
Aufgabe einen Timer verwenden wollen, würde der Code um einiges komplexer und es könnte sogar möglich sein, dass die Abarbeitung des Codes zur Verwaltung des Timers länger dauert als
eine solch kleine Schleife. Im Einzelfall muss man also abwägen.
10.2 Laboraufgabe 9a: SPI-Bus-Modul
10.2.1 Aufgabenstellung
1) Folgen Sie den Anweisungen des Abschnitts 4 auf Seite 10.
2) Erzeugen Sie ein neues Projekt in HiTOP.
3) Kopieren Sie die Dateien spi.c und spi.h in Ihr Projekt und fügen Sie diese in die Dateikategorien „Source Files“ bzw. „Header Files“ des Projekts hinzu.
4) Versehen Sie die Funktionen in spi.c mit Inhalt, der die in Abschnitt 10.2.2 erläuterten Aufgaben
erfüllt. Die Prototypen in spi.h dürfen dabei nicht verändert werden. Orientieren Sie sich bei der
Codierung der Funktionen an den Beispielen des vorangegangenen Abschnittes.
5) Für diese Aufgabe ist keinerlei Diagramm erforderlich.
10.2.2 Schnittstelle von spi.h
void SPI1_Init()
Diese Funktion initialisiert die SPI-Schnittstelle, indem sie die in Abschnitt 10.1 erläuterte Initialisierungsliste umsetzt.
void SPI1_SendData(u8 Data)
Unter Verwendung dieser Funktion sollen andere Programmteile Daten über den SPI-Bus schicken
können. In Listing 14 wurde das notwendige Vorgehen bereits beschrieben.
10.2 - Laboraufgabe 9a: SPI-Bus-Modul
33
bool SPI1_SelectDevice(SPI1_Device Device)
Die Funktion SelectDevice() dient dazu, eine der am Bus angeschlossenen Komponenten zu aktivieren. Bevor dies erledigt werden kann, müssen jedoch unbedingt alle anderen Komponenten deaktiviert werden, da sie sonst Daten auswerten würden, die gar nicht für sie bestimmt sind. Die
Wahl der Komponente erfolgt über den Parameter „Device“, der nur die in spi.h definierten Werte
annehmen kann. Verwenden Sie in Ihrem Code die benannten Konstanten der dortigen Aufzählung, nicht deren numerische Entsprechungen.
Die Funktion soll vorerst immer TRUE zurückliefern.
10.3 LCD-Ansteuerung
Der SPI-Bus ist jetzt einsatzbereit. Es bedarf jedoch noch weiterer Schritte, bis das LCD tatsächlich angesprochen werden kann, denn der Controller auf dem LCD-Modul befindet sich nach dem
Einschalten in einem unbekannten Zustand. Er muss zunächst durch ein Reset in einen definierten
Zustand gebracht werden.
Die Reset-Leitung ist an Pin PA10 angeschlossen und arbeitet mit negativer Logik. Da ein GPIOPin nach der Konfiguration im Modus GPIO_Mode_Out_PP standardmäßig einen Pegel von 0V
aufweist, wird das Display demnach fortwährend zurückgesetzt. Also muss der Ausgangspegel angehoben werden, um den Reset zu beenden.
Danach nimmt der Controller die über den SPI-Bus übertragenen Daten entgegen und wertet sie
aus. Der Controller ist jedoch sehr flexibel gestaltet, um mit möglichst vielen LCDs arbeiten zu können. Die Anpassung an unsere Umgebung geschieht über eine einmalige Initialisierung, die in der
folgenden Tabelle beschrieben wird:
Abbildung 10.2: Initialisierung des LCD-Moduls
Die Tabelle ist fast unverändert aus dem Datenblatt entnommen. Sie gibt an, welche Einstellungen
vorzunehmen sind und mit welchen Befehlen diese umgesetzt werden können. Die Bits D0..7 sind
dabei nicht als Datenleitungen ausgeführt, sondern geben die einzelnen Bits eines Bytes an, das
über den Bus an das LCD geschickt wird. Zusammengefasst ergeben sie den hexadezimalen
Wert, der in der Spalte „Hex“ eingetragen ist. Diese Werte müssen also lediglich in der angegebenen Reihenfolge übertragen werden.
10.3 - LCD-Ansteuerung
34
Das Kürzel „C/D“ in der Tabelle steht für die „Command / Data“-Leitung. Ihr Pegel bestimmt, ob
das übertragene Byte ein Befehl an den Controller ist, oder ob es Daten sind, die in den internen
Speicher geschrieben werden sollen. Wie in der Tabelle ersichtlich, bedeutet ein „low“-Pegel, dass
die Daten als Befehl zu interpretieren sind.
Zu welchem Zeitpunkt der Datenübertragung das C/D-Signal vom Controller ausgewertet wird, ist
aus dem im Datenblatt vorhandenen Zeitdiagramm ersichtlich:
Abbildung 10.3: Zeitdiagramm der Datenübertragung zum LCD-Controller
Der Pegel wird also vor Übertragung eines Bytes ausgewertet und ist zu allen anderen Zeitpunkten
beliebig. Daraus folgt für die Ansteuerung, dass die C/D-Leitung auf den gewünschten Pegel gesetzt werden muss, bevor das Daten- oder Befehlsbyte an die SPI-Schnittstelle des STM32 übergeben wird. Danach sollte die C/D-Leitung auf low gesetzt werden, um einen definierten Standardzustand herzustellen.
Ist die Initialisierung erfolgt, so ist das LCD-Modul vollständig einsatzbereit. Zum Verständnis der
Ansteuerung des Moduls wird nachfolgend sein interner Aufbau besprochen:
Das Modul ist mit einem Display ausgestattet, das 128x64 Pixel besitzt. Der Controller des Moduls
verfügt über einen kleinen Speicher, um den Displayinhalt verwalten zu können. Dieser ist jedoch
nicht pixelweise ansprechbar. Vielmehr lassen sich nur Gruppen zu acht Pixeln ansprechen:
Die Pixel sind dabei in der Horizontalen auf 128 Spalten (columns) verteilt, während die Pixel auf der vertikalen Achse in 8 Speicherseiten (pages) mit jeweils 8
Pixeln aufgeteilt sind.
Die Werte der acht vertikalen Pixel einer Seite werden
dabei in einem Byte abgelegt - jede Seite beansprucht
also 128*1 Byte = 128 Bytes an Speicher.
Abbildung
10.4:
Speicherorganisation
LCD-
Diese Aufteilung bereitet dann Probleme, wenn Displayinhalte erst nach und nach erzeugt werden: Weiß
das Programm von der Struktur des Displayspeichers
nichts, so werden mit der Übertragung eines Bytes
möglicherweise sieben Pixel gelöscht, um ein einzelnes zu setzen. Dem kann mit Hilfe eines Framebuffers
begegnet werden, was aber nicht Thema dieses Abschnittes ist.
Zur Verdeutlichung des Aufbaus einer einzelnen Seite dient Abbildung 10.5, die die Spalten 0 bis 8
einer Zeichenkette illustriert, die in einer Speicherseite dargestellt ist. Man erkennt, wie sich die
Werte eines Bytes zusammensetzen: Das Byte in Spalte 0 hat beispielsweise den Wert 38
(2+4+32), das in Spalte 1 den Wert 73 (1+8+64). Würden also die Werte 38 und 73 in den Speicher des LCD-Controllers übertragen, würden die ersten beiden Spalten dargestellt.
10.3 - LCD-Ansteuerung
35
Abbildung 10.5: Aufbau einer einzelnen Speicherseite des LCDModuls
Damit die Werte im Speicher auch an die richtige Stelle geschrieben werden, muss im Normalfall
die Seite und Spalte gesetzt werden, in der sich das zu schreibende Byte befindet. Hierzu dienen
zwei Befehle, die in Abschnitt 10.4.4 gelistet sind. Schreibt man eine Funktion, die die Spalte setzt,
die Seite setzt und dann das Datenbyte überträgt, so lässt sich darauf aufbauend eine Funktion
schreiben, die Text auf dem Display ausgeben kann.
Eine Funktion zur Textausgabe ist aufgrund der Komplexität des Schrift-Datenformats bereits vorhanden, es ist nun jedoch Ihre Aufgabe, die noch fehlenden Funktionen zu codieren, auf denen der
bereits geschriebene Code aufbaut.
10.4 Laboraufgabe 9b: LCD-Ansteuerung
10.4.1 Aufgabenstellung
1) Folgen Sie den Anweisungen des Abschnitts 4 auf Seite 10.
2) Erzeugen Sie ein neues Projekt in HiTOP.
3) Übernehmen Sie die Dateien spi.c und spi.h aus Ihrer Lösung zu Laborübung 4a in das neue
Projekt und fügen Sie sie zum Projekt hinzu.
4) Kopieren Sie die Dateien lcd.c, lcd.h, lcd_drawing.c, lcd_drawing.h und lcd_fonts.c in Ihr
Projekt und fügen Sie diese zu den Dateikategorien „Source Files“ bzw. „Header Files“ des
Projekts hinzu.
5) Versehen Sie die Funktionen in lcd.c mit Inhalt, der die in Abschnitt 10.4.2 erläuterten Aufgaben
erfüllt. Die Prototypen in lcd.h dürfen dabei nicht verändert werden. Erstellen Sie für jede zu erstellende Funktion ein Struktogramm, einen PAP oder ein passendes UML-Diagramm, bevor
Sie mit der Codierung beginnen.
6) Lassen Sie in der Funktion main() Text auf dem LCD ausgeben. Denken Sie daran, dass vor
der Ansteuerung des LCDs SPI1 zu initialisieren ist.
10.4 - Laboraufgabe 9b: LCD-Ansteuerung
36
10.4.2 Schnittstelle von lcd.h
void LCD_Init()
Diese Funktion initialisiert zuerst die für das LCD verwendeten GPIOs als Ausgänge. Es sind hierfür die drei Konstanten LCD_Backlight, LCD_NRST und LCD_CD zu benutzen (siehe Tabelle).
Anschließend wird, wie weiter oben beschrieben, der kontinuierliche Reset des LCD-Controllers
beendet und mittels SPI1_SelectDevice() das LCD auf dem Bus aktiviert. Ist dies erfolgt, so wird
die in Abbildung 10.2 dargestellte Initialisierungssequenz übertragen. Schließlich wird mittels
LCD_Clear() der Speicherinhalt des Controllers gelöscht.
Konstante
Port
Aufgabe
Logik
LCD_NRST
GPIOA
Reset LCD-Controller
negativ
LCD_CD
GPIOA
Command/Data-Signal
positiv
LCD_Backlight
GPIOC
LCD-Hintergrundbel.
positiv
void LCD_SetBacklightState(bool State)
Aufgabe dieser Funktion ist es, die Hintergrundbeleuchtung des LCDs an- bzw. auszuschalten, je
nach Wert des Parameters.
void LCD_SetPageData(u8 X, u8 Page, u8 Data)
Die Übertragung eines Bytes in den Displayspeicher des Moduls erfolgt mit dieser Funktion. Sie
wählt die Spalte und Seite, in die das Byte geschrieben werden soll, setzt die C/D-Leitung auf 1,
überträgt das Byte an den LCD-Controller und setzt dann die C/D-Leitung wieder auf 0 zurück.
void LCD_Clear()
In dieser Funktion wird der gesamte Speicherinhalt des LCD-Controllers mit Nullen gefüllt. Es werden somit alle Pixel des Displays ausgeschaltet. Dazu müssen in jede der 8 Seiten des Controllers
128 Nullbytes geschrieben werden. Sie können dazu die Funktion LCD_SetPageData() verwenden, wenn Sie möchten.
Sie können allerdings auch von der Eigenschaft des Controllers Gebrauch machen, die gewählte
Spalte nach jedem erfolgten Schreibzugriff auf den Speicher um eins zu erhöhen. Dadurch entfällt
die Notwendigkeit, die Spalte vor jedem Nullbyte neu zu setzen. Es reicht somit aus, nach der
Wahl der Speicherseite die Spalte auf Null zu setzen und 128 Nullbytes zu übertragen, um die Seite zu füllen. Vergessen Sie jedoch nicht, dass bei dieser Variante die C/D-Leitung entsprechend zu
setzen ist.
10.4.3 Schnittstelle von lcd_drawing.h
void LCD_Print(u8 X, u8 Page, char* String, const u8 Font[])
Diese Funktion ermöglicht die Ausgabe von Strings auf dem LCD. Es stehen dabei vier verschiedene Schriftarten zur Verfügung, die in Abschnitt 10.4.5 dargestellt sind.
void LCD_DrawLine(u8 XStart, u8 YStart, u8 XEnd, u8 YEnd)
Mit Hilfe von LCD_DrawLine() kann man Linien auf dem LCD ausgeben lassen. Der Anfangspunkt
wird dabei durch die Koordinaten (XStart,YStart) bestimmt, während der Endpunkt durch
(XEnd,YEnd) beschrieben wird.
10.4 - Laboraufgabe 9b: LCD-Ansteuerung
37
void LCD_DrawCircle(u8 X, u8 Y, u8 Radius)
Analog zur vorhergehenden Funktion lassen sich mit LCD_DrawCircle() Kreise darstellen. Die Parameter X und Y geben dabei den Mittelpunkt an.
10.4.4 Wichtige Befehle des LCD-Controllers
Befehl
Befehlscode
C/D
Funktion
D7 D6 D5 D4 D3 D2 D1 D0
1
Display on/off
Page address set
0
Column address set
0
Display data write
1
Display normal/reverse
0
1
0
1
1
1
0
LCD aus
1
LCD an
0
A2 A1 A0 Speicherseite A wählen (0..7)
1
0
1
1
0
0
0
1
A7 A6 A5 A4 Spalte A wählen (0..127), obere 4 Bits
0
A3 A2 A1 A0 Spalte A wählen (0..127), untere 4 Bits
A7 A6 A5 A4 A3 A2 A1 A0 Schreibt A in die gewählte Speicherstelle
1
1
Display all points on/off
0
0
0
0
1
1
0
0
0
0
1
1
1
0
0
Alle Pixel werden normal dargestellt
1
Alle Pixel werden invertiert dargestellt
0
Normaldarstellung
1
Alle Pixel werden eingeschaltet
0
10.4 - Laboraufgabe 9b: LCD-Ansteuerung
10.4.5 Schriftarten
Abbildung 10.6: Schriftart LCD_Font_6x7int
Abbildung 10.7: Schriftart LCD_Font_11x14
38
10.4 - Laboraufgabe 9b: LCD-Ansteuerung
Abbildung 10.8: Schriftart LCD_Font_6x7lcd
Abbildung 10.9: Schriftart LCD_Font_21x28
39
10.4 - Laboraufgabe 9b: LCD-Ansteuerung
Verweise
[Thumb2]: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.qrc/index.html
[STM32DS]: http://www.st.com/stonline/products/literature/ds/13587.pdf
[UMLDiag]: http://de.wikipedia.org/wiki/UML#Darstellung_in_Diagrammen
[UMLTools]: http://de.wikipedia.org/wiki/UML-Werkzeug#Programme
[STM32Files]: http://www.st.com/mcu/familiesdocs-110.html
[STM32LibDoc]: http://www.st.com/stonline/products/literature/um/13475.pdf
[PSSchem]: http://www.hitex.com/fileadmin/free/stm32-performancestick/stm32-stick-schematic.pdf
40