Skip to content

Commit

Permalink
Add acceptance filter configuration helpers (#171)
Browse files Browse the repository at this point in the history
Adds helper API functions for configuring CAN controller hardware acceptance filters.

Closes #169

Co-authored-by: Pavel Kirienko <pavel.kirienko@gmail.com>
  • Loading branch information
coderkalyan and pavel-kirienko authored Dec 4, 2021
1 parent e858d79 commit 13cb7b1
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 2 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, &register_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.
Expand All @@ -228,6 +247,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()`.
Expand Down
42 changes: 42 additions & 0 deletions libcanard/canard.c
Original file line number Diff line number Diff line change
Expand Up @@ -1202,3 +1202,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;
}
58 changes: 58 additions & 0 deletions libcanard/canard.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -635,6 +649,50 @@ int8_t canardRxUnsubscribe(CanardInstance* const ins,
const CanardTransferKind transfer_kind,
const CanardPortID port_id);

/// Utilities for generating CAN controller hardware acceptance filter configurations
/// to accept specific subjects, services, or nodes.
///
/// 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.

/// Generate an acceptance filter configuration to accept a specific subject ID.
CanardFilter canardMakeFilterForSubject(const CanardPortID subject_id);

/// Generate an acceptance filter configuration to accept both requests and responses for a specific service.
///
/// 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 and 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* const a, const CanardFilter* const b);

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,6 @@ gen_test_matrix(test_private_crc_table
"-Wno-missing-declarations")

gen_test_matrix(test_public
"test_public_tx.cpp;test_public_rx.cpp;test_public_roundtrip.cpp;test_self.cpp"
"test_public_tx.cpp;test_public_rx.cpp;test_public_roundtrip.cpp;test_self.cpp;test_public_filters.cpp"
""
"-Wmissing-declarations")
71 changes: 71 additions & 0 deletions tests/test_public_filters.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// This software is distributed under the terms of the MIT License.
// Copyright (c) 2016-2021 UAVCAN Development Team.

#include "exposed.hpp"
#include "helpers.hpp"
#include "catch.hpp"

namespace
{
constexpr std::uint32_t OFFSET_PRIORITY = 26U;
constexpr std::uint32_t OFFSET_SUBJECT_ID = 8U;
constexpr std::uint32_t OFFSET_SERVICE_ID = 14U;
constexpr std::uint32_t OFFSET_DST_NODE_ID = 7U;

constexpr std::uint32_t FLAG_SERVICE_NOT_MESSAGE = std::uint32_t(1) << 25U;
constexpr std::uint32_t FLAG_ANONYMOUS_MESSAGE = std::uint32_t(1) << 24U;
constexpr std::uint32_t FLAG_REQUEST_NOT_RESPONSE = std::uint32_t(1) << 24U;
constexpr std::uint32_t FLAG_RESERVED_23 = std::uint32_t(1) << 23U;
constexpr std::uint32_t FLAG_RESERVED_07 = std::uint32_t(1) << 7U;

TEST_CASE("FilterSubject")
{
const std::uint16_t heartbeat_subject_id = 7509;
CanardFilter heartbeat_config = canardMakeFilterForSubject(heartbeat_subject_id);
REQUIRE((heartbeat_config.extended_can_id &
static_cast<std::uint32_t>(heartbeat_subject_id << OFFSET_SUBJECT_ID)) != 0);
REQUIRE((heartbeat_config.extended_mask & FLAG_SERVICE_NOT_MESSAGE) != 0);
REQUIRE((heartbeat_config.extended_mask & FLAG_RESERVED_07) != 0);
REQUIRE((heartbeat_config.extended_mask & (CANARD_SUBJECT_ID_MAX << OFFSET_SUBJECT_ID)) != 0);
}

TEST_CASE("FilterService")
{
const std::uint16_t access_service_id = 7509;
const std::uint16_t node_id = 42;
CanardFilter access_config = canardMakeFilterForService(access_service_id, node_id);
REQUIRE((access_config.extended_can_id & static_cast<std::uint32_t>(access_service_id << OFFSET_SERVICE_ID)) != 0);
REQUIRE((access_config.extended_can_id & static_cast<std::uint32_t>(node_id << OFFSET_DST_NODE_ID)) != 0);
REQUIRE((access_config.extended_can_id & FLAG_SERVICE_NOT_MESSAGE) != 0);
REQUIRE((access_config.extended_mask & FLAG_SERVICE_NOT_MESSAGE) != 0);
REQUIRE((access_config.extended_mask & FLAG_RESERVED_23) != 0);
REQUIRE((access_config.extended_mask & static_cast<std::uint32_t>(CANARD_SERVICE_ID_MAX << OFFSET_SERVICE_ID)) !=
0);
REQUIRE((access_config.extended_mask & static_cast<std::uint32_t>(CANARD_NODE_ID_MAX << OFFSET_DST_NODE_ID)) != 0);
}

TEST_CASE("FilterServices")
{
const std::uint8_t node_id = 42;
CanardFilter access_config = canardMakeFilterForServices(node_id);
REQUIRE((access_config.extended_can_id & static_cast<std::uint32_t>(node_id << OFFSET_DST_NODE_ID)) != 0);
REQUIRE((access_config.extended_can_id & FLAG_SERVICE_NOT_MESSAGE) != 0);
REQUIRE((access_config.extended_mask & FLAG_SERVICE_NOT_MESSAGE) != 0);
REQUIRE((access_config.extended_mask & FLAG_RESERVED_23) != 0);
REQUIRE((access_config.extended_mask & static_cast<std::uint32_t>(CANARD_NODE_ID_MAX << OFFSET_DST_NODE_ID)) != 0);
}

TEST_CASE("Consolidate")
{
const std::uint16_t heartbeat_subject_id = 7509;
CanardFilter heartbeat_config = canardMakeFilterForSubject(heartbeat_subject_id);

const std::uint16_t access_service_id = 7509;
const std::uint8_t node_id = 42;
CanardFilter access_config = canardMakeFilterForService(access_service_id, node_id);

CanardFilter combined = canardConsolidateFilters(&heartbeat_config, &access_config);
REQUIRE((combined.extended_mask | heartbeat_config.extended_mask) == heartbeat_config.extended_mask);
REQUIRE((combined.extended_mask | access_config.extended_mask) == access_config.extended_mask);
}
} // namespace

0 comments on commit 13cb7b1

Please sign in to comment.