Laborpraktikum Embedded Systems SS 2015 Stand: 02.06.2015 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. Zur Organisation des Laborpraktikums: Für das Programmieren der Mikrocontroller-Boards (STM32 PerformanceStick) wird die Entwicklungsumgebung „HiTOP“ der Firma HiTex benötigt. Diese steht als kostenlose Demo-Version zur Verfügung, die jedoch nur unter Windows 32 Bit lauffähig ist. Virtueller Desktop Auf den PCs in den Rechnerräumen ist die Software „VMware View Client“ installiert, die den Desktop eines virtuellen PCs mit Windows 7 (32 Bit) und vorinstallierter HiTOP Entwicklungsumgebung zur Verfügung stellt. Starten Sie den virtuellen Desktop über das Desktop-Icon! Als „Verbindungsserver“ sollte view-ext.thm.de eingestellt sein. Geben Sie als Benutzernamen „iem_user“ ein und als Kennwort „Test123“ Wählen Sie anschließend den virtuellen Desktop „Embedded Systems“ 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! ViewClient auf einem eigenen PC installieren Sie können den VMware View Client auch auf Ihrem privaten PC/Laptop installieren, um die virtuelle Maschine mit der HiTOP Entwicklungsumgebung zu nutzen. Die Installationsdateien für Windows, Mac OS und Linux finden Sie unter: https://view-ext.thm.de Alternativ können Sie auch die Entwicklungsumgebung HiTOP direkt auf Ihrem eigenen PC installieren, wenn Sie ein 32 Bit Betriebssystem verwenden (oder eine entsprechende virtuelle Maschine eingerichtet haben). Sie finden den Download-Link dazu in Moodle. Zusätzlich benötigen Sie dann die Template-Dateien für die Übungsaufgaben, die ebenfalls in Moodle eingestellt sind. Weitere Hinweise: Unterlassen Sie bitte das Kopieren von Programmen anderer Gruppen, denn ein Lernerfolg ist nur durch eigenständige Arbeit möglich. 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 hard-warespezifischen Bibliothek eine Binärdatei erzeugt, die der Zielprozessor ausführen kann. • Ein Debugger, der mittels der vom Compiler/Linker erzeugten Debuginformationen den Programmverlauf auf dem Prozessor mit Verweisen auf den C/C++-Quellcode nachvollziehbar macht. Darüberhinaus 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 Teil-aufgaben 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 Pro-blemstellung überschaubare, kurze Programmteile (z.B. C-Funktionen) entstehen. Programm-Module sollen nur einen Eingang und einen Ausgang haben. Jedes Modul soll eine ab-geschlossene 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_Up 2 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 In terrupt-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 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 wer den 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
© Copyright 2024 ExpyDoc