Skip to content

Instantly share code, notes, and snippets.

@BrechtSerckx
Created April 23, 2025 20:05
Show Gist options
  • Save BrechtSerckx/6d23b859b3192aa284aa145c497aafe5 to your computer and use it in GitHub Desktop.
Save BrechtSerckx/6d23b859b3192aa284aa145c497aafe5 to your computer and use it in GitHub Desktop.
All cyclic messages
#include <Arduino.h>
#include "ODriveCAN.h"
// Documentation for this example can be found here:
// https://docs.odriverobotics.com/v/latest/guides/arduino-can-guide.html
/* Configuration of example sketch -------------------------------------------*/
// CAN bus baudrate. Make sure this matches for every device on the bus
#define CAN_BAUDRATE 250000
// ODrive node_id for odrv0
#define ODRV0_NODE_ID 0
// Uncomment below the line that corresponds to your hardware.
// See also "Board-specific settings" to adapt the details for your hardware setup.
// #define IS_TEENSY_BUILTIN // Teensy boards with built-in CAN interface (e.g. Teensy 4.1). See below to select which interface to use.
// #define IS_ARDUINO_BUILTIN // Arduino boards with built-in CAN interface (e.g. Arduino Uno R4 Minima)
// #define IS_MCP2515 // Any board with external MCP2515 based extension module. See below to configure the module.
// #define IS_STM32_BUILTIN // STM32 boards with built-in CAN interface (e.g. STM32F4 Discovery).
/* Board-specific includes ---------------------------------------------------*/
#if defined(IS_TEENSY_BUILTIN) + defined(IS_ARDUINO_BUILTIN) + defined(IS_MCP2515) + defined(IS_STM32_BUILTIN) != 1
#warning "Select exactly one hardware option at the top of this file."
#if CAN_HOWMANY > 0 || CANFD_HOWMANY > 0
#define IS_ARDUINO_BUILTIN
#warning "guessing that this uses HardwareCAN"
#else
#error "cannot guess hardware version"
#endif
#endif
#ifdef IS_ARDUINO_BUILTIN
// See https://github.com/arduino/ArduinoCore-API/blob/master/api/HardwareCAN.h
// and https://github.com/arduino/ArduinoCore-renesas/tree/main/libraries/Arduino_CAN
#include <Arduino_CAN.h>
#include <ODriveHardwareCAN.hpp>
#endif // IS_ARDUINO_BUILTIN
#ifdef IS_MCP2515
// See https://github.com/sandeepmistry/arduino-CAN/
#include "MCP2515.h"
#include "ODriveMCPCAN.hpp"
#endif // IS_MCP2515
#ifdef IS_TEENSY_BUILTIN
// See https://github.com/tonton81/FlexCAN_T4
// clone https://github.com/tonton81/FlexCAN_T4.git into /src
#include <FlexCAN_T4.h>
#include "ODriveFlexCAN.hpp"
struct ODriveStatus; // hack to prevent teensy compile error
#endif // IS_TEENSY_BUILTIN
#ifdef IS_STM32_BUILTIN
// See https://github.com/pazi88/STM32_CAN
#include <STM32_CAN.h>
#include "ODriveSTM32CAN.hpp"
#endif // IS_STM32_BUILTIN
/* Board-specific settings ---------------------------------------------------*/
/* Teensy */
#ifdef IS_TEENSY_BUILTIN
FlexCAN_T4<CAN1, RX_SIZE_256, TX_SIZE_16> can_intf;
bool setupCan() {
can_intf.begin();
can_intf.setBaudRate(CAN_BAUDRATE);
can_intf.setMaxMB(16);
can_intf.enableFIFO();
can_intf.enableFIFOInterrupt();
can_intf.onReceive(onCanMessage);
return true;
}
#endif // IS_TEENSY_BUILTIN
/* MCP2515-based extension modules -*/
#ifdef IS_MCP2515
MCP2515Class& can_intf = CAN;
// chip select pin used for the MCP2515
#define MCP2515_CS 10
// interrupt pin used for the MCP2515
// NOTE: not all Arduino pins are interruptable, check the documentation for your board!
#define MCP2515_INT 2
// freqeuncy of the crystal oscillator on the MCP2515 breakout board.
// common values are: 16 MHz, 12 MHz, 8 MHz
#define MCP2515_CLK_HZ 8000000
static inline void receiveCallback(int packet_size) {
if (packet_size > 8) {
return; // not supported
}
CanMsg msg = {.id = (unsigned int)CAN.packetId(), .len = (uint8_t)packet_size};
CAN.readBytes(msg.buffer, packet_size);
onCanMessage(msg);
}
bool setupCan() {
// configure and initialize the CAN bus interface
CAN.setPins(MCP2515_CS, MCP2515_INT);
CAN.setClockFrequency(MCP2515_CLK_HZ);
if (!CAN.begin(CAN_BAUDRATE)) {
return false;
}
CAN.onReceive(receiveCallback);
return true;
}
#endif // IS_MCP2515
/* Arduinos with built-in CAN */
#ifdef IS_ARDUINO_BUILTIN
HardwareCAN& can_intf = CAN;
bool setupCan() {
return can_intf.begin((CanBitRate)CAN_BAUDRATE);
}
#endif
/* STM32 boards with built-in CAN */
#ifdef IS_STM32_BUILTIN
STM32_CAN Can1( CAN1 );
STM32_CAN& can_intf = Can1;
bool setupCan() {
can_intf.begin();
can_intf.setBaudRate(CAN_BAUDRATE);
return true;
}
#endif // IS_STM32_BUILTIN
/* Example sketch ------------------------------------------------------------*/
// Instantiate ODrive objects
ODriveCAN odrv0(wrap_can_intf(can_intf), ODRV0_NODE_ID); // Standard CAN message ID
ODriveCAN* odrives[] = {&odrv0}; // Make sure all ODriveCAN instances are accounted for here
struct ODriveUserData {
Heartbeat_msg_t last_heartbeat;
bool received_heartbeat = false;
};
// Keep some application-specific user data for every ODrive.
ODriveUserData odrv0_user_data;
// Called every time a Heartbeat message arrives from the ODrive
void onHeartbeat(Heartbeat_msg_t& msg, void* user_data) {
ODriveUserData* odrv_user_data = static_cast<ODriveUserData*>(user_data);
odrv_user_data->last_heartbeat = msg;
odrv_user_data->received_heartbeat = true;
}
void onFeedback(Get_Encoder_Estimates_msg_t& msg, void* user_data) {
Serial.print("Feedback - Pos_Estimate: ");
Serial.print(msg.Pos_Estimate);
Serial.print(", Vel_Estimate: ");
Serial.println(msg.Vel_Estimate);
}
void onTorques(Get_Torques_msg_t& msg, void* user_data) {
Serial.print("Torques - Torque_Target: ");
Serial.print(msg.Torque_Target);
Serial.print(", Torque_Estimate: ");
Serial.println(msg.Torque_Estimate);
}
void onTemperature(Get_Temperature_msg_t& msg, void* user_data) {
Serial.print("Temperature - FET_Temperature: ");
Serial.print(msg.FET_Temperature);
Serial.print(", Motor_Temperature: ");
Serial.println(msg.Motor_Temperature);
}
void onBusVI(Get_Bus_Voltage_Current_msg_t& msg, void* user_data) {
Serial.print("BusVI - Voltage: ");
Serial.print(msg.Bus_Voltage);
Serial.print(", Current: ");
Serial.println(msg.Bus_Current);
}
void onCurrents(Get_Iq_msg_t& msg, void* user_data) {
Serial.print("Currents - Iq_Setpoint: ");
Serial.print(msg.Iq_Setpoint);
Serial.print(", Iq_Measured: ");
Serial.println(msg.Iq_Measured);
}
void onError(Get_Error_msg_t& msg, void* user_data) {
Serial.print("Error - Active_Errors: ");
Serial.print(msg.Active_Errors);
Serial.print(", Disarm_Reason: ");
Serial.println(msg.Disarm_Reason);
}
// Called for every message that arrives on the CAN bus
void onCanMessage(const CanMsg& msg) {
for (auto odrive: odrives) {
onReceive(msg, *odrive);
}
}
void setup() {
Serial.begin(115200);
// Wait for up to 3 seconds for the serial port to be opened on the PC side.
// If no PC connects, continue anyway.
for (int i = 0; i < 30 && !Serial; ++i) {
delay(100);
}
delay(200);
Serial.println("Starting ODriveCAN demo");
// Register callbacks for the cyclic messages
odrv0.onFeedback(onFeedback, &odrv0_user_data);
odrv0.onStatus(onHeartbeat, &odrv0_user_data);
odrv0.onTorques(onTorques, &odrv0_user_data);
odrv0.onTemperature(onTemperature, &odrv0_user_data);
odrv0.onBusVI(onBusVI, &odrv0_user_data);
odrv0.onCurrents(onCurrents, &odrv0_user_data);
odrv0.onError(onError, &odrv0_user_data);
// Configure and initialize the CAN bus interface. This function depends on
// your hardware and the CAN stack that you're using.
if (!setupCan()) {
Serial.println("CAN failed to initialize: reset required");
while (true); // spin indefinitely
}
Serial.println("Waiting for ODrive...");
while (!odrv0_user_data.received_heartbeat) {
pumpEvents(can_intf);
delay(100);
}
Serial.println("found ODrive");
}
void loop() {
pumpEvents(can_intf);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment