C/C++ für Mikrocontroller - Arduino HAL

Der Arduino Hardware Abstraction Layer (HAL) wird mit den C/C++ Bindings für Mikrocontroller verwendet um mit Bricklets über SPI zu kommunizieren.

Unterstützte Hardware

Dieser HAL wurde mit den folgenden Boards getestet:

  • Arduino Uno (benötigt einen Pegelwandler)
  • Arduino MEGA (benötigt einen Pegelwandler)
  • Teensy 3.1

Der HAL sollte aber mit allen Arduino-kompatiblen Boards funktionieren die zum Beispiel hier aufgelistet sind. Eine weniger vollständige deutsche Liste findet sich hier.

Bemerkung

Ein Pegelwandler ist für Geräte, die einen Logik-Pegel von 5V verwenden notwendig. Das betrifft unter anderem alle AVR-basierten Arduinos.

Bemerkung

Manche Arduinos haben nur eine kleine Menge Flash und RAM verfügbar. Siehe hier für einige Größenoptimierungen.

Test eines Beispiels

Die Beispiele sind für die Verwendung in der Arduino IDE gemacht. Daher muss diese zuerst entsprechend ihrer Installationsanleitung installiert werden. Danach in Arduino IDE das passende Board auswählen.

Dieser HAL beinhaltet einen Beispiel-Treiber, mit dem alle Beispiele, die den Bindings beigelegt sind, ausgeführt werden können.

Die Arduino IDE hat spezifische Anforderungen an das Order-Layout des Sketch-Ordners. Ein gültiges Layout sieht aus wie folgt:

  • example_driver/
    • example_driver.ino [aus dem hal_arduino-Ordner]
    • [hier die gewünschte Beispiel .c-Datei ablegen]
    • src/
      • bindings/
        • [hier den Inhalt des bindings-Ordners ablegen]
      • hal_arduino/
        • [hier hal_arduino.cpp und hal_arduino.h ablegen]

Der Hauptordner muss zwingend den selben Namen wie die Sketch-Datei haben. Wenn also example_driver.ino umbenannt werden soll, muss auch der Ordner umbenannt werden.

Als nächstes muss die Port-Zuweisung im Beispiel-Treiber auf den Aufbau angepasst werden. (siehe dieser Abschnitt). Wenn mehrere Bricklets am selben SPI-Bus verbunden werden sollen (das ist nur mit einem Trenner-Chip möglich), müssen alle Chip-Select-Pins mit dem Arduino verbunden und in der Port-Zuweisung aufgeführt werden, selbst wenn noch keine Kommunikation mit den Bricklets gewünscht ist. Das stellt sicher, dass die Signale korrekt getrennt werden.

Jetzt das Board mit dem PC verbinden und den Sketch bauen und hochladen. Die Ausgaben des Beispiels sollten in der seriellen Konsole zu sehen sein.

Port-Spezifikations-Format

Ein Port wird als Instanz der TF_Port-Struktur spezifiziert:

struct TF_Port {
    int chip_select_pin;
    char port_name;
}

Der chip_select_pin ist der Pin, der gesetzt werden muss, um mit dem Port zu kommunizieren. Der port_name ist ein Zeichen, das den Port identifiziert. Der Name wird in die Ergebnisse von tf_[device]_get_identity aufrufen eingefügt, falls das Gerät direkt mit dem Host verbunden ist.

Im Beispiel-Treiber example_driver.c ist eine Beispiel-Port-Spezifikation enthalten.

API

Die meisten HAL-Funktionen geben einen Fehlercode (e_code) zurück.

Mögliche Fehlercodes sind (wie in errors.h definiert):

  • TF_E_OK = 0
  • TF_E_TIMEOUT = -1
  • TF_E_INVALID_PARAMETER = -2
  • TF_E_NOT_SUPPORTED = -3
  • TF_E_UNKNOWN_ERROR_CODE = -4
  • TF_E_STREAM_OUT_OF_SYNC = -5
  • TF_E_INVALID_CHAR_IN_UID = -6
  • TF_E_UID_TOO_LONG = -7
  • TF_E_UID_OVERFLOW = -8
  • TF_E_TOO_MANY_DEVICES = -9
  • TF_E_DEVICE_NOT_FOUND = -10
  • TF_E_WRONG_DEVICE_TYPE = -11
  • TF_E_LOCKED = -12
  • TF_E_PORT_NOT_FOUND = -13
  • TF_E_NULL = -14
  • TF_E_DEVICE_ALREADY_IN_USE = -15
  • TF_E_WRONG_RESPONSE_LENGTH = -16
  • TF_E_NOT_INITIALIZED = -17

Dieser HAL definiert keine weiteren Fehlercodes. Mit tf_hal_strerror() kann eine Fehlerbeschreibung zu einem Fehlercode abgefragt werden.

Grundfunktionen

int tf_hal_create(TF_HAL *hal, TF_Port *ports, uint8_t port_count)

Erstellt ein HAL-Objekt, das verwendet werden kann um die verfügbaren Geräte aufzulisten. Es wird außerdem für den Konstruktor von Bricks und Bricklets benötigt.

  • ports ist ein Array von Port-Spezifikationen, wie hier beschrieben.
  • port_count ist die Länge des ports-Array.
int tf_hal_destroy(TF_HAL *hal)

Zerstört den übergebenen TF_HAL.

void tf_hal_set_timeout(TF_HAL *hal, uint32_t timeout_us)

Setzt den Timeout in Mikrosekunden für Getter und Setter für die das Response-Expected-Flag gesetzt ist.

Der Standard-Timeout ist 2500000 (2,5 Sekunden).

uint32_t tf_hal_get_timeout(TF_HAL *hal)

Gibt den Timeout zurück, der von tf_hal_set_timeout() gesetzt wurde.

int tf_hal_get_device_info(TF_HAL *hal, uint16_t index, char ret_uid[7], char *ret_port_name, uint16_t *ret_device_id)

Gibt die UID, den Port und den Device-Identifier für das n-te (=index) gefundene Gerät zurück. Diese Funktion gibt TF_E_DEVICE_NOT_FOUND zurück, wenn der index größer oder gleich der Anzahl gefundener Geräte ist. Damit alle Geräte aufgelistet werden, kann diese Funktion in einer Schleife mit wachsendem index aufgerufen werden, bis einmal TF_E_DEVICE_NOT_FOUND zurückgegeben wird.

int tf_hal_callback_tick(TF_HAL *hal, uint32_t timeout_us)

Pollt auf allen Geräten mit registriertem Callback-Handler nach Callbacks. Blockiert für die übergebene Zeit in Mikrosekunden.

Diese Funktion kann nicht-blockierend verwendet werden, indem sie mit einem Timeout von 0 aufgerufen wird. Die Bindings pollen dann ein einziges Gerät nach einem Callback, indem sie ein Byte über SPI senden und empfangen. Falls kein Callback verfügbar ist, wird die Funktion sofort beendet. Wenn das Gerät beginnt ein Callback zu schicken, wird es empfangen, bestätigt und der Callback-Handler wird ausgeführt.

Diese Funktion pollt mit einem Round-Robin-Scheduler über mehrere Aufrufe. Das heißt dass selbst wenn immer mit einem Timeout von 0 gepollt wird, alle Geräte so fair wie möglich abgefragt werden.

bool tf_hal_deadline_elapsed(TF_HAL *hal, uint32_t deadline_us)

Gibt true zurück, wenn die übergebene Deadline in Mikrosekunden abgelaufen ist, ansonsten false. Robust gegen Überläufe bis zu UINT32_MAX / 2.

int tf_hal_get_error_counters(TF_HAL *hal, char port_name, uint32_t *ret_spitfp_error_count_checksum, uint32_t *ret_spitfp_error_count_frame, uint32_t *ret_tfp_error_count_frame, uint32_t *ret_tfp_error_count_unexpected)

Gibt die Fehlerzähler für den übergebenen Port zurück. Folgende Fehler werden gezählt:

  • spitfp_error_count_checksum: Empfangene SPITFP-Pakete die wegen falscher Checksumme ignoriert wurden
  • spitfp_error_count_frame: Empfangene SPITFP-Pakete mit invalider Länge
  • tfp_error_count_frame: Empfangene TFP-Pakete mit invalider Länge
  • tfp_error_count_unexpected: Empfangene TFP-Pakete die unerwartet waren, da sie auf unbekannte Anfragen antworten
void tf_hal_log_error(const char *format, ...)

Loggt einen Fehler, falls das Log-Level in bindings/config.h TF_LOG_LEVEL_ERROR oder höher ist. Unterstützt eine Teilmenge der normalen printf-Syntax. Siehe tf_hal_printf() für Details.

void tf_hal_log_info(const char *format, ...)

Loggt eine Information, falls das Log-Level in bindings/config.h TF_LOG_LEVEL_INFO oder höher ist. Unterstützt eine Teilmenge der normalen printf-Syntax. Siehe tf_hal_printf() für Details.

void tf_hal_log_debug(const char *format, ...)

Loggt eine Debug-Meldung, falls das Log-Level in bindings/config.h TF_LOG_LEVEL_DEBUG oder höher ist. Unterstützt eine Teilmenge der normalen printf-Syntax. Siehe tf_hal_printf() für Details.

void tf_hal_printf(const char *format, ...)

Diese Funktion ist eine minimalistische printf-Implementierung. Die folgenden Platzhalter werden unterstützt:

  • %[präfix]u: Eine vorzeichenlose Ganzzahl in Basis 10
  • %[präfix]d: EIne vorzeichenbehaftete Ganzzahl in Basis 10
  • %[präfix]b: Eine vorzeichenlose Ganzzahl in Basis 2
  • %[präfix]x and %[präfix]X: Eine vorzeichenlose Ganzzahl in Basis 16, in beiden Fällen mit Kleinbuchstaben.
  • %c: Ein einzelnes Zeichen
  • %s: Ein null-terminierter String
  • %%: Ein Prozentzeichen

Mit den Präfixen kann die Breite von Ganzzahlen kontrolliert werden. Valide Präfixe sind I8, I16, I32 und I64. Zum Beispiel kann %I16x als Platzhalter verwendet werden um eine 16-Bit Zahl hexadezimal auszugeben.

Padding, gruppierungen, l-Modifikatoren oder ähnliches, oder Floats werden nicht unterstützt.

Der Zeilenumbruch \n wird in den oder die plattformspezifischen Zeilenumbruchs-Zeichen übersetzt.

const char *tf_hal_strerror(int e_code)

Gibt eine Beschreibung für den übergebenen Fehlercode zurück.

const char *tf_get_device_display_name(uint16_t device_id)

Gibt den Anzeigenamen für den übergebenen Device-Identifier zurück.