diff --git a/README.md b/README.md index de25dd9..1fdf370 100644 --- a/README.md +++ b/README.md @@ -206,10 +206,29 @@ else } ``` +A simple API for generating CAN hardware acceptance filter configurations is also provided. +Acceptance filters are generated in an extended 29-bit ID + mask scheme and can be used to minimize the number of irrelevant transfers processed in software. + +```c +// Generate an acceptance filter to receive only uavcan.node.Heartbeat.1.0 messages (fixed port ID 7509): +CanardAcceptanceFilterConfig heartbeat_config = canardMakeAcceptanceFilterConfigForSubject(7509); +// And to receive only uavcan.register.Access.1.0 services (fixed port ID 384): +CanardAcceptanceFilterConfig register_access_config = canardMakeAcceptanceFilterConfigForService(384, ins.node_id); + +// You can also combine the two filter configurations into one (may also accept in other 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 UAVCAN specification. +CanardAcceptanceFilterConfig combined_config = canardConsolidateAcceptanceFilterConfigs(&heartbeat_config, ®ister_access_config); +configureHardwareFilters(combined_config.extended_can_id, combined_config.extended_mask); +``` + +Full API specification is available in the documentation. +If you find the examples to be unclear or incorrect, please, open a ticket. + ## Revisions ### v2.0 - - Dedicated transmission queues per redundant CAN interface with depth limits. The application is now expected to instantiate `CanardTxQueue` (or several in case of redundant transport) manually. @@ -225,6 +244,8 @@ else - Support build configuration headers via `CANARD_CONFIG_HEADER`. +- Add API for generating CAN hardware acceptance filter configurations + ### v1.1 - Add new API function `canardRxAccept2()`, deprecate `canardRxAccept()`. diff --git a/libcanard/canard.c b/libcanard/canard.c index 52e981a..cd82690 100644 --- a/libcanard/canard.c +++ b/libcanard/canard.c @@ -139,16 +139,16 @@ CANARD_PRIVATE uint32_t txMakeMessageSessionSpecifier(const CanardPortID subject } CANARD_PRIVATE uint32_t txMakeServiceSessionSpecifier(const CanardPortID service_id, - const bool request_not_response, - const CanardNodeID src_node_id, - const CanardNodeID dst_node_id) + const bool request_not_response, + const CanardNodeID src_node_id, + const CanardNodeID dst_node_id) { CANARD_ASSERT(src_node_id <= CANARD_NODE_ID_MAX); CANARD_ASSERT(dst_node_id <= CANARD_NODE_ID_MAX); CANARD_ASSERT(service_id <= CANARD_SERVICE_ID_MAX); return src_node_id | (((uint32_t) dst_node_id) << OFFSET_DST_NODE_ID) | // - (((uint32_t) service_id) << OFFSET_SERVICE_ID) | // - (request_not_response ? FLAG_REQUEST_NOT_RESPONSE : 0U) | FLAG_SERVICE_NOT_MESSAGE; + (((uint32_t) service_id) << OFFSET_SERVICE_ID) | // + (request_not_response ? FLAG_REQUEST_NOT_RESPONSE : 0U) | FLAG_SERVICE_NOT_MESSAGE; } /// This is the transport MTU rounded up to next full DLC minus the tail byte. @@ -172,16 +172,16 @@ CANARD_PRIVATE size_t adjustPresentationLayerMTU(const size_t mtu_bytes) } CANARD_PRIVATE int32_t txMakeCANID(const CanardTransferMetadata* const tr, - const size_t payload_size, - const void* const payload, - const CanardNodeID local_node_id, - const size_t presentation_layer_mtu) + const size_t payload_size, + const void* const payload, + const CanardNodeID local_node_id, + const size_t presentation_layer_mtu) { CANARD_ASSERT(tr != NULL); CANARD_ASSERT(presentation_layer_mtu > 0); int32_t out = -CANARD_ERROR_INVALID_ARGUMENT; if ((tr->transfer_kind == CanardTransferKindMessage) && (CANARD_NODE_ID_UNSET == tr->remote_node_id) && - (tr->port_id <= CANARD_SUBJECT_ID_MAX)) + (tr->port_id <= CANARD_SUBJECT_ID_MAX)) { if (local_node_id <= CANARD_NODE_ID_MAX) { @@ -202,14 +202,14 @@ CANARD_PRIVATE int32_t txMakeCANID(const CanardTransferMetadata* const tr, } } else if (((tr->transfer_kind == CanardTransferKindRequest) || (tr->transfer_kind == CanardTransferKindResponse)) && - (tr->remote_node_id <= CANARD_NODE_ID_MAX) && (tr->port_id <= CANARD_SERVICE_ID_MAX)) + (tr->remote_node_id <= CANARD_NODE_ID_MAX) && (tr->port_id <= CANARD_SERVICE_ID_MAX)) { if (local_node_id <= CANARD_NODE_ID_MAX) { out = (int32_t) txMakeServiceSessionSpecifier(tr->port_id, - tr->transfer_kind == CanardTransferKindRequest, - local_node_id, - tr->remote_node_id); + tr->transfer_kind == CanardTransferKindRequest, + local_node_id, + tr->remote_node_id); CANARD_ASSERT(out >= 0); } else @@ -239,14 +239,14 @@ CANARD_PRIVATE int32_t txMakeCANID(const CanardTransferMetadata* const tr, } CANARD_PRIVATE uint8_t txMakeTailByte(const bool start_of_transfer, - const bool end_of_transfer, - const bool toggle, - const CanardTransferID transfer_id) + const bool end_of_transfer, + const bool toggle, + const CanardTransferID transfer_id) { CANARD_ASSERT(start_of_transfer ? (toggle == INITIAL_TOGGLE_STATE) : true); return (uint8_t) ((start_of_transfer ? TAIL_START_OF_TRANSFER : 0U) | - (end_of_transfer ? TAIL_END_OF_TRANSFER : 0U) | (toggle ? TAIL_TOGGLE : 0U) | - (transfer_id & CANARD_TRANSFER_ID_MAX)); + (end_of_transfer ? TAIL_END_OF_TRANSFER : 0U) | (toggle ? TAIL_TOGGLE : 0U) | + (transfer_id & CANARD_TRANSFER_ID_MAX)); } /// Takes a frame payload size, returns a new size that is >=x and is rounded up to the nearest valid DLC. @@ -261,9 +261,9 @@ CANARD_PRIVATE size_t txRoundFramePayloadSizeUp(const size_t x) /// The item is only allocated and initialized, but NOT included into the queue! The caller needs to do that. CANARD_PRIVATE TxItem* txAllocateQueueItem(CanardInstance* const ins, - const uint32_t id, - const CanardMicrosecond deadline_usec, - const size_t payload_size) + const uint32_t id, + const CanardMicrosecond deadline_usec, + const size_t payload_size) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(payload_size > 0U); @@ -289,7 +289,7 @@ CANARD_PRIVATE TxItem* txAllocateQueueItem(CanardInstance* const ins, /// This ensures that CAN frames with the same CAN ID are transmitted in the FIFO order. /// Frames that should be transmitted earlier compare smaller (i.e., put on the left side of the tree). CANARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const CanardTreeNode* const node) + const CanardTreeNode* const node) { const CanardTxQueueItem* const target = (const CanardTxQueueItem*) user_reference; const CanardTxQueueItem* const other = (const CanardTxQueueItem*) node; @@ -299,12 +299,12 @@ CANARD_PRIVATE int8_t txAVLPredicate(void* const user_reference, // NOSONAR Cav /// Returns the number of frames enqueued or error (i.e., =1 or <0). CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que, - CanardInstance* const ins, - const CanardMicrosecond deadline_usec, - const uint32_t can_id, - const CanardTransferID transfer_id, - const size_t payload_size, - const void* const payload) + CanardInstance* const ins, + const CanardMicrosecond deadline_usec, + const uint32_t can_id, + const CanardTransferID transfer_id, + const size_t payload_size, + const void* const payload) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT((payload != NULL) || (payload_size == 0)); @@ -346,12 +346,12 @@ CANARD_PRIVATE int32_t txPushSingleFrame(CanardTxQueue* const que, /// Produces a chain of Tx queue items for later insertion into the Tx queue. The tail is NULL if OOM. CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins, - const size_t presentation_layer_mtu, - const CanardMicrosecond deadline_usec, - const uint32_t can_id, - const CanardTransferID transfer_id, - const size_t payload_size, - const void* const payload) + const size_t presentation_layer_mtu, + const CanardMicrosecond deadline_usec, + const uint32_t can_id, + const CanardTransferID transfer_id, + const size_t payload_size, + const void* const payload) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(presentation_layer_mtu > 0U); @@ -369,8 +369,8 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins, out.size++; const size_t frame_payload_size_with_tail = ((payload_size_with_crc - offset) < presentation_layer_mtu) - ? txRoundFramePayloadSizeUp((payload_size_with_crc - offset) + 1U) // Padding in the last frame only. - : (presentation_layer_mtu + 1U); + ? txRoundFramePayloadSizeUp((payload_size_with_crc - offset) + 1U) // Padding in the last frame only. + : (presentation_layer_mtu + 1U); TxItem* const tqi = txAllocateQueueItem(ins, can_id, deadline_usec, frame_payload_size_with_tail); if (NULL == out.head) { @@ -444,13 +444,13 @@ CANARD_PRIVATE TxChain txGenerateMultiFrameChain(CanardInstance* const ins, /// Returns the number of frames enqueued or error. CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que, - CanardInstance* const ins, - const size_t presentation_layer_mtu, - const CanardMicrosecond deadline_usec, - const uint32_t can_id, - const CanardTransferID transfer_id, - const size_t payload_size, - const void* const payload) + CanardInstance* const ins, + const size_t presentation_layer_mtu, + const CanardMicrosecond deadline_usec, + const uint32_t can_id, + const CanardTransferID transfer_id, + const size_t payload_size, + const void* const payload) { CANARD_ASSERT((ins != NULL) && (que != NULL)); CANARD_ASSERT(presentation_layer_mtu > 0U); @@ -463,12 +463,12 @@ CANARD_PRIVATE int32_t txPushMultiFrame(CanardTxQueue* const que, if ((que->size + num_frames) <= que->capacity) // Bail early if we can see that we won't fit anyway. { const TxChain sq = txGenerateMultiFrameChain(ins, - presentation_layer_mtu, - deadline_usec, - can_id, - transfer_id, - payload_size, - payload); + presentation_layer_mtu, + deadline_usec, + can_id, + transfer_id, + payload_size, + payload); if (sq.tail != NULL) { CanardTxQueueItem* next = &sq.head->base; @@ -547,8 +547,8 @@ typedef struct /// Returns truth if the frame is valid and parsed successfully. False if the frame is not a valid UAVCAN/CAN frame. CANARD_PRIVATE bool rxTryParseFrame(const CanardMicrosecond timestamp_usec, - const CanardFrame* const frame, - RxFrameModel* const out) + const CanardFrame* const frame, + RxFrameModel* const out) { CANARD_ASSERT(frame != NULL); CANARD_ASSERT(frame->extended_can_id <= CAN_EXT_ID_MASK); @@ -603,7 +603,7 @@ CANARD_PRIVATE bool rxTryParseFrame(const CanardMicrosecond timestamp_usec, valid = valid && ((!out->start_of_transfer) || (INITIAL_TOGGLE_STATE == out->toggle)); // Anonymous transfers can be only single-frame transfers. valid = valid && - ((out->start_of_transfer && out->end_of_transfer) || (CANARD_NODE_ID_UNSET != out->source_node_id)); + ((out->start_of_transfer && out->end_of_transfer) || (CANARD_NODE_ID_UNSET != out->source_node_id)); // Non-last frames of a multi-frame transfer shall utilize the MTU fully. valid = valid && ((out->payload_size >= MFT_NON_LAST_FRAME_PAYLOAD_MIN) || out->end_of_transfer); // A frame that is a part of a multi-frame transfer cannot be empty (tail byte not included). @@ -613,7 +613,7 @@ CANARD_PRIVATE bool rxTryParseFrame(const CanardMicrosecond timestamp_usec, } CANARD_PRIVATE void rxInitTransferMetadataFromFrame(const RxFrameModel* const frame, - CanardTransferMetadata* const out_transfer) + CanardTransferMetadata* const out_transfer) { CANARD_ASSERT(frame != NULL); CANARD_ASSERT(frame->payload != NULL); @@ -640,10 +640,10 @@ CANARD_PRIVATE uint8_t rxComputeTransferIDDifference(const uint8_t a, const uint } CANARD_PRIVATE int8_t rxSessionWritePayload(CanardInstance* const ins, - CanardInternalRxSession* const rxs, - const size_t extent, - const size_t payload_size, - const void* const payload) + CanardInternalRxSession* const rxs, + const size_t extent, + const size_t payload_size, + const void* const payload) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(rxs != NULL); @@ -706,10 +706,10 @@ CANARD_PRIVATE void rxSessionRestart(CanardInstance* const ins, CanardInternalRx } CANARD_PRIVATE int8_t rxSessionAcceptFrame(CanardInstance* const ins, - CanardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const size_t extent, - CanardRxTransfer* const out_transfer) + CanardInternalRxSession* const rxs, + const RxFrameModel* const frame, + const size_t extent, + CanardRxTransfer* const out_transfer) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(rxs != NULL); @@ -776,12 +776,12 @@ CANARD_PRIVATE int8_t rxSessionAcceptFrame(CanardInstance* const ins, /// advantageous because it allows implementers to choose whatever solution works best for the specific application at /// hand, while the wire compatibility is still guaranteed by the high-level requirements given in the specification. CANARD_PRIVATE int8_t rxSessionUpdate(CanardInstance* const ins, - CanardInternalRxSession* const rxs, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - const CanardMicrosecond transfer_id_timeout_usec, - const size_t extent, - CanardRxTransfer* const out_transfer) + CanardInternalRxSession* const rxs, + const RxFrameModel* const frame, + const uint8_t redundant_transport_index, + const CanardMicrosecond transfer_id_timeout_usec, + const size_t extent, + CanardRxTransfer* const out_transfer) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(rxs != NULL); @@ -791,12 +791,12 @@ CANARD_PRIVATE int8_t rxSessionUpdate(CanardInstance* const ins, CANARD_ASSERT(frame->transfer_id <= CANARD_TRANSFER_ID_MAX); const bool tid_timed_out = (frame->timestamp_usec > rxs->transfer_timestamp_usec) && - ((frame->timestamp_usec - rxs->transfer_timestamp_usec) > transfer_id_timeout_usec); + ((frame->timestamp_usec - rxs->transfer_timestamp_usec) > transfer_id_timeout_usec); const bool not_previous_tid = rxComputeTransferIDDifference(rxs->transfer_id, frame->transfer_id) > 1; const bool need_restart = tid_timed_out || ((rxs->redundant_transport_index == redundant_transport_index) && - frame->start_of_transfer && not_previous_tid); + frame->start_of_transfer && not_previous_tid); if (need_restart) { @@ -827,10 +827,10 @@ CANARD_PRIVATE int8_t rxSessionUpdate(CanardInstance* const ins, } CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins, - CanardRxSubscription* const subscription, - const RxFrameModel* const frame, - const uint8_t redundant_transport_index, - CanardRxTransfer* const out_transfer) + CanardRxSubscription* const subscription, + const RxFrameModel* const frame, + const uint8_t redundant_transport_index, + CanardRxTransfer* const out_transfer) { CANARD_ASSERT(ins != NULL); CANARD_ASSERT(subscription != NULL); @@ -872,12 +872,12 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins, { CANARD_ASSERT(out == 0); out = rxSessionUpdate(ins, - subscription->sessions[frame->source_node_id], - frame, - redundant_transport_index, - subscription->transfer_id_timeout_usec, - subscription->extent, - out_transfer); + subscription->sessions[frame->source_node_id], + frame, + redundant_transport_index, + subscription->transfer_id_timeout_usec, + subscription->extent, + out_transfer); } } else @@ -908,9 +908,9 @@ CANARD_PRIVATE int8_t rxAcceptFrame(CanardInstance* const ins, return out; } -CANARD_PRIVATE int8_t + CANARD_PRIVATE int8_t rxSubscriptionPredicateOnPortID(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const CanardTreeNode* const node) + const CanardTreeNode* const node) { const CanardPortID sought = *((const CanardPortID*) user_reference); const CanardPortID other = ((const CanardRxSubscription*) node)->port_id; @@ -919,9 +919,9 @@ rxSubscriptionPredicateOnPortID(void* const user_reference, // NOSONAR Cavl API return (sought == other) ? 0 : NegPos[sought > other]; // NOLINT no narrowing conversion is taking place here } -CANARD_PRIVATE int8_t + CANARD_PRIVATE int8_t rxSubscriptionPredicateOnStruct(void* const user_reference, // NOSONAR Cavl API requires pointer to non-const. - const CanardTreeNode* const node) + const CanardTreeNode* const node) { return rxSubscriptionPredicateOnPortID(&((CanardRxSubscription*) user_reference)->port_id, node); } @@ -967,11 +967,11 @@ CanardTxQueue canardTxInit(const size_t capacity, const size_t mtu_bytes) } int32_t canardTxPush(CanardTxQueue* const que, - CanardInstance* const ins, - const CanardMicrosecond tx_deadline_usec, - const CanardTransferMetadata* const metadata, - const size_t payload_size, - const void* const payload) + CanardInstance* const ins, + const CanardMicrosecond tx_deadline_usec, + const CanardTransferMetadata* const metadata, + const size_t payload_size, + const void* const payload) { int32_t out = -CANARD_ERROR_INVALID_ARGUMENT; if ((ins != NULL) && (que != NULL) && (metadata != NULL) && ((payload != NULL) || (0U == payload_size))) @@ -983,24 +983,24 @@ int32_t canardTxPush(CanardTxQueue* const que, if (payload_size <= pl_mtu) { out = txPushSingleFrame(que, - ins, - tx_deadline_usec, - (uint32_t) maybe_can_id, - metadata->transfer_id, - payload_size, - payload); + ins, + tx_deadline_usec, + (uint32_t) maybe_can_id, + metadata->transfer_id, + payload_size, + payload); CANARD_ASSERT((out < 0) || (out == 1)); } else { out = txPushMultiFrame(que, - ins, - pl_mtu, - tx_deadline_usec, - (uint32_t) maybe_can_id, - metadata->transfer_id, - payload_size, - payload); + ins, + pl_mtu, + tx_deadline_usec, + (uint32_t) maybe_can_id, + metadata->transfer_id, + payload_size, + payload); CANARD_ASSERT((out < 0) || (out >= 2)); } } @@ -1045,15 +1045,15 @@ CanardTxQueueItem* canardTxPop(CanardTxQueue* const que, const CanardTxQueueItem } int8_t canardRxAccept(CanardInstance* const ins, - const CanardMicrosecond timestamp_usec, - const CanardFrame* const frame, - const uint8_t redundant_transport_index, - CanardRxTransfer* const out_transfer, - CanardRxSubscription** const out_subscription) + const CanardMicrosecond timestamp_usec, + const CanardFrame* const frame, + const uint8_t redundant_transport_index, + CanardRxTransfer* const out_transfer, + CanardRxSubscription** const out_subscription) { int8_t out = -CANARD_ERROR_INVALID_ARGUMENT; if ((ins != NULL) && (out_transfer != NULL) && (frame != NULL) && (frame->extended_can_id <= CAN_EXT_ID_MASK) && - ((frame->payload != NULL) || (0 == frame->payload_size))) + ((frame->payload != NULL) || (0 == frame->payload_size))) { RxFrameModel model = {0}; if (rxTryParseFrame(timestamp_usec, frame, &model)) @@ -1065,9 +1065,9 @@ int8_t canardRxAccept(CanardInstance* const ins, // is memcpy(). Excepting these two cases, the entire RX pipeline contains neither loops nor recursion. CanardRxSubscription* const sub = (CanardRxSubscription*) cavlSearch(&ins->rx_subscriptions[(size_t) model.transfer_kind], - &model.port_id, - &rxSubscriptionPredicateOnPortID, - NULL); + &model.port_id, + &rxSubscriptionPredicateOnPortID, + NULL); if (out_subscription != NULL) { *out_subscription = sub; // Expose selected instance to the caller. @@ -1097,11 +1097,11 @@ int8_t canardRxAccept(CanardInstance* const ins, } 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) + const CanardTransferKind transfer_kind, + const CanardPortID port_id, + const size_t extent, + const CanardMicrosecond transfer_id_timeout_usec, + CanardRxSubscription* const out_subscription) { int8_t out = -CANARD_ERROR_INVALID_ARGUMENT; const size_t tk = (size_t) transfer_kind; @@ -1124,9 +1124,9 @@ int8_t canardRxSubscribe(CanardInstance* const ins, out_subscription->sessions[i] = NULL; } const CanardTreeNode* const res = cavlSearch(&ins->rx_subscriptions[tk], - out_subscription, - &rxSubscriptionPredicateOnStruct, - &avlTrivialFactory); + out_subscription, + &rxSubscriptionPredicateOnStruct, + &avlTrivialFactory); (void) res; CANARD_ASSERT(res == &out_subscription->base); out = (out > 0) ? 0 : 1; @@ -1136,8 +1136,8 @@ int8_t canardRxSubscribe(CanardInstance* const ins, } int8_t canardRxUnsubscribe(CanardInstance* const ins, - const CanardTransferKind transfer_kind, - const CanardPortID port_id) + const CanardTransferKind transfer_kind, + const CanardPortID port_id) { int8_t out = -CANARD_ERROR_INVALID_ARGUMENT; const size_t tk = (size_t) transfer_kind; @@ -1165,3 +1165,45 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins, } return out; } + +CanardFilter canardMakeFilterForSubject(const CanardPortID subject_id) +{ + CanardFilter out = {0}; + + out.extended_can_id = ((uint32_t) subject_id) << OFFSET_SUBJECT_ID; + out.extended_mask = FLAG_SERVICE_NOT_MESSAGE | FLAG_RESERVED_07 | (CANARD_SUBJECT_ID_MAX << OFFSET_SUBJECT_ID); + + return out; +} + +CanardFilter canardMakeFilterForService(const CanardPortID service_id, const CanardNodeID local_node_id) +{ + CanardFilter out = {0}; + + out.extended_can_id = FLAG_SERVICE_NOT_MESSAGE | (((uint32_t) service_id) << OFFSET_SERVICE_ID) | + (((uint32_t) local_node_id) << OFFSET_DST_NODE_ID); + out.extended_mask = FLAG_SERVICE_NOT_MESSAGE | FLAG_RESERVED_23 | (CANARD_SERVICE_ID_MAX << OFFSET_SERVICE_ID) | + (CANARD_NODE_ID_MAX << OFFSET_DST_NODE_ID); + + return out; +} + +CanardFilter canardMakeFilterForServices(const CanardNodeID local_node_id) +{ + CanardFilter out = {0}; + + out.extended_can_id = FLAG_SERVICE_NOT_MESSAGE | (((uint32_t) local_node_id) << OFFSET_DST_NODE_ID); + out.extended_mask = FLAG_SERVICE_NOT_MESSAGE | FLAG_RESERVED_23 | (CANARD_NODE_ID_MAX << OFFSET_DST_NODE_ID); + + return out; +} + +CanardFilter canardConsolidateFilters(const CanardFilter* a, const CanardFilter* b) +{ + CanardFilter out = {0}; + + out.extended_mask = a->extended_mask & b->extended_mask & ~(a->extended_can_id ^ b->extended_can_id); + out.extended_can_id = a->extended_can_id & out.extended_mask; + + return out; +} diff --git a/libcanard/canard.h b/libcanard/canard.h index 39f4be1..897ae17 100644 --- a/libcanard/canard.h +++ b/libcanard/canard.h @@ -383,6 +383,20 @@ struct CanardInstance CanardTreeNode* rx_subscriptions[CANARD_NUM_TRANSFER_KINDS]; }; +/// CAN acceptance filter configuration with an extended 29-bit ID utilizing an ID + mask filter scheme. +/// Filter configuration can be programmed into a CAN controller to filter out irrelevant messages in hardware. +/// This allows the software application to reduce CPU load spent on processing irrelevant messages. +typedef struct CanardFilter +{ + /// 29-bit extended ID. Defines the extended CAN ID to filter incoming frames against. + /// The bits above 29-th shall be zero. + uint32_t extended_can_id; + /// 29-bit extended mask. Defines the bitmask used to enable/disable bits used to filter messages. + /// Only bits that are enabled are compared to the extended_can_id for filtering. + /// The bits above 29-th shall be zero. + uint32_t extended_mask; +} CanardFilter; + /// Construct a new library instance. /// The default values will be assigned as specified in the structure field documentation. /// If any of the pointers are NULL, the behavior is undefined. @@ -635,6 +649,52 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins, const CanardTransferKind transfer_kind, const CanardPortID port_id); +/// Generate an acceptance filter configuration to accept a specific subject ID. +/// +/// Complex applications will likely subscribe to more subject IDs than there are +/// acceptance filters available in the CAN hardware. In this case, the application +/// should implement filter consolidation. See canardConsolidateFilters() +/// as well as the UAVCAN specification for details. +CanardFilter canardMakeFilterForSubject(const CanardPortID subject_id); + +/// Generate an acceptance filter configuration to accept a specific service. +/// +/// Complex applications will likely subscribe to more service IDs than there are +/// acceptance filters available in the CAN hardware. In this case, the application +/// should implement filter consolidation. See canardConsolidateFilters() +/// as well as the UAVCAN specification for details. +/// +/// Users may prefer to instead use a catch-all acceptance filter configuration for accepting +/// all service requests and responses targeted at the specified local node ID. +/// See canardMakeFilterForServices() for this. +CanardFilter canardMakeFilterForService(const CanardPortID service_id, const CanardNodeID local_node_id); + +/// Generate an acceptance filter configuration to accept all service +/// requests or responses targeted to the specified local node ID. +/// +/// Due to the relatively low frequency of service transfers expected on a network, +/// and the fact that a service directed at a specific node is not likely to be rejected by that node, +/// a user may prefer to use this over canardMakeFilterForService() +/// in order to simplify the API usage and reduce the number of required hardware CAN acceptance filters. +CanardFilter canardMakeFilterForServices(const CanardNodeID local_node_id); + +/// Consolidate two acceptance filter configurations into a single configuration. +/// +/// Complex applications will likely subscribe to more subject IDs than there are +/// acceptance filters available in the CAN hardware. In this case, the application +/// should implement filter consolidation. While this may make it impossible to create +/// a 'perfect' filter that only accepts desired subject IDs, the application should apply +/// consolidation in a manner that minimizes the number of undesired messages that pass +/// through the hardware acceptance filters and require software filtering (implemented by canardRxSubscribe). +/// +/// While optimal choice of filter consolidation is a function of the number of available hardware filters, +/// the set of transfers needed by the application, and the expected frequency of occurence +/// of all possible distinct transfers on the bus, it is possible to generate a quasi-optimal configuration +/// if information about the frequency of occurence of different transfers is not known. +/// For details, see the "Automatic hardware acceptance filter configuration" note under the UAVCAN/CAN section +/// in the Transport Layer chapter of the UAVCAN specification. +CanardFilter canardConsolidateFilters(const CanardFilter* a, const CanardFilter* b); + #ifdef __cplusplus } #endif