Libcanard เป็นการใช้งานขนาดกะทัดรัดของสแต็กโปรโตคอล Cyphal/CAN ใน C99/C11 สำหรับระบบฝังตัวแบบเรียลไทม์ที่มีความสมบูรณ์สูง
Cyphal เป็นมาตรฐานบัสข้อมูลน้ำหนักเบาแบบเปิดที่ออกแบบมาเพื่อการสื่อสารภายในยานพาหนะที่เชื่อถือได้ในการใช้งานด้านการบินและอวกาศและหุ่นยนต์ผ่าน CAN บัส อีเธอร์เน็ต และการขนส่งที่แข็งแกร่งอื่นๆ
อ่านเอกสารใน libcanard/canard.h
ค้นหาตัวอย่าง ข้อมูลเริ่มต้น บทช่วยสอนในฟอรัม Cyphal
หากคุณต้องการมีส่วนร่วม โปรดอ่าน CONTRIBUTING.md
ไลบรารีได้รับการออกแบบให้ใช้งานได้ทันทีกับแพลตฟอร์ม 8/16/32/64 บิตทั่วไป รวมถึงแพลตฟอร์ม Baremetal ที่ฝังลึก ตราบใดที่ยังมีคอมไพเลอร์ที่เป็นไปตามมาตรฐาน แอปพลิเคชันควรจัดเตรียมเลเยอร์ IO สื่อเฉพาะแพลตฟอร์ม (ไดรเวอร์):
+---------------------------------+
| Application |
+-------+-----------------+-------+
| |
+-------+-------+ +-------+-------+
| Libcanard | | Media layer |
+---------------+ +-------+-------+
|
+-------+-------+
| Hardware |
+---------------+
ทีมพัฒนา OpenCyphal ดูแลรักษาคอลเลกชันของส่วนประกอบเฉพาะแพลตฟอร์มต่างๆ ในพื้นที่เก็บข้อมูลแยกต่างหากที่ https://github.com/OpenCyphal/platform_special_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.
}
ใช้นูนาวุตเพื่อสร้าง (ยกเลิก) รหัสการทำให้เป็นอนุกรมจากข้อกำหนด 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 เช่น นูนาวุต
ใน 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.
}
นอกจากนี้ยังมี API แบบง่ายสำหรับการสร้างการกำหนดค่าตัวกรองการยอมรับฮาร์ดแวร์ CAN อีกด้วย ตัวกรองการยอมรับถูกสร้างขึ้นในรูปแบบ ID + มาสก์ 29 บิตแบบขยาย และสามารถใช้เพื่อลดจำนวนการถ่ายโอนที่ไม่เกี่ยวข้องที่ประมวลผลในซอฟต์แวร์
// 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)
แก้ไขปัญหาการพึ่งพาใน toolchain นักเทียบท่า
ไม่มีการเปลี่ยนแปลง API ในรุ่นนี้ นอกเหนือจากการรีแบรนด์/การเปลี่ยนชื่อ: CANARD_UAVCAN_SPECIFICATION_VERSION_MAJOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MAJOR
CANARD_UAVCAN_SPECIFICATION_VERSION_MINOR
-> CANARD_CYPHAL_SPECIFICATION_VERSION_MINOR
_canard_cavl.h
(#196) คิวการส่งข้อมูลเฉพาะต่ออินเทอร์เฟซ CAN ซ้ำซ้อนพร้อมขีดจำกัดความลึก ขณะนี้แอปพลิเคชันคาดว่าจะสร้างอินสแตนซ์ CanardTxQueue
(หรือหลายรายการในกรณีของการขนส่งที่ซ้ำซ้อน) ด้วยตนเอง
แทนที่รายการที่เชื่อมโยง O(n) ด้วยทรี O(log n) AVL ที่รวดเร็ว (ไลบรารี Cavl แจกจ่ายด้วย libcanard) การข้ามผ่านรายการการสมัครสมาชิก RX ในตอนนี้จำเป็นต้องมีการข้ามผ่านแผนผังแบบเรียกซ้ำ
ตัวช่วยซีเรียลไลเซชัน DSDL ด้วยตนเองถูกลบออก ใช้นูนาวุตแทน
แทนที่การคำนวณ CRC ระดับบิตด้วยตารางคงที่ที่เร็วกว่ามากตามค่าเริ่มต้น (#185) สามารถปิดใช้งานได้โดยการตั้งค่า CANARD_CRC_TABLE=0
ซึ่งคาดว่าจะช่วยประหยัดได้ประมาณ รอม 500 ไบต์
แก้ไขปัญหาเกี่ยวกับความถูกต้องของ const ใน API (#175)
canardRxAccept2()
เปลี่ยนชื่อเป็น canardRxAccept()
รองรับการสร้างส่วนหัวการกำหนดค่าผ่าน CANARD_CONFIG_HEADER
เพิ่ม API สำหรับการสร้างการกำหนดค่าตัวกรองการยอมรับฮาร์ดแวร์ CAN (#169)
canardRxAccept2()
เลิกใช้ canardRxAccept()
CanardRxSubscription
การเปิดตัวครั้งแรก