Skip to content

[DRAFT] Add build option to build for can classic #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions differential_pressure_sensor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ add_definitions(
-DNODE_NAME="org.opencyphal.demos.differential_pressure"
)

option(CAN_CLASSIC "Build for can classic" ON) # Enabled by default

if(CAN_CLASSIC)
add_definitions(-DCAN_CLASSIC)
endif(CAN_CLASSIC)


# Transpile DSDL into C using Nunavut. Install Nunavut as follows: pip install nunavut.
# Alternatively, you can invoke the transpiler manually or use https://nunaweb.opencyphal.org.
find_package(nnvg REQUIRED)
Expand Down
3 changes: 2 additions & 1 deletion differential_pressure_sensor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ This demo supports only Cyphal/CAN at the moment, but it can be extended to supp
You will need GNU/Linux, CMake, a C11 compiler, [Yakut](https://github.com/OpenCyphal/yakut),
and [SocketCAN utils](https://github.com/linux-can/can-utils).

Build the demo as follows:
Build the demo as follows
(the default builds for can-fd, you can pass `-DCAN_CLASSIC=y` to cmake to build for can classic):

```bash
git clone --recursive https://github.com/OpenCyphal/demos
Expand Down
110 changes: 105 additions & 5 deletions differential_pressure_sensor/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@
#include <uavcan/node/port/List_0_1.h>
#include <uavcan/_register/Access_1_0.h>
#include <uavcan/_register/List_1_0.h>
#ifdef CAN_CLASSIC
#include <uavcan/pnp/NodeIDAllocationData_1_0.h>
#else
#include <uavcan/pnp/NodeIDAllocationData_2_0.h>

#endif /* CAN_CLASSIC */
// Use /sample/ instead of /unit/ if you need timestamping.
#include <uavcan/si/unit/pressure/Scalar_1_0.h>
#include <uavcan/si/unit/temperature/Scalar_1_0.h>
Expand All @@ -43,6 +46,43 @@
/// For CAN FD the queue can be smaller.
#define CAN_TX_QUEUE_CAPACITY 100

#ifdef CAN_CLASSIC
#define CRC64WE_POLY 0x92F0E1EAABEA3693
#define CRC64WE_INIT 0xFFFFFFFFFFFFFFFF
#define CRC64WE_XOROUT 0xFFFFFFFFFFFFFFFF

static uint64_t crc64we_table[256];

static void init_crc64we_table()
{
for (uint64_t i = 0; i < 256; i++)
{
uint64_t crc = i;
for (uint64_t j = 0; j < 8; j++)
{
if (crc & 1)
crc = (crc >> 1) ^ CRC64WE_POLY;
else
crc = crc >> 1;
}
crc64we_table[i] = crc;
}
}

static uint64_t crc64we(const uint8_t* data, size_t length)
{
uint64_t crc = CRC64WE_INIT;

for (size_t i = 0; i < length; i++)
{
uint8_t byte = data[i];
crc = crc64we_table[(crc ^ byte) & 0xFF] ^ (crc >> 8);
}

return crc ^ CRC64WE_XOROUT;
}
#endif /* CAN_CLASSIC */

/// We keep the state of the application here. Feel free to use static variables instead if desired.
typedef struct State
{
Expand Down Expand Up @@ -255,20 +295,35 @@ static void handle1HzLoop(State* const state, const CanardMicrosecond monotonic_
// Note that a high-integrity/safety-certified application is unlikely to be able to rely on this feature.
if (rand() > RAND_MAX / 2) // NOLINT
{
// Note that this will only work over CAN FD. If you need to run PnP over Classic CAN, use message v1.0.
#ifdef CAN_CLASSIC
uavcan_pnp_NodeIDAllocationData_1_0 msg = {0};
uint8_t uid[uavcan_node_GetInfo_Response_1_0_unique_id_ARRAY_CAPACITY_] = {0};
getUniqueID(uid);
init_crc64we_table();
uint64_t uid_hash = crc64we(uid, sizeof uid);
memcpy(&msg.unique_id_hash, &uid_hash, 5);
uint8_t serialized[uavcan_pnp_NodeIDAllocationData_1_0_SERIALIZATION_BUFFER_SIZE_BYTES_] = {0};
size_t serialized_size = sizeof(serialized);
const int8_t err = uavcan_pnp_NodeIDAllocationData_1_0_serialize_(&msg, &serialized[0], &serialized_size);
#else
uavcan_pnp_NodeIDAllocationData_2_0 msg = {0};
msg.node_id.value = UINT16_MAX;
getUniqueID(msg.unique_id);
uint8_t serialized[uavcan_pnp_NodeIDAllocationData_2_0_SERIALIZATION_BUFFER_SIZE_BYTES_] = {0};
size_t serialized_size = sizeof(serialized);
const int8_t err = uavcan_pnp_NodeIDAllocationData_2_0_serialize_(&msg, &serialized[0], &serialized_size);
const int8_t err = uavcan_pnp_NodeIDAllocationData_2_0_serialize_(&msg, &serialized[0], &serialized_size);
#endif /* CAN_CLASSIC */
assert(err >= 0);
if (err >= 0)
{
const CanardTransferMetadata meta = {
.priority = CanardPrioritySlow,
.transfer_kind = CanardTransferKindMessage,
#ifdef CAN_CLASSIC
.port_id = uavcan_pnp_NodeIDAllocationData_1_0_FIXED_PORT_ID_,
#else
.port_id = uavcan_pnp_NodeIDAllocationData_2_0_FIXED_PORT_ID_,
#endif
.remote_node_id = CANARD_NODE_ID_UNSET,
.transfer_id = (CanardTransferID) (state->next_transfer_id.uavcan_pnp_allocation++),
};
Expand Down Expand Up @@ -381,7 +436,33 @@ static void handle01HzLoop(State* const state, const CanardMicrosecond monotonic
}
}
}

#ifdef CAN_CLASSIC
static void processMessagePlugAndPlayNodeIDAllocation(State* const state,
const uavcan_pnp_NodeIDAllocationData_1_0* const msg)
{
uint8_t uid[uavcan_node_GetInfo_Response_1_0_unique_id_ARRAY_CAPACITY_] = {0};
getUniqueID(uid);
init_crc64we_table();
uint64_t uid_hash = crc64we(uid, sizeof uid);
if (msg->allocated_node_id.count > 0 && (msg->allocated_node_id.elements[0].value <= CANARD_NODE_ID_MAX) &&
(memcmp(&uid_hash, &msg->unique_id_hash, 5 /*sizeof(uid)*/) == 0))
{
printf("Got PnP node-ID allocation: %d\n", msg->allocated_node_id.elements[0].value);
state->canard.node_id = (CanardNodeID) msg->allocated_node_id.elements[0].value;
// Store the value into the non-volatile storage.
uavcan_register_Value_1_0 reg = {0};
uavcan_register_Value_1_0_select_natural16_(&reg);
reg.natural16.value.elements[0] = msg->allocated_node_id.elements[0].value;
reg.natural16.value.count = 1;
registerWrite("uavcan.node.id", &reg);
// We no longer need the subscriber, drop it to free up the resources (both memory and CPU time).
(void) canardRxUnsubscribe(&state->canard,
CanardTransferKindMessage,
uavcan_pnp_NodeIDAllocationData_1_0_FIXED_PORT_ID_);
}
// Otherwise, ignore it: either it is a request from another node or it is a response to another node.
}
#else
static void processMessagePlugAndPlayNodeIDAllocation(State* const state,
const uavcan_pnp_NodeIDAllocationData_2_0* const msg)
{
Expand All @@ -404,6 +485,7 @@ static void processMessagePlugAndPlayNodeIDAllocation(State* const
}
// Otherwise, ignore it: either it is a request from another node or it is a response to another node.
}
#endif /* CAN_CLASSIC */

static uavcan_node_ExecuteCommand_Response_1_1 processRequestExecuteCommand(
const uavcan_node_ExecuteCommand_Request_1_1* req)
Expand Down Expand Up @@ -525,10 +607,17 @@ static void processReceivedTransfer(State* const state, const CanardRxTransfer*
if (transfer->metadata.transfer_kind == CanardTransferKindMessage)
{
size_t size = transfer->payload_size;
#ifdef CAN_CLASSIC
if (transfer->metadata.port_id == uavcan_pnp_NodeIDAllocationData_1_0_FIXED_PORT_ID_)
{
uavcan_pnp_NodeIDAllocationData_1_0 msg = {0};
if (uavcan_pnp_NodeIDAllocationData_1_0_deserialize_(&msg, transfer->payload, &size) >= 0)
#else
if (transfer->metadata.port_id == uavcan_pnp_NodeIDAllocationData_2_0_FIXED_PORT_ID_)
{
uavcan_pnp_NodeIDAllocationData_2_0 msg = {0};
if (uavcan_pnp_NodeIDAllocationData_2_0_deserialize_(&msg, transfer->payload, &size) >= 0)
#endif /* CAN_CLASSIC */
{
processMessagePlugAndPlayNodeIDAllocation(state, &msg);
}
Expand Down Expand Up @@ -698,18 +787,24 @@ int main(const int argc, char* const argv[])
// Configure the transport by reading the appropriate standard registers.
uavcan_register_Value_1_0_select_natural16_(&val);
val.natural16.value.count = 1;
#ifdef CAN_CLASSIC
val.natural16.value.elements[0] = CANARD_MTU_CAN_CLASSIC;
#else
val.natural16.value.elements[0] = CANARD_MTU_CAN_FD;
#endif
registerRead("uavcan.can.mtu", &val);
assert(uavcan_register_Value_1_0_is_natural16_(&val) && (val.natural16.value.count == 1));
// We also need the bitrate configuration register. In this demo we can't really use it but an embedded application
// should define "uavcan.can.bitrate" of type natural32[2]; the second value is 0/ignored if CAN FD not supported.
const char iface_name[] = "vcan0";
const int sock[CAN_REDUNDANCY_FACTOR] = {
socketcanOpen("vcan0", val.natural16.value.elements[0] > CANARD_MTU_CAN_CLASSIC) //
socketcanOpen(iface_name, val.natural16.value.elements[0] > CANARD_MTU_CAN_CLASSIC) //
};
for (uint8_t ifidx = 0; ifidx < CAN_REDUNDANCY_FACTOR; ifidx++)
{
if (sock[ifidx] < 0)
{
printf("Unable to open can interface '%s'", iface_name);
return -sock[ifidx];
}
state.canard_tx_queues[ifidx] = canardTxInit(CAN_TX_QUEUE_CAPACITY, val.natural16.value.elements[0]);
Expand All @@ -734,8 +829,13 @@ int main(const int argc, char* const argv[])
const int8_t res = //
canardRxSubscribe(&state.canard,
CanardTransferKindMessage,
#ifdef CAN_CLASSIC
uavcan_pnp_NodeIDAllocationData_1_0_FIXED_PORT_ID_,
uavcan_pnp_NodeIDAllocationData_1_0_EXTENT_BYTES_,
#else
uavcan_pnp_NodeIDAllocationData_2_0_FIXED_PORT_ID_,
uavcan_pnp_NodeIDAllocationData_2_0_EXTENT_BYTES_,
#endif
CANARD_DEFAULT_TRANSFER_ID_TIMEOUT_USEC,
&rx);
if (res < 0)
Expand Down