Blinki
Blinky ist das “Hallo Welt” Beispiel Programm der Embedded Welt. Die meisten Entwicklungsboards haben nicht unbedingt eine Anzeige um darauf etwas darzustellen und eine solche anzusteuern ist ja auch schon recht komplex. Viel einfacher und oft vorhanden ist hingegen eine Leuchtdiode - und diese kann man blinken lassen.
Programm Dateien
Wir schauen uns die Dateien an welche im Verzeichniss <prog/blinky> zu finden sind.
prog/blinky
├── CMakeLists.txt
├── prj.conf
└── src
└── main.c
CMakeLists.txt
1cmake_minimum_required(VERSION 3.20.0)
2find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
3project(blinky)
4target_sources(app PRIVATE src/main.c)
Diese Datei, welche vom cmake
Build System gelesen wird beschreibt den
Namen des Projektes und wo sich die Quellen des Projektes, hier src/main.c
befinden.
prj.conf
1CONFIG_GPIO=y
Die Projektkonfigurationsdatei gibt an, welche Komponenten des Kernels benötigt werden.
Diese werden dann mit in die Firmware eingebunden.
Hier benötigen wir das GPIO
- General Purpose In/Out Subsystem.
main.c
main.c
ist die Hauptdatei unseres Programmes, welche in der Sprache C geschrieben ist.
1/*
2 * Copyright (c) 2016 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
Dies ist der Urheberrechtshinweis. In C beginnt der Schrägstrich und der Stern /*
einen Kommentar,
welcher durch ein entsprechenden Schrägstrich und Stern */
wieder geschlossen wird.
Diese Art von Kommentar kann sich über mehrere Zeilen erstrecken,
während ein //
Doppelschrägstrich Platz für Kommentare bis zum Ende der Zeile gibt.
1#include <zephyr.h>
2#include <drivers/gpio.h>
Alle Schlüsselwörter in C, die mit #
beginnen, sind Präprozessor
Direktiven. #include <file.h>
lädt eine Header-Datei, welche die
Schnittstelle von Deklarationen enthält die wir verwenden möchten.
Wenn <
und >
zur Begrenzung des File Namens verwendet werden,
dann werden die dem Compiler übergebenen Such Pfade verwendet um die Datei
zu finden. Wird die Datei im Gegensatz dazu mit "
umgeben, dann ist
der Pfad relativ zu der Datei, welche die Include - Direktive enhält.
1/* 1000 msec = 1 sec */
2#define SLEEP_TIME_MS 1000
3
4/* The devicetree node identifier for the "led0" alias. */
5#define LED0_NODE DT_ALIAS(led0)
Auch hier haben wir eine Präprozessordirektive. #define PLATZHALTER wert
erzeugt ein
Makro, welches alle Vorkommen von PLATZHALTER
durch den angegebenen wert
ersetzt.
Normalerweise werden die Platzhalter mit Großbuchstaben geschrieben, dies ist jedoch nicht
zwingend erforderlich. Es wird so oft gemacht um auf diese Weise die Makros von Funktionen
und Variablen zu unterscheiden, welche oft mit Kleinbuchstaben geschrieben werden.
Es können auch Makros definiert werden, welche Argumente haben. Hier verwenden wir ein solches
Makro DT_ALIAS
mit dem Argument led0
als Wert für unser Makro LED0_NODE
.
led0
ist ein Alias, der für das Board was wir verwenden angibt, an welchem Port und Pin
die LED 0 (LED - light emiting diode - Leuchtdiode) angeschlossen ist.
1/*
2 * A build error on this line means your board is unsupported.
3 * See the sample documentation for information on how to fix this.
4 */
5static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
Diese C-Deklaration deklariert einen Bezeichner led0
für einen fixen (static
)
Bereich des “nur lese” (read only - const
) Speicherbereiches,
der strukturierte Daten (struct
) der Form gpio_dt_spec
aufnimmt
und mit dem Ergebnis des Makroaufrufs
GPIO_DT_SPEC_GET(LED0_NODE, gpios)
initialisiert wird.
1void main(void)
2{
Hier definieren wir unsere Hauptroutine des Programms, void main(void)
ist der so genannte Einstiegspunkt,
der aufgerufen wird, um die Anwendung zu starten.
void
bezeichnet einen Nullwert, daher wird diese
Funktion keinen Wert zurückgeben und auch kein Argument haben.
Das Schreiben von void main()
ist
gleichbedeutend mit der vorherigen Form mit einem void
Argument.
Der Körper der Funktion wird eingeschlossen
in die geschweiften Klammern {}
.
1int ret;
Diese Zeile besagt, dass wir Speicher für die Aufnahme eines ganzzahligen (Integer - int
)
Wertes benötigen, auf den mit dem Namen ret
zugegriffen werden kann.
Dies ist ein uninitialisierter
Speicherplatz, d.h. es wurde bisher kein bestimmter Wert dorthin geschrieben.
1if (!device_is_ready(led.port)) {
2 return;
3}
Hier haben wir eine Bedingungsklausel namens if
. Wenn die Bedingung in
der Klammer wahr ist, dann werden die Anweisungen in den geschweiften Klammern
ausgeführt.
Bedingte Klauseln werden auf Seite 79 in “Die Programmiersprache C”
beschrieben.
Zephyr folgt mindestens der C99-Spezifikation in welcher die Unterstützung
von booleschen Werten mit dem Typ bool
in die Sprache aufgenommen hat.
(Siehe C-Sprachunterstützung)
Das Ausrufezeichen !
ist ein Negationsoperator welcher bewirkt dass,
wenn die Funktion device_is_ready
false zurückgibt, was bedeutet, dass das
Gerät nicht einsatzbereit ist, die direkt folgende Anweisung (hier
der Block { return; }
ausgeführt wird.
Siehe hier
für die Beschreibung der device_is_ready
Funktion.
Mit dem Aufruf von return
wird unsere main
Funktion verlassen,
also unser Programm beendet.
Das heißt, falls das Gerät nicht korrekt initialisiert werden konnte,
wird unser Programm hier beendet.
1ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
2if (ret < 0) {
3 return;
4}
Aufruf der Funktion gpio_pin_configure_dt
und abspeichern des Integer-Ergebnisses in
der Variablen ret
.
Ein Blick in die Dokumentation von gpio_pin_configure_dt
in
GPIO Driver APIs
zeigt, dass die Funktion bei Erfolg Null und bei Mißerfolg negative
Werte zurückgibt, welche die aufgetretenen Fehlerbedingungen beschreiben.
GPIO_OUTPUT_ACTIVE
konfiguriert den angegebenen GPIO-Pin als Ausgang und
initialisiert ihn auf eine logische 1.
Auch hier wird das Programm durch den Aufruf von return
beendet,
falls ein Fehler aufgetreten sein sollte.
1while (1) {
2 ret = gpio_pin_toggle_dt(&led);
3 if (ret < 0) {
4 return;
5 }
6 k_msleep(SLEEP_TIME_MS);
7}
Das Schlüsselwort while
beschreibt eine ‘while’-Schleife,
solange die Bedingung in den geschweiften Klammern ( 1 )
wahr ist ( d.h. nicht Null ist ), wird die folgende Anweisung, { ... }
ausgeführt werden.
Hier ist die Bedingung immer wahr, also auf den ersten Blick eine nie endende
Schleife programmiert worden. Aber wenn wir weiter schauen dann sehen wir,
dass wenn gpio_pin_toggle_dt
eine Fehlerbedingung zurückgibt (ret < 0
),
dann wird return
aufgerufen, was zum
die Beenden der main
Funktion führt.
Der Aufruf von gpio_pin_toggle_dt
verändert den Zustand des Pins
zwischen high und low – die LED wird ein oder ausgeschaltet.
k_msleep
versetzt den aktuellen Thread für die in Millisekunden angegebene
Zeit in den Schlaf.
Das bedeutet, dass die Ausführung für die angegebene Zeit angehalten wird.
Danach wird der Pin wieder umgeschaltet werden und so weiter.
1}
Die abschließende Klammer den Körper der main
Funktionsdeklaration.
Hier ist das vollständige Programms:
1/*
2 * Copyright (c) 2016 Intel Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6#include <zephyr.h>
7#include <drivers/gpio.h>
8/* 1000 msec = 1 sec */
9#define SLEEP_TIME_MS 1000
10
11/* The devicetree node identifier for the "led0" alias. */
12#define LED0_NODE DT_ALIAS(led0)
13/*
14 * A build error on this line means your board is unsupported.
15 * See the sample documentation for information on how to fix this.
16 */
17static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
18void main(void)
19{
20 int ret;
21 if (!device_is_ready(led.port)) {
22 return;
23 }
24 ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
25 if (ret < 0) {
26 return;
27 }
28 while (1) {
29 ret = gpio_pin_toggle_dt(&led);
30 if (ret < 0) {
31 return;
32 }
33 k_msleep(SLEEP_TIME_MS);
34 }
35}