Libcanard 是 C99/C11 中 Cyphal/CAN 協定堆疊的緊湊實現,適用於高完整性即時嵌入式系統。
Cyphal 是一種開放式輕量級資料匯流排標準,旨在透過 CAN 總線、乙太網路和其他可靠的傳輸方式在航空航太和機器人應用中實現可靠的車內通訊。
閱讀libcanard/canard.h
中的文件。
在 Cyphal 論壇上尋找範例、入門教學和教學。
如果您想貢獻,請閱讀CONTRIBUTING.md
。
該程式庫設計為可在任何傳統 8/16/32/64 位元平台上開箱即用,包括深度嵌入式裸機平台,只要有可用的符合標準的編譯器即可。平台特定的媒體 IO 層(驅動程式)應該由應用程式提供:
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
OpenCyphal 開發團隊在 https://github.com/OpenCyphal/platform_specific_components 的單獨儲存庫中維護了各種特定於平台的元件的集合。我們鼓勵用戶在該儲存庫中搜尋驅動程式、範例以及可在目標應用程式中重複使用的其他部分,以加快應用程式的媒體 IO 層(驅動程式)的設計。
該範例補充了文檔,但並沒有取代它。
該函式庫需要一個恆定複雜度的確定性動態記憶體分配器。我們可以使用標準 C 堆,但大多數實現的複雜度都不是恆定的,因此假設我們使用 O1Heap。我們需要基本的包裝器:
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 );
}
初始化一個函式庫實例:
CanardInstance canard = canardInit ( & memAllocate , & memFree );
canard . node_id = 42 ; // Defaults to anonymous; can be set up later at any point.
為了能夠透過網路發送傳輸,我們需要每個冗餘 CAN 介面一個傳輸佇列:
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.
發布訊息(訊息序列化未顯示):
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.
}
使用 Nunavut 從 DSDL 定義自動產生(反)序列化程式碼。
訊息傳輸產生的 CAN 幀現在儲存在queue
中。我們需要將它們一一挑出來並傳送出去。通常,應定期呼叫以下片段,將 CAN 幀從優先傳輸佇列(或多個,如果使用冗餘網路介面)卸載到 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 ));
}
傳輸接收是透過將訊框從任何冗餘介面饋送到傳輸重組狀態機來完成的。但首先,我們需要訂閱:
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 );
「範圍」是指保存資料類型的任何相容版本的任何序列化表示所需的最小記憶體量;或者,換句話說,它是接收到的物件最大可能的大小。此參數由資料類型作者在資料類型定義時決定。它通常大於最大物件大小,以便允許資料類型作者在該類型的未來版本中引入更多欄位;例如, MyMessage.1.0
的最大大小可以為100字節,範圍為200字節;修訂版MyMessage.1.1
最大大小可能在 0 到 200 位元組之間。 DSDL 轉編譯器(例如 Nunavut)會依資料類型提供範圍值。
在 Libcanard 中,為了簡單起見,我們不僅將「訂閱」一詞用於主題(訊息),還用於服務。
我們可以在運行時根據需要多次訂閱和取消訂閱。然而,通常情況下,嵌入式應用程式會訂閱一次並隨之捲動。好的,這就是我們接收轉帳的方式:
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.
}
還提供了一個用於產生 CAN 硬體接受過濾器配置的簡單 API。接受過濾器以擴展的 29 位元 ID + 遮罩方案生成,可用於最大限度地減少軟體中處理的不相關傳輸的數量。
// 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 );
文件中提供了完整的 API 規格。如果您發現範例不清楚或不正確,請開票。
canardRxGetSubscription
。更新品牌,UAVCAN v1 更名為 Cyphal。
透過取消使用柔性陣列來提高 MISRA 合規性:(#192)。
修復 docker 工具鏈中的依賴問題。
除了重新命名/重新命名之外,此版本中沒有 API 變更: CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
-> CANARDCY
_canard_cavl.h
來消除標頭檔名衝突的風險 (#196)。每個冗餘 CAN 介面都有專用傳輸佇列,具有深度限制。現在,應用程式需要手動實例化CanardTxQueue
(或在冗餘傳輸的情況下實例化多個實例)。
將 O(n) 鍊錶替換為快速 O(log n) AVL 樹(Cavl 庫隨 libcanard 一起分發)。現在,遍歷 RX 訂閱清單需要遞歸遍歷樹。
手動 DSDL 序列化助手已刪除;請改用努納武特地區。
預設用更快的靜態表取代位元 CRC 計算 (#185)。可以透過設定CANARD_CRC_TABLE=0
來停用此功能,預計可以節省約 10% 的空間。 500 位元組 ROM。
修正了 API 中 const 正確性的問題 (#175)。
canardRxAccept2()
重新命名為canardRxAccept()
。
透過CANARD_CONFIG_HEADER
支援建置配置標頭。
新增用於產生 CAN 硬體接受過濾器配置的 API (#169)。
canardRxAccept2()
,並棄用canardRxAccept()
。CanardRxSubscription
中提供使用者參考。初始版本。