Libcanard es una implementación compacta de la pila de protocolos Cyphal/CAN en C99/C11 para sistemas integrados en tiempo real de alta integridad.
Cyphal es un estándar de bus de datos abierto y liviano diseñado para una comunicación intravehicular confiable en aplicaciones aeroespaciales y robóticas a través de bus CAN, Ethernet y otros transportes robustos.
Lea los documentos en libcanard/canard.h
.
Encuentre ejemplos, principiantes y tutoriales en el foro de Cyphal.
Si desea contribuir, lea CONTRIBUTING.md
.
La biblioteca está diseñada para poder utilizarse desde el primer momento con cualquier plataforma convencional de 8/16/32/64 bits, incluidas las plataformas baremetal profundamente integradas, siempre que haya disponible un compilador compatible con el estándar. Se supone que la aplicación proporciona la capa de E/S de medios específica de la plataforma (controlador):
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
El equipo de desarrollo de OpenCyphal mantiene una colección de varios componentes específicos de la plataforma en un repositorio separado en https://github.com/OpenCyphal/platform_specific_components. Se anima a los usuarios a buscar en ese repositorio controladores, ejemplos y otras piezas que puedan reutilizarse en la aplicación de destino para acelerar el diseño de la capa de E/S de medios (controlador) para la aplicación.
El ejemplo aumenta la documentación pero no la reemplaza.
La biblioteca requiere un asignador de memoria dinámica determinista de complejidad constante. Podríamos usar el montón C estándar, pero la mayoría de las implementaciones no son de complejidad constante, así que supongamos que estamos usando O1Heap en su lugar. Vamos a necesitar envoltorios básicos:
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 );
}
Inicie una instancia de biblioteca:
CanardInstance canard = canardInit ( & memAllocate , & memFree );
canard . node_id = 42 ; // Defaults to anonymous; can be set up later at any point.
Para poder enviar transferencias a través de la red, necesitaremos una cola de transmisión por cada interfaz CAN redundante:
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.
Publicar un mensaje (no se muestra la serialización del mensaje):
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.
}
Utilice Nunavut para generar automáticamente código de (des)serialización a partir de definiciones DSDL.
Las tramas CAN generadas a partir de la transferencia de mensajes ahora se almacenan en la queue
. Necesitamos seleccionarlos uno por uno y transmitirlos. Normalmente, se debe invocar periódicamente el siguiente fragmento para descargar las tramas CAN de la cola de transmisión priorizada (o varias, si se utilizan interfaces de red redundantes) al controlador 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 recepción de la transferencia se realiza alimentando marcos a la máquina de estado de reensamblaje de transferencia desde cualquiera de las interfaces redundantes. Pero primero debemos suscribirnos:
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 );
La "extensión" se refiere a la cantidad mínima de memoria necesaria para contener cualquier representación serializada de cualquier versión compatible del tipo de datos; o, en otras palabras, es el tamaño máximo posible de los objetos recibidos. Este parámetro lo determina el autor del tipo de datos en el momento de la definición del tipo de datos. Normalmente es mayor que el tamaño máximo de objeto para permitir que el autor del tipo de datos introduzca más campos en futuras versiones del tipo; por ejemplo, MyMessage.1.0
puede tener un tamaño máximo de 100 bytes y una extensión de 200 bytes; una versión revisada MyMessage.1.1
puede tener un tamaño máximo entre 0 y 200 bytes. Los transcompiladores DSDL, como Nunavut, proporcionan valores de extensión por tipo de datos.
En Libcanard utilizamos el término "suscripción" no sólo para asuntos (mensajes), sino también para servicios, por simplicidad.
Podemos suscribirnos y darnos de baja en tiempo de ejecución tantas veces como queramos. Sin embargo, normalmente una aplicación integrada se suscribiría una vez y continuaría con ella. Bien, así es como recibimos las transferencias:
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.
}
También se proporciona una API sencilla para generar configuraciones de filtro de aceptación de hardware CAN. Los filtros de aceptación se generan en un esquema extendido de ID + máscara de 29 bits y se pueden usar para minimizar la cantidad de transferencias irrelevantes procesadas en el software.
// 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 especificación API completa está disponible en la documentación. Si encuentra que los ejemplos no son claros o incorrectos, abra un ticket.
canardRxGetSubscription
.Actualice la marca ya que UAVCAN v1 pasa a llamarse Cyphal.
Mejore el cumplimiento de MISRA eliminando el uso de matriz flexible: (#192).
Solucionar problemas de dependencia en la cadena de herramientas de Docker.
No hay cambios de API en esta versión aparte del cambio de marca/cambio de nombre: CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
_canard_cavl.h
(#196). Colas de transmisión dedicadas por interfaz CAN redundante con límites de profundidad. Ahora se espera que la aplicación cree una instancia CanardTxQueue
(o varias en caso de transporte redundante) manualmente.
Reemplace las listas enlazadas O(n) con árboles AVL rápidos O(log n) (la biblioteca Cavl se distribuye con libcanard). Recorrer la lista de suscripciones RX ahora requiere un recorrido recursivo del árbol.
Se eliminaron los ayudantes de serialización DSDL manual; utilice Nunavut en su lugar.
Reemplace el cálculo CRC bit a bit con una tabla estática mucho más rápida de forma predeterminada (#185). Esto se puede desactivar configurando CANARD_CRC_TABLE=0
, lo que se espera que ahorre ca. 500 bytes de ROM.
Se solucionaron problemas con la corrección constante en la API (#175).
canardRxAccept2()
renombrado a canardRxAccept()
.
Admite encabezados de configuración de compilación a través de CANARD_CONFIG_HEADER
.
Agregue API para generar configuraciones de filtro de aceptación de hardware CAN (#169).
canardRxAccept2()
, desapruebe canardRxAccept()
.CanardRxSubscription
.La liberación inicial.