Libcanard ist eine kompakte Implementierung des Cyphal/CAN-Protokollstapels in C99/C11 für hochintegrierte Echtzeit-Embedded-Systeme.
Cyphal ist ein offener, leichter Datenbusstandard, der für die zuverlässige Kommunikation innerhalb von Fahrzeugen in Luft- und Raumfahrt- und Roboteranwendungen über CAN-Bus, Ethernet und andere robuste Transportmittel entwickelt wurde.
Lesen Sie die Dokumente in libcanard/canard.h
.
Beispiele, Einsteiger und Tutorials finden Sie im Cyphal-Forum.
Wenn Sie einen Beitrag leisten möchten, lesen Sie bitte CONTRIBUTING.md
.
Die Bibliothek ist so konzipiert, dass sie sofort mit jeder herkömmlichen 8/16/32/64-Bit-Plattform verwendet werden kann, einschließlich tief eingebetteter Bare-Metal-Plattformen, sofern ein standardkonformer Compiler verfügbar ist. Die plattformspezifische Medien-IO-Schicht (Treiber) soll von der Anwendung bereitgestellt werden:
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
Das OpenCyphal-Entwicklungsteam verwaltet eine Sammlung verschiedener plattformspezifischer Komponenten in einem separaten Repository unter https://github.com/OpenCyphal/platform_special_components. Benutzern wird empfohlen, dieses Repository nach Treibern, Beispielen und anderen Teilen zu durchsuchen, die in der Zielanwendung wiederverwendet werden können, um den Entwurf der Medien-E/A-Schicht (Treiber) für die Anwendung zu beschleunigen.
Das Beispiel ergänzt die Dokumentation, ersetzt sie jedoch nicht.
Die Bibliothek erfordert einen deterministischen dynamischen Speicherzuweiser mit konstanter Komplexität. Wir könnten den Standard-C-Heap verwenden, aber die meisten Implementierungen sind nicht konstant komplex. Nehmen wir also an, dass wir stattdessen O1Heap verwenden. Wir benötigen grundlegende Wrapper:
static void * memAllocate ( CanardInstance * const canard , const size_t amount )
{
( void ) canard ;
return o1heapAllocate ( my_allocator , amount );
}
static void memFree ( CanardInstance * const canard , void * const pointer )
{
( void ) canard ;
o1heapFree ( my_allocator , pointer );
}
Initiieren Sie eine Bibliotheksinstanz:
CanardInstance canard = canardInit ( & memAllocate , & memFree );
canard . node_id = 42 ; // Defaults to anonymous; can be set up later at any point.
Um Übertragungen über das Netzwerk senden zu können, benötigen wir eine Übertragungswarteschlange pro redundanter CAN-Schnittstelle:
CanardTxQueue queue = canardTxInit ( 100 , // Limit the size of the queue at 100 frames.
CANARD_MTU_CAN_FD ); // Set MTU = 64 bytes. There is also CANARD_MTU_CAN_CLASSIC.
Veröffentlichen Sie eine Nachricht (Nachrichtenserialisierung nicht angezeigt):
static uint8_t my_message_transfer_id ; // Must be static or heap-allocated to retain state between calls.
const CanardTransferMetadata transfer_metadata = {
. priority = CanardPriorityNominal ,
. transfer_kind = CanardTransferKindMessage ,
. port_id = 1234 , // This is the subject-ID.
. remote_node_id = CANARD_NODE_ID_UNSET , // Messages cannot be unicast, so use UNSET.
. transfer_id = my_message_transfer_id ,
};
++ my_message_transfer_id ; // The transfer-ID shall be incremented after every transmission on this subject.
int32_t result = canardTxPush ( & queue , // Call this once per redundant CAN interface (queue).
& canard ,
tx_deadline_usec , // Zero if transmission deadline is not limited.
& transfer_metadata ,
47 , // Size of the message payload (see Nunavut transpiler).
"x2Dx00" "Sancho, it strikes me thou art in great fear." );
if ( result < 0 )
{
// An error has occurred: either an argument is invalid, the TX queue is full, or we've run out of memory.
// It is possible to statically prove that an out-of-memory will never occur for a given application if the
// heap is sized correctly; for background, refer to the Robson's Proof and the documentation for O1Heap.
}
Verwenden Sie Nunavut, um automatisch (De-)Serialisierungscode aus DSDL-Definitionen zu generieren.
Die aus der Nachrichtenübertragung generierten CAN-Frames werden nun in der queue
gespeichert. Wir müssen sie einzeln auswählen und übertragen lassen. Normalerweise sollte das folgende Fragment regelmäßig aufgerufen werden, um die CAN-Frames aus der priorisierten Übertragungswarteschlange (oder mehreren, wenn redundante Netzwerkschnittstellen verwendet werden) in den CAN-Treiber zu entladen:
for ( const CanardTxQueueItem * ti = NULL ; ( ti = canardTxPeek ( & queue )) != NULL ;) // Peek at the top of the queue.
{
if (( 0U == ti -> tx_deadline_usec ) || ( ti -> tx_deadline_usec > getCurrentMicroseconds ())) // Check the deadline.
{
if (! pleaseTransmit ( ti )) // Send the frame over this redundant CAN iface.
{
break ; // If the driver is busy, break and retry later.
}
}
// After the frame is transmitted or if it has timed out while waiting, pop it from the queue and deallocate:
canard . memory_free ( & canard , canardTxPop ( & queue , ti ));
}
Der Transferempfang erfolgt durch Einspeisung von Frames in die Transfer-Reassembly-Zustandsmaschine von einer der redundanten Schnittstellen. Aber zuerst müssen wir uns anmelden:
CanardRxSubscription heartbeat_subscription ;
( void ) canardRxSubscribe ( & canard , // Subscribe to messages uavcan.node.Heartbeat.
CanardTransferKindMessage ,
7509 , // The fixed Subject-ID of the Heartbeat message type (see DSDL definition).
16 , // The extent (the maximum possible payload size) provided by Nunavut.
CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC ,
& heartbeat_subscription );
CanardRxSubscription my_service_subscription ;
( void ) canardRxSubscribe ( & canard , // Subscribe to an arbitrary service response.
CanardTransferKindResponse , // Specify that we want service responses, not requests.
123 , // The Service-ID whose responses we will receive.
1024 , // The extent (see above).
CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC ,
& my_service_subscription );
Der „Umfang“ bezieht sich auf die minimale Speichermenge, die erforderlich ist, um eine serialisierte Darstellung einer kompatiblen Version des Datentyps zu speichern. oder mit anderen Worten, es ist die maximal mögliche Größe der empfangenen Objekte. Dieser Parameter wird vom Datentypautor zum Zeitpunkt der Datentypdefinition bestimmt. Sie ist in der Regel größer als die maximale Objektgröße, damit der Autor des Datentyps in zukünftigen Versionen des Typs mehr Felder einführen kann. MyMessage.1.0
kann beispielsweise eine maximale Größe von 100 Byte und einen Umfang von 200 Byte haben. Eine überarbeitete Version MyMessage.1.1
kann eine maximale Größe zwischen 0 und 200 Byte haben. Extent-Werte werden pro Datentyp von DSDL-Transcompilern wie Nunavut bereitgestellt.
In Libcanard verwenden wir den Begriff „Abonnement“ der Einfachheit halber nicht nur für Betreffzeilen (Nachrichten), sondern auch für Dienste.
Wir können uns zur Laufzeit beliebig oft anmelden und abmelden. Normalerweise würde eine eingebettete Anwendung jedoch einmal abonniert und mit ihr rollen. Okay, so erhalten wir Überweisungen:
CanardRxTransfer transfer ;
const int8_t result = canardRxAccept ( & canard ,
rx_timestamp_usec , // When the frame was received, in microseconds.
& received_frame , // The CAN frame received from the bus.
redundant_interface_index , // If the transport is not redundant, use 0.
& transfer ,
NULL );
if ( result < 0 )
{
// An error has occurred: either an argument is invalid or we've ran out of memory.
// It is possible to statically prove that an out-of-memory will never occur for a given application if
// the heap is sized correctly; for background, refer to the Robson's Proof and the documentation for O1Heap.
// Reception of an invalid frame is NOT an error.
}
else if ( result == 1 )
{
processReceivedTransfer ( redundant_interface_index , & transfer ); // A transfer has been received, process it.
canard . memory_free ( & canard , transfer . payload ); // Deallocate the dynamic memory afterwards.
}
else
{
// Nothing to do.
// The received frame is either invalid or it's a non-last frame of a multi-frame transfer.
// Reception of an invalid frame is NOT reported as an error because it is not an error.
}
Eine einfache API zum Generieren von CAN-Hardware-Akzeptanzfilterkonfigurationen wird ebenfalls bereitgestellt. Akzeptanzfilter werden in einem erweiterten 29-Bit-ID-+-Masken-Schema generiert und können verwendet werden, um die Anzahl irrelevanter Übertragungen, die in der Software verarbeitet werden, zu minimieren.
// Generate an acceptance filter to receive only uavcan.node.Heartbeat.1.0 messages (fixed port-ID 7509):
CanardFilter heartbeat_config = canardMakeFilterForSubject ( 7509 );
// And to receive only uavcan.register.Access.1.0 service transfers (fixed port-ID 384):
CanardFilter register_access_config = canardMakeFilterForService ( 384 , ins . node_id );
// You can also combine the two filter configurations into one (may also accept irrelevant messages).
// This allows consolidating a large set of configurations to fit the number of hardware filters.
// For more information on the optimal subset of configurations to consolidate to minimize wasted CPU,
// see the Cyphal specification.
CanardFilter combined_config =
canardConsolidateFilters ( & heartbeat_config , & register_access_config );
configureHardwareFilters ( combined_config . extended_can_id , combined_config . extended_mask );
Die vollständige API-Spezifikation finden Sie in der Dokumentation. Wenn Sie feststellen, dass die Beispiele unklar oder falsch sind, eröffnen Sie bitte ein Ticket.
canardRxGetSubscription
hinzugefügt.Update-Branding, da UAVCAN v1 in Cyphal umbenannt wird.
Verbessern Sie die MISRA-Konformität, indem Sie die Verwendung von Flex-Arrays entfernen: (#192).
Beheben Sie Abhängigkeitsprobleme in der Docker-Toolchain.
Abgesehen von der Umbenennung/Umbenennung gibt es in dieser Version keine API-Änderungen: CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
_canard_cavl.h
(#196) umbenennen. Dedizierte Übertragungswarteschlangen pro redundanter CAN-Schnittstelle mit Tiefenbegrenzung. Von der Anwendung wird nun erwartet, dass sie CanardTxQueue
(oder mehrere bei redundantem Transport) manuell instanziiert.
Ersetzen Sie verknüpfte O(n)-Listen durch schnelle O(log n) AVL-Bäume (Cavl-Bibliothek wird mit libcanard verteilt). Das Durchlaufen der Liste der RX-Abonnements erfordert nun ein rekursives Durchlaufen des Baums.
Manuelle DSDL-Serialisierungshilfen entfernt; Verwenden Sie stattdessen Nunavut.
Ersetzen Sie die bitweise CRC-Berechnung standardmäßig durch eine viel schnellere statische Tabelle (#185). Dies kann durch Setzen von CANARD_CRC_TABLE=0
deaktiviert werden, was voraussichtlich ca. 500 Byte ROM.
Probleme mit der Konstantenkorrektheit in der API behoben (#175).
canardRxAccept2()
umbenannt in canardRxAccept()
.
Unterstützt Build-Konfigurationsheader über CANARD_CONFIG_HEADER
.
API zum Generieren von CAN-Hardware-Akzeptanzfilterkonfigurationen hinzufügen (#169).
canardRxAccept2()
hinzufügen, canardRxAccept()
verwerfen.CanardRxSubscription
.Die Erstveröffentlichung.