一、源码下载
1、git下载
点击我下载
2、csdn下载
自己上传的点击下载
二、源码移植
我自己是使用rt-thread操作系统移植的。但是不局限与操作系统,裸机也可以。
1、首先将源码加入到工程
2、分别实现一个内存的分配与释放函数,他是一个指针函数,原型为typedef void* (*CanardMemoryAllocate)(CanardInstance* ins, size_t amount);
static void* mem_allocate(CanardInstance* const canard, const size_t amount)
{(void) canard;return rt_malloc(amount);
}
static void mem_free(CanardInstance* const canard, void* const pointer)
{(void) canard;rt_free(pointer);
}
3、初始化canard
void slave_comm_init()
{canard = canardInit(&mem_allocate, &mem_free);canard.node_id = savePara.id; txQueue = canardTxInit(1536, CANARD_MTU_CAN_CLASSIC); // Set MTU = 64 bytes. There is also CANARD_MTU_CAN_CLASSIC.
}
canard.node_id
设置本机id
canardTxInit(1536, CANARD_MTU_CAN_CLASSIC);
初始化发送队列,1536为大小。CANARD_MTU_CAN_CLASSIC
表示使用的是普通的can,数据最大为8个字节,CANARD_MTU_CAN_FD
表示使用的是can fd。
3、实现发送函数
void slave_comm_tx_process()
{for (const CanardTxQueueItem* ti = NULL; (ti = canardTxPeek(&txQueue)) != NULL;) // Peek at the top of the queue.{if ((0U == ti->tx_deadline_usec) || (ti->tx_deadline_usec > rt_tick_get_millisecond()*1000)) // Check the deadline.{if (!slave_send_ext(ti->frame.extended_can_id,(void *)ti->frame.payload,ti->frame.payload_size)) // 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(&txQueue, ti));}
}
canard协议将发送的包处理完后会写入到队列中,canardTxPeek(&txQueue))
从队列中取出数据,slave_send_ext(ti->frame.extended_can_id,(void *)ti->frame.payload,ti->frame.payload_size))
为硬件发送函数,调用can发送。注意:UAVCAN使用的是扩展帧id
。
硬件发送函数为:
rt_inline uint8_t slave_send_ext(uint32_t id,uint8_t *sendBuf,uint8_t len)
{struct rt_can_msg txMsg = {0};txMsg.id = id;txMsg.ide = RT_CAN_EXTID;txMsg.rtr = RT_CAN_DTR;txMsg.len = len;for(rt_uint8_t i=0;i<len;i++){txMsg.data[i] = sendBuf[i];}return rt_device_write(slaveDev,0,&txMsg,sizeof(txMsg));}
RT_CAN_EXTID
表示使用扩展帧
4、实现can硬件接收处理函数
void slave_comm_rx_process()
{CanardRxTransfer transfer;CanardFrame receivedFrame;struct rt_can_msg canRxMsg = {0};uint32_t rxTimestampUsec;int8_t result;while(rt_mq_recv(&slave_rec_msgq,&canRxMsg,sizeof(canRxMsg),RT_WAITING_NO) == RT_EOK){receivedFrame.extended_can_id = canRxMsg.id;receivedFrame.payload_size = canRxMsg.len;receivedFrame.payload = canRxMsg.data;rxTimestampUsec = rt_tick_get_millisecond()*1000;result = canardRxAccept(&canard,rxTimestampUsec, // When the frame was received, in microseconds.&receivedFrame, // The CAN frame received from the bus.0, // 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){void process_received_transfer(const uint8_t index,CanardRxTransfer* const transfer);process_received_transfer(0, &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的中断中接收到数据放入slave_rec_msgq
队列,
struct rt_can_msg rxMsg = {0};rt_device_read(slaveDev,0,&rxMsg,sizeof(rxMsg));rt_mq_send(&slave_rec_msgq, &rxMsg, sizeof(rxMsg));
然后协议从队列中读取数据处理。
将can数据转为canard支持的数据类型。
receivedFrame.extended_can_id = canRxMsg.id;receivedFrame.payload_size = canRxMsg.len;receivedFrame.payload = canRxMsg.data;
当协议接收到完整的一帧数据后返回result
等于1,自己处理接收到的数据。
process_received_transfer(0, &transfer); // A transfer has been received, process it.
实现为:
void process_received_transfer(const uint8_t index,CanardRxTransfer* const transfer)
{LOG_D("slave rec id:%d size:%d",transfer->metadata.remote_node_id,transfer->payload_size);if(transfer->metadata.remote_node_id == canard.node_id){slavePackDef *p = (slavePackDef *)transfer->payload;recCmd = p->packCmd;}}
数据保存在transfer->payload
中。执行完毕后释放内存:canard.memory_free(&canard, transfer.payload);
。
5、订阅消息
在canard协议中消息有三种类型,分别是: CanardTransferKindMessage``CanardTransferKindResponse
、CanardTransferKindRequest
区别在于:
CanardTransferKindMessage
:广播,从发布者到所有订阅者。
CanardTransferKindResponse
:点对点,从服务器到客户端。
CanardTransferKindRequest
:点对点,从客户端到服务器。
一般来说从机为服务端,主机为客户端。
void slave_control_init()
{(void) canardRxSubscribe(&canard, // Subscribe to an arbitrary service response.CanardTransferKindResponse, // Specify that we want service responses, not requests.SLAVE_RESPONSE_PORT_ID, // The Service-ID whose responses we will receive.1536, // The extent (see above).CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC,&responseSubscription);}
以上订阅了一个CanardTransferKindResponse
类型的消息,Service-ID为SLAVE_RESPONSE_PORT_ID
,
#define SLAVE_RESPONSE_PORT_ID 123
函数原型为:
int8_t canardRxSubscribe(CanardInstance* const ins,const CanardTransferKind transfer_kind,const CanardPortID port_id,const size_t extent,const CanardMicrosecond transfer_id_timeout_usec,CanardRxSubscription* const out_subscription);
参数解析:
ins
:canard的一个实例,就是上面初始化的那个变量。
transfer_kind
:消息类型,上面说的三个。
port_id
:消息id。
extent
:定义了传输有效载荷存储器缓冲器的大小。
transfer_id_timeout_usec
:默认传输ID超时值定义。
out_subscription
:如果已根据请求创建新订阅,则返回值为1。
如果在调用函数时存在此类订阅,则返回值为0。在这种情况下,终止现有订阅,然后在其位置创建新订阅。挂起的传输可能会丢失。
如果任何输入参数无效,则返回值为否定的无效参数错误。
三、应用层数据发送
static void send_data()
{static uint8_t messageTransferId = 0; const CanardTransferMetadata transferMetadata = {.priority = CanardPriorityNominal,.transfer_kind = CanardTransferKindResponse,.port_id = SLAVE_RESPONSE_PORT_ID, .remote_node_id = id, .transfer_id = messageTransferId,};uint8_t sendBuf[100];for(uint8_t i=0;i<sizeof(sendBuf);i++){sendBuf[i] = i;}++messageTransferId; transmission on this subject.int32_t result = canardTxPush(&txQueue, &canard,0, &transferMetadata,sizeof(sendBuf), sendBuf);if (result < 0){LOG_W("slave cmd send failed!");}
}
需要注意的是:
const CanardTransferMetadata transferMetadata = {.priority = CanardPriorityNominal,.transfer_kind = CanardTransferKindResponse,.port_id = SLAVE_RESPONSE_PORT_ID, // This is the subject-ID..remote_node_id = id, // Messages cannot be unicast, so use UNSET..transfer_id = messageTransferId,};
transfer_kind
需要和上面订阅的消息类型一样才能接收成功。
messageTransferId
:每次发送都需要自加1。
port_id
:也需要和订阅消息port_id一样。
四、移植成功源码下载
点击我下载