firmware
IEM Firmware Documentation
Loading...
Searching...
No Matches
can_codec

CAN code generator. More...

Collaboration diagram for can_codec:

Topics

 Can_1
 Can_2
 Can_3
 Can_codec_static_checks
 Can_codec_can_1
 Can_codec_can_2
 Can_codec_can_3

Detailed Description

CAN code generator.

RE: Jamie Pruett
+1 (312) 882-5212
pruet.nosp@m.t4@i.nosp@m.llino.nosp@m.is.e.nosp@m.du
Please reach out with any questions :D

CAN Codegen

Todo
pruett4 Update all of this for Bazel

The CAN codegen library aims to minimize development/debugging headaches when dealing with CAN message data. C code is generated from a DBC file (CAN message database) to pack real values into their encoded state in a CAN message for transmission and to unpack encoded signals in a received CAN message into real values. Skip to the usage examples section to get started quickly or keep reading for a more detailed explanation :)

The codegen.py Python script generates C source/header files for each DBC in the can_codec/dbc/ directory. There's one DBC for each CAN bus (CAN bus refresher) - can_1.c and can_1.h contain generated code for all the messages on CAN 1, can_2.c/h for CAN 2, etc.

CAN messages follow (or should follow) this naming scheme in DBCs: [sending node]_[message_name]. All identifiers generated for a message (structs, functions, defines, etc.) use the message name as it appears in the DBC, prefixed with the name of the CAN bus, i.e. CAN_1_VNAV_TV_INPUTS_ID, struct can_1_vnav_tv_inputs, can_1_vnav_tv_inputs_pack(). See the codegen example for an example of all the generated code for a message.

[bus_name].h:

  • Defines for message ID, length (in bytes), and whether or not the message has an extended/29-bit ID.
  • Two structs for each message:
    • One contains unpacked/decoded values for the signals in a CAN message.
    • The other (_packed suffix) represents the message in its packed state. There are very few reasons to use the _packed struct for a message in application code - they're mostly for internal use
      • _packed structs use GCC's __attribute__((packed)) so a pointer to a uint8_t array can be cast to a pointer to a _packed struct without issues and so padding can be manually inserted to match the CAN message definition
  • Both structs have detailed Doxygen documentation describing the units/range (very useful) for each signal in a message as well as the scale/offset parameters from the DBC that define how signals are encoded.

[bus_name].c:

  • [message_name]_pack()
    • Packs "real" values from an instance of struct [message_name] into a uint8_t array to be sent as a CAN message
    • Parameters:
      • Pointer to uint8_t destination array
      • Pointer to struct [message_name] (contains values to pack)
      • Length of destination array
    • Returns 0 if the values were successfully packed or a nonzero error code on failure
  • [message_name]_unpack()
    • Unpacks encoded values from a uint8_t array of data received over CAN into real values in an instance of struct [message_name]
    • Same as [message_name]_pack(), but in reverse
    • Parameters:
      • Pointer to struct [message_name] (will be populated with unpacked signal values)
      • Pointer to uint8_t source array
      • Length of source array
    • Returns 0 if the values were successfully unpacked or a nonzero error code on failure

Codegen Usage Example

This is the code generated in can_1.h for the (current version of the) VectorNAV Buddy Board TV inputs message.

// can_1.h
#define CAN_1_VNAV_TV_INPUTS_ID 0x210
#define CAN_1_VNAV_TV_INPUTS_LENGTH 8U
#define CAN_1_VNAV_TV_INPUTS_IS_EXT false
// (...)
struct can_1_vnav_tv_inputs {
float ax;
float ay;
float gz;
float vx;
uint8_t vnav_status;
};
// (Doxygen)
struct __attribute__((packed)) can_1_vnav_tv_inputs_packed {
uint16_t ax : 16;
uint16_t ay : 16;
uint16_t gz : 16;
uint16_t vx : 14;
uint8_t vnav_status : 2;
};
// (...)
// (Doxygen in can_1.c)
int can_1_vnav_tv_inputs_pack(uint8_t* dst_ptr, const struct can_1_vnav_tv_inputs* src_ptr, size_t size);
int can_1_vnav_tv_inputs_unpack(struct can_1_vnav_tv_inputs* dst_ptr, const uint8_t* src_ptr, size_t size);

Example usage - unpacking a received CAN message:

FDCAN_RxHeaderTypeDef rx_header;
uint8_t rx_data[8];
// Receive message with HAL function call (as usual)
HAL_FDCAN_GetRxMessage(&hfdcan1, FDCAN_RX_FIFO0, &rx_header, rx_data);
switch (rx_header.Identifier) {
case CAN_1_VNAV_TV_INPUTS_ID: {
struct can_1_vnav_tv_inputs vnav_msg;
can_1_vnav_tv_inputs_unpack(&vnav_msg, rx_data, rx_header.DLC);
// vnav_msg now contains the decoded CAN message values, add application-specific code here
}
// (handle other messages as needed)
}
volatile CAN_RxHeaderTypeDef rx_header
Definition main.c:106
volatile uint8_t rx_data[8]
Definition main.c:108
FDCAN_HandleTypeDef hfdcan1
Definition main.c:58

Example usage - packing a CAN message for transmission:

FDCAN_TxHeaderTypeDef tx_header;
tx_header.Identifier = CAN_1_VNAV_TV_INPUTS_ID;
tx_header.DLC = CAN_1_VNAV_TV_INPUTS_LENGTH;
// (rest of tx_header initialization)
uint8_t tx_data[CAN_1_VNAV_TV_INPUTS_LENGTH];
struct can_1_vnav_tv_inputs vnav_msg;
// Set values (real units/whatever the Doxygen says) in the unpacked struct
vnav_msg.ax = vnav_uart_data.ax; // In m/s^2 (float)
vnav_msg.ay = vnav_uart_data.ay; // In m/s^2 (float)
vnav_msg.gz = vnav_uart_data.gz; // In rad/s (float)
vnav_msg.vx = vnav_uart_data.vx; // In m/s (float)
vnav_msg.vnav_status = vnav_status; // enum (type of struct member is uint8_t)
can_1_vnav_tv_inputs_pack(tx_data, &vnav_msg, CAN_1_VNAV_TV_INPUTS_LENGTH);
// Transmit message with HAL function call (as usual)
HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &tx_header, tx_data);
uint8_t tx_data[8]
Definition main.c:107
CAN_TxHeaderTypeDef tx_header
Definition main.c:105

Editing DBCs

Wiki page on the way!

The easiest way to edit DBC files is to use CANdb++ Editor (the free version of CANdb++). Vector makes it a little difficult to find on their website - the installer (Windows only :( ) is available if you search for it in their download center. Please follow the [](CAN message/signal naming convention) and (especially for CAN 1) the [](CAN ID assignment scheme) when adding new messages to a DBC!

Generating Code After DBC Changes

  1. Make sure you have Python installed
  2. Create, activate, and set up a Python virtual environment (can be reused for future code generation) by running the following commands:
    1. python -m venv venv - creates a new virtual environment in the current directory under the venv/ subdirectory
    2. Linux/MacOS: source venv/bin/activate, Windows: .\venv\Scripts\activate - future Python commands will now use the virtual environment instead of your system Python until you run deactivate. If you're on Windows and using Powershell, you'll have to enable running scripts on your system first. Command Prompt (cmd) should be fine out of the box
    3. In the project root directory, run pip install -r requirements.txt to install necessary Python packages
  3. In the can_codec/ subdirectory, run python codegen.py. That's it!

CAN Bus Refresher

  • CAN 1:
    • Car-critical messages
    • All message received by a CAN node other than the logger or Plex (dashboard display) should be on CAN 1
    • Limited high-importance logging and board status messages
  • CAN 2:
    • Logging bus
    • As a general rule, only the logger and Plex should be receiving messages on CAN 2
  • CAN 3:
    • Inverter CAN
    • Safety Board and AMK (all 4 inverters) are the only nodes on CAN 3 to maximize stability/reliability and minimize latency
    • Logger is not on CAN 3 - all CAN 3 messages transmitted or received by Safety are echoed on CAN 2 for logging