Libcanard est une implémentation compacte de la pile de protocoles Cyphal/CAN en C99/C11 pour les systèmes embarqués temps réel à haute intégrité.
Cyphal est une norme de bus de données légère et ouverte conçue pour une communication intravéhiculaire fiable dans les applications aérospatiales et robotiques via le bus CAN, Ethernet et d'autres transports robustes.
Lisez la documentation dans libcanard/canard.h
.
Retrouvez des exemples, des starters, des tutoriels sur le forum Cyphal.
Si vous souhaitez contribuer, veuillez lire CONTRIBUTING.md
.
La bibliothèque est conçue pour être utilisable immédiatement avec n'importe quelle plate-forme conventionnelle 8/16/32/64 bits, y compris les plates-formes baremetal profondément intégrées, à condition qu'un compilateur conforme aux normes soit disponible. La couche IO multimédia spécifique à la plate-forme (pilote) est censée être fournie par l'application :
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
L'équipe de développement d'OpenCyphal gère une collection de divers composants spécifiques à la plate-forme dans un référentiel distinct à l'adresse https://github.com/OpenCyphal/platform_special_components. Les utilisateurs sont encouragés à rechercher dans ce référentiel des pilotes, des exemples et d'autres éléments pouvant être réutilisés dans l'application cible pour accélérer la conception de la couche d'E/S multimédia (pilote) pour l'application.
L'exemple augmente la documentation mais ne la remplace pas.
La bibliothèque nécessite un allocateur de mémoire dynamique déterministe à complexité constante. Nous pourrions utiliser le tas C standard, mais la plupart des implémentations ne sont pas à complexité constante, supposons donc que nous utilisons O1Heap à la place. Nous aurons besoin de wrappers de base :
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 );
}
Initialisez une instance de bibliothèque :
CanardInstance canard = canardInit ( & memAllocate , & memFree );
canard . node_id = 42 ; // Defaults to anonymous; can be set up later at any point.
Afin de pouvoir envoyer des transferts sur le réseau, nous aurons besoin d'une file d'attente de transmission par interface CAN redondante :
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.
Publier un message (la sérialisation du message n'est pas affichée) :
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.
}
Utilisez Nunavut pour générer automatiquement du code de (dé)sérialisation à partir des définitions DSDL.
Les trames CAN générées à partir du transfert de messages sont désormais stockées dans la queue
. Il faut les sélectionner un à un et les faire transmettre. Normalement, le fragment suivant doit être invoqué périodiquement pour décharger les trames CAN de la file d'attente de transmission prioritaire (ou plusieurs, si des interfaces réseau redondantes sont utilisées) dans le pilote CAN :
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 ));
}
La réception du transfert s'effectue en introduisant des trames dans la machine à états de réassemblage de transfert à partir de l'une des interfaces redondantes. Mais il faut d'abord s'abonner :
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 );
L'« étendue » fait référence à la quantité minimale de mémoire requise pour contenir toute représentation sérialisée de toute version compatible du type de données ; ou, en d'autres termes, c'est la taille maximale possible des objets reçus. Ce paramètre est déterminé par l'auteur du type de données au moment de la définition du type de données. Elle est généralement supérieure à la taille maximale de l'objet afin de permettre à l'auteur du type de données d'introduire davantage de champs dans les futures versions du type ; par exemple, MyMessage.1.0
peut avoir une taille maximale de 100 octets et une étendue de 200 octets ; une version révisée MyMessage.1.1
peut avoir une taille maximale comprise entre 0 et 200 octets. Les valeurs d'étendue sont fournies par type de données par des transcompilateurs DSDL tels que le Nunavut.
Dans Libcanard nous utilisons le terme « abonnement » non seulement pour les sujets (messages), mais aussi pour les services, par souci de simplicité.
Nous pouvons nous abonner et nous désinscrire au moment de l'exécution autant de fois que nous le souhaitons. Cependant, normalement, une application intégrée s’abonne une seule fois et s’en sert. D'accord, voici comment nous recevons les virements :
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.
}
Une API simple pour générer des configurations de filtres d'acceptation matérielle CAN est également fournie. Les filtres d'acceptation sont générés dans un schéma étendu d'identification + masque de 29 bits et peuvent être utilisés pour minimiser le nombre de transferts non pertinents traités dans le logiciel.
// 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 );
La spécification complète de l'API est disponible dans la documentation. Si vous trouvez les exemples peu clairs ou incorrects, veuillez ouvrir un ticket.
canardRxGetSubscription
.Mettre à jour la marque car UAVCAN v1 est renommé Cyphal.
Améliorez la conformité MISRA en supprimant l’utilisation du tableau flexible : (#192).
Résoudre les problèmes de dépendance dans la chaîne d'outils Docker.
Il n'y a aucun changement d'API dans cette version en dehors du changement de marque/renommage : CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
_canard_cavl.h
(#196). Files d'attente de transmission dédiées par interface CAN redondante avec limites de profondeur. L'application devrait désormais instancier CanardTxQueue
(ou plusieurs en cas de transport redondant) manuellement.
Remplacez les listes chaînées O(n) par des arbres AVL O(log n) rapides (la bibliothèque Cavl est distribuée avec libcanard). Le parcours de la liste des abonnements RX nécessite désormais un parcours récursif de l'arborescence.
Aides à la sérialisation DSDL manuelles supprimées ; utilisez plutôt le Nunavut.
Remplacez le calcul CRC au niveau du bit par une table statique beaucoup plus rapide par défaut (#185). Cela peut être désactivé en définissant CANARD_CRC_TABLE=0
, ce qui devrait permettre d'économiser environ 1 000 000 $. 500 octets de ROM.
Correction de problèmes avec const-correctness dans l'API (#175).
canardRxAccept2()
renommé canardRxAccept()
.
Prise en charge des en-têtes de configuration de build via CANARD_CONFIG_HEADER
.
Ajoutez une API pour générer des configurations de filtres d'acceptation matérielle CAN (#169).
canardRxAccept2()
, désapprouvez canardRxAccept()
.CanardRxSubscription
.La version initiale.