Um die C/C++ für Mikrocontroller verwenden zu können ist ein hardware-spezifischer HAL notwendig. Falls für die Zielplattform kein HAL verfügbar ist, kann mittels dieser Anleitung ein eigener geschrieben werden.
Bemerkung
Falls Du einen HAL für eine neue Hardware-Plattform geschrieben hast und es möglich ist diesen unter der CC0-Lizenz zu veröffentlichen, bitten wir um eine Mail an info@tinkerforge.com. Wir haben immer Interesse daran, HALs für weitere Plattformen in die Bindings aufzunehmen.
Um die Bindings auszuführen, wird ein Mikrocontroller benötigt, der in etwa vergleichbar zum, oder besser als der ATmega328 (Arduino Uno) ist. Der Bindings-Code benötigt mindestens 2k RAM und 16k Flash. Außerdem muss die Host-Hardware SPI mit mindestens 400 kHz kommunizieren können. Für maximale Performance wird eine SPI-Taktfrequenz von 2 MHz empfohlen.
Ein HAL ist für die Initialisierung und Kontrolle der SPI-Hardware der Zielplattform verantwortlich. Außerdem abstrahiert er andere hardwarespezifische Funktionen wie Zeitmessung und Logging.
Um einen HAL zu implementieren müssen die folgenden Schritte durchgeführt werden:
TF_HAL
- und TF_Port
-Strukturentf_hal_create
und tf_hal_destroy
-Funktionen für die definierte StrukturDie folgenden Funktionen aus bindings/hal_common.h
werden verwendet:
tf_hal_common_create
(TF_HAL *hal)¶Initialisiert die TF_HALCommon
-Instanz die zum übergebenen TF_HAL
gehört.
Diese Funktion muss bei der HAL-Initialisierung so früh wie möglich aufgerufen werden.
tf_hal_common_prepare
(TF_HAL *hal, uint8_t port_count, uint32_t port_discovery_timeout_us)¶Schließt die Initialisierung der TF_HALCommon
-Instanz, die zum übergebenen TF_HAL
gehört,
ab. Das ist typischerweise der letzte Schritt der HAL-Initialisierung. SPI-Kommunikation muss hier bereits
möglich sein. Diese Funktion erwartet die Anzahl verwendbarer Ports, sowie einen Timeout in Mikrosekunden,
der angibt, wie lange die Bindings versuchen sollen, ein Gerät an einem der Ports zu erreichen.
tf_hal_common_prepare()
baut dann eine Liste der erreichbaren Geräte und speichert diese in der TF_HALCommon
-Instanz.
TF_HAL
-Struktur¶Die TF_HAL
-Struktur hält alle Informationen, die für die SPI-Kommunikation
notwendig sind. Sie speichert außerdem eine Instanz von TF_HALCommon
,
einem internen Typen, der pro HAL-Instanz einmal von den Bindings benötigt wird,
sowie einen Pointer auf ein Array von Port-Mapping-Informationen (TF_Port
).
Die TF_Port
-Struktur kann komplett auf den HAL angepasst werden,
um zum Beispiel den Chip-Select-Pin eines Ports, sowie dessen Namen, zu verwendende
SPI-Einheit usw. abzuspeichern. Beispiele finden sich in hal_arduino_esp32/hal_arduino_esp32.h
und hal_linux/hal_linux.h
.
Bricklets werden anhand ihrer UID und des Ports unter dem sie erreichbar sind identifiziert.
Ein Port mappt typischerweise auf den Chip-Select-Pin der gesetzt werden muss, damit Daten über SPI
an das Bricklet übertragen werden. Manche HAL-Funktionen bekommen eine Port-ID übergeben.
Diese ist typischerweise der Index in ein TF_Port
-Array.
tf_hal_create
und tf_hal_destroy
-Funktionen¶Nachdem die TF_HAL
-Funktion definiert wurde, muss deren Initialisierungsfunktion
tf_hal_create
implementiert werden. Sie hat folgende Aufgaben:
TF_HAL
-Struktur in einen definierten Zustand bringenTF_HALCommon
-Instanz mit tf_hal_common_create()
initialisierentf_hal_common_prepare()
.
Das ist typischerweise der letzte Initialisierungsschritt. SPI-Kommunikation muss hier möglich sein.Nach Konvention gibt tf_hal_create
einen int zurück, der bei Erfolg auf TF_E_OK
gesetzt ist. Falls die Initialisierung fehlschlägt, kann ein anderer Fehlercode aus
bindings/errors.h
zurückgegeben werden. Es ist außerdem möglich eigene Fehlercodes
für den HAL in dessen Header zu definieren. Die Fehlercodes von -99 bis -1 sind allerdings für die
Bindings reserviert. Der erste valide Fehlercode ist also -100.
Nachdem tf_hal_create
implementiert wurde, kann jetzt tf_hal_destroy
implementiert werden.
Es sollte möglich sein, einen HAL mit tf_hal_create
zu erstellen, zu verwenden,
ihn dann mit tf_hal_destroy
zu zerstören und danach mit tf_hal_create
wieder zu erstellen.
Der neu erstellte HAL muss dann wieder funktionsfähig sein.
Als letzter Schritt müssen die folgenden Funktionen implementiert werden,
die in bindings/hal_common.h
zwischen
// BEGIN - To be implemented by the specific HAL
und
// END - To be implemented by the specific HAL
definiert sind.
Alle Funktionen, die einen int zurückgeben, sollten TF_E_OK
zurückgeben, wenn
kein Fehler aufgetreten ist.
tf_hal_chip_select
(TF_HAL *hal, uint8_t port_id, bool enable)¶Wenn enable
true ist, wählt diese Funktion den Port mit der übergebenen ID für die folgende
SPI-Kommunikation aus. Wenn enable
false ist, wird der Port nicht mehr ausgewählt.
Abhängig von der Plattform müssen hier mehrere Schritte durchgeführt werden.
Zum Beispiel muss auf einem Arduino begin/endTransaction
aufgerufen werden
um sicherzustellen, dass die SPI-Konfiguration angewendet wird.
Die Bindings stellen sicher, dass immer nur ein Port gleichzeitig ausgewählt wird.
tf_hal_transceive
(TF_HAL *hal, uint8_t port_id, const uint8_t *write_buffer, uint8_t *read_buffer, uint32_t length)¶Überträgt length
Bytes an Daten aus dem write_buffer
zum Bricklet und empfängt währenddessen
die selbe Menge an Bytes vom Bricklet in den read_buffer
(da SPI bidirektional ist). Die übergebenen
Buffer sind immer groß genug um length
Bytes zu lesen oder zu schreiben.
Diese Funktion wird nur aufgerufen, wenn zuvor tf_hal_chip_select()
mit der selben Port-ID
und enable=true
aufgerufen wurde.
Falls die Zielplattform DMA unterstützt, kann hier ein Transfer initiiert werden, es muss aber blockiert werden bis die Daten übertragen wurden.
Falls die Zielplattform kooperatives Multitasking unterstützt, kann, nachdem ein Transfer initiiert wurde,
yield
o.Ä. aufgerufen werden. Um sicherzustellen, dass während die Bindings während des Transfers nicht
verwendet werden, sollten sie wie folgt gesperrt werden:
TF_HALCommon *common = tf_hal_get_common(hal);
common->locked = true
Nachdem der Transfer abgeschlossen ist, sollten die Bindings wieder entsperrt werden, damit sie weiter verwendet werden können.
tf_hal_current_time_us
(TF_HAL *hal)¶Gibt die aktuelle Zeit in Mikrosekunden zurück. Diese Zeit muss keine Relation zu einer "echten" Zeit haben, aber monoton außer bei Überläufen sein.
tf_hal_sleep_us
(TF_HAL *hal, uint32_t us)¶Blockiert für die übergebene Zeit in Mikrosekunden. Falls die Plattform kooperatives
Multitasking unterstützt, können die Bindings hier gesperrt und danach durch yield
pausiert werden. Siehe tf_hal_transceive()
für Details.
tf_hal_get_common
(TF_HAL *hal)¶Gibt die TF_HALCommon
-Instanz zurück, die zum übergebenen TF_HAL
gehört.
tf_hal_get_port_name
(TF_HAL *hal, uint8_t port_id)¶Gibt den Port-Namen (typischerweise ein Buchstabe zwischen 'A' and 'Z') für die übergebene Port-ID zurück.
Der Name wird in get_identity
-Rückgaben eingefügt, falls das Gerät direkt mit dem Host
verbunden ist.
tf_hal_log_message
(const char *msg, size_t len)¶Loggt die übergebene Nachricht. Die Nachricht hat eine Länge von len
und ist nicht null-terminiert.
Abhängig von der Plattform kann hier z.B. eine serielle Konsole (Arduino) oder die Standardausgabe (Linux)
verwendet werden. Es kann auch in eine Log-Datei geschrieben werden.
tf_hal_log_newline
()¶Loggt das/die plattformspezifischen Zeilenumbruchszeichen.
tf_hal_strerror
(int e_code)¶Gibt eine Fehlerbeschreibung für den übergebenen Fehlercode zurück.
Um so platzeffizient wie möglich zu sein, kann diese Funktion komplett entfernt werden,
falls TF_IMPLEMENT_STRERROR
nicht in bindings/config.h
definiert ist.
Fehlercodes die von den Bindings verwendet werden können durch Einbinden von bindings/error_cases.h
behandelt werden.
Zur Implementierung kann die folgende Vorlage verwendet werden:
#if TF_IMPLEMENT_STRERROR != 0
const char *tf_hal_strerror(int e_code) {
switch(e_code) {
#include "../bindings/error_cases.h"
/* Add HAL specific error codes here, for example:
case TF_E_OPEN_GPIO_FAILED:
return "failed to open GPIO";
*/
default:
return "unknown error";
}
}
#endif
tf_hal_get_port_name
(TF_HAL *hal, uint8_t port_id)Gibt den 1-Zeichen Namen zurück, der zur übergebenen Port ID gehört.
tf_hal_get_port_common
(TF_HAL *hal, uint8_t port_id)¶Gibt die TF_PortCommon
-Instanz zurück, die zur übergebenen Port ID gehört.
Die Kommunikation zwischen dem Host und den Bricks/Bricklets verwendet SPI Modus 3:
Daten werden mit dem MSB (most significant bit) zuerst übertragen. Die Standardtaktfrequenz ist 1,4 MHz, Bricks und Bricklets unterstützen aber Taktfrequenzen zwischen 400 kHz und 2 MHz. Der Logikpegel aller Signale beträgt 3,3V.
Aufgrund eines Bugs des auf den Bricklets verwendeten XMC-Mikrocontrollers von Infineon trennt das Bricklets sich nicht korrekt vom SPI-Bus, wenn das Chip-Select-Signal deaktiviert wird. Es treibt dann weiterhin auf MISO einen Wert, was dazu führt, dass sich mehrere Bricklets am selben SPI-Bus gegenseitig stören. Falls mehrere Bricklets eingesetzt werden sollen, müssen deshalb vom Chip-Select-Signal kontrollierte Trenner-Chips eingesetzt werden.