Hi everyone, I’m trying to use the DroneCAN protocol on an STM32 chip. The Libcanard documentation says that you only need to include the canard.c, canard.h, and canard_internals.h files. I’ve also checked various resources, and they all mention using Python and CMake to compile Libcanard into a C library, but I’m not entirely sure about the process or if I’m doing the right thing. Could anyone share an example project or offer some guidance? Thanks so much!
1 Like
Hey!
You need these things from the library libcanard:
- the canard.c & .h and canard_internals.h
- If you want to use c++, you need everything from the canard subfolder (did not try this yet)
- the can driver for stm32: canard_stm32.c and canard_stm32.h (in drivers/stm32/)
Then you are also going to need the DroneCAN DSDL datatypes and the generator (the datatype generator, all types can be found here) and generate the types you need.
I have just started with DroneCAN so my way might not be the best but it does work:
- Create a STM32 project using CMake
- Add the whole Libcanard project to Drivers/libcanard (git clone or git add submodule)
- Add the DSDL generator to Drivers/dronecan_dsdlc
- Add the DSDL sources to Drivers/DSDL
- go to Drivers/ and generate the types:
python dronecan_dsdlc/dronecan_dsdlc.py -O dsdl_generated DSDL/dronecan DSDL/uavcan DSDL/com DSDL/ardupilot
(you only need to generate the types you are going to use) - now add all necessary files to the CMakeLists.txt of your file (again, not very familiar with CMake but this works):
# project include files target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE Drivers/libcanard Drivers/libcanard/drivers/stm32 Drivers/dsdl_generated/include ) # all dsdl sources file(GLOB_RECURSE DSDL_SOURCES "Drivers/dsdl_generated/src/*.c") target_sources(${CMAKE_PROJECT_NAME} PRIVATE ${DSDL_SOURCES} ) # canard and driver add_library(libcanard STATIC Drivers/libcanard/canard.c Drivers/libcanard/drivers/stm32/canard_stm32.c ) # canard include paths target_include_directories(libcanard PRIVATE Drivers/libcanard )
Now I was able to import canard.h, canard_stm32.h and dronecan_msgs.h and from there you can use the examples in the libcanard project (you just need to change the adapter).
Here are my adapter specific snippets:
// some helper functions:
uint64_t micros64(void) {
return HAL_GetTick() * 1000;
}
uint32_t millis32(void) {
return HAL_GetTick();
}
void getUniqueID(uint8_t id[16]) {
memset(id, 0, 16);
// use stm32 unique id
uint32_t uid[3];
uid[0] = HAL_GetUIDw0(); // 0-3
uid[1] = HAL_GetUIDw1(); // 4-7
uid[2] = HAL_GetUIDw2(); // 8-11
memcpy(id, uid, sizeof(uid));
// TODO 12-15 is empty, you can add something here if you want
}
// the dronecan specific functions
#include <canard.h>
#include <canard_stm32.h>
#include <dronecan_msgs.h>
void processTxRxOnce() {
// Transmitting
for (const CanardCANFrame *txf = NULL; (txf = canardPeekTxQueue(&canard)) != NULL;) {
const int16_t tx_res = canardSTM32Transmit(txf);
if (tx_res < 0) { // Failure - drop the frame
canardPopTxQueue(&canard);
} else if (tx_res > 0) { // Success - just drop the frame
canardPopTxQueue(&canard);
} else { // Timeout - just exit and try again later
break;
}
}
// Receiving
CanardCANFrame rx_frame;
const uint64_t timestamp = micros64();
const int16_t rx_res = canardSTM32Receive(&rx_frame);
if (rx_res < 0) {
CNT_ReceiveErrors++;
} else if (rx_res > 0) { // Success - process the frame
canardHandleRxFrame(&canard, &rx_frame, timestamp);
}
}
bool shouldAcceptTransfer(const CanardInstance *ins __attribute__((unused)),
uint64_t *out_data_type_signature,
uint16_t data_type_id,
CanardTransferType transfer_type,
uint8_t source_node_id __attribute__((unused))) {
if (transfer_type == CanardTransferTypeRequest) {
if (!connected) {
return false; // can not get message if not connected (and therefore no node id)
}
switch (data_type_id) {
case UAVCAN_PROTOCOL_GETNODEINFO_ID: {
*out_data_type_signature = UAVCAN_PROTOCOL_GETNODEINFO_REQUEST_SIGNATURE;
return true;
}
case UAVCAN_PROTOCOL_PARAM_GETSET_ID: {
*out_data_type_signature = UAVCAN_PROTOCOL_PARAM_GETSET_SIGNATURE;
return true;
}
case UAVCAN_PROTOCOL_RESTARTNODE_ID: {
*out_data_type_signature = UAVCAN_PROTOCOL_RESTARTNODE_SIGNATURE;
return true;
}
}
} else if (transfer_type == CanardTransferTypeBroadcast) {
switch (data_type_id) {
if (!connected) {
*out_data_type_signature = UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_SIGNATURE;
return true;
} else { // ignore if already connected
return false;
}
case UAVCAN_PROTOCOL_NODESTATUS_ID: {
return false;
}
}
}
return false;
}
//TODO you can copy the handle_* functions from the examples in the libcanard project
void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) {
if (transfer->transfer_type == CanardTransferTypeRequest) {
switch (transfer->data_type_id) {
case UAVCAN_PROTOCOL_GETNODEINFO_ID: {
handle_GetNodeInfo(ins, transfer);
break;
}
case UAVCAN_PROTOCOL_RESTARTNODE_ID: {
handle_RestartNode();
break;
}
}
} else if (transfer->transfer_type == CanardTransferTypeBroadcast) {
switch (transfer->data_type_id) {
case UAVCAN_PROTOCOL_DYNAMIC_NODE_ID_ALLOCATION_ID: {
handle_DNA_Allocation(ins, transfer); //TODO you need to set connected = true when DNA is finished
break;
}
}
}
}
void init_dronecan() {
canardSTM32Init(&timings, CanardSTM32IfaceModeNormal);
canardInit(&canard,
memory_pool,
sizeof(memory_pool),
onTransferReceived,
shouldAcceptTransfer,
NULL);
next_1hz_service_at = micros64();
}
void tick_dronecan() {
processTxRxOnce();
CNT.stats = canardSTM32GetStats();
if (canardGetLocalNodeID(&canard) == CANARD_BROADCAST_NODE_ID) {
connected = false;
if (millis32() >= send_next_node_id_allocation_request_at_ms) {
request_DNA();
}
}
const uint64_t ts = micros64();
if (ts >= next_1hz_service_at) {
next_1hz_service_at += 1000000ULL;
canardCleanupStaleTransfers(&canard, ts);
if (connected) {
send_NodeStatus();
}
}
}
Feel free to reply if something does not work
1 Like
Hey! We’re done all the hard work of bringing together the libraries and written boilerplate code in our project here, have a look!