diff --git a/examples/SineWaveCAN/SineWaveCAN.ino b/examples/SineWaveCAN/SineWaveCAN.ino index 1cfbdc2..f950f8c 100644 --- a/examples/SineWaveCAN/SineWaveCAN.ino +++ b/examples/SineWaveCAN/SineWaveCAN.ino @@ -1,141 +1,157 @@ - -#include #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. - - -/* Board-specific includes ---------------------------------------------------*/ - -#if defined(IS_TEENSY_BUILTIN) + defined(IS_ARDUINO_BUILTIN) + defined(IS_MCP2515) != 1 -#warning "Select exactly one hardware option at the top of this file." + // CAN bus baudrate. Make sure this matches for every device on the bus + #define CAN_BAUDRATE 250000 -#if CAN_HOWMANY > 0 || CANFD_HOWMANY > 0 -#define IS_ARDUINO_BUILTIN -#warning "guessing that this uses HardwareCAN" -#else -#error "cannot guess hardware version" -#endif + // ODrive node_id for odrv0 + #define ODRV0_NODE_ID 0 -#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 + // Uncomment below the line that corresponds to your hardware. + // See also "Board-specific settings" to adapt the details for your hardware setup. -#include -#include -#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 -#include "ODriveFlexCAN.hpp" -struct ODriveStatus; // hack to prevent teensy compile error -#endif // IS_TEENSY_BUILTIN + // #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_ESP32_TWAI // ESP32 boards using a external transceiver such as sn65hvd230. Uses Espressif's native TWAI driver. Connect and TX_PIN from ESP32 to D on transceiver and RX_PIN from ESP32 to R on transceiver. +/* Board-specific includes ---------------------------------------------------*/ + #if defined(IS_TEENSY_BUILTIN) + defined(IS_ARDUINO_BUILTIN) + defined(IS_MCP2515) + defined(IS_ESP32_TWAI) != 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 + #include + #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 + #include "ODriveFlexCAN.hpp" + struct ODriveStatus; // hack to prevent teensy compile error + #endif // IS_TEENSY_BUILTIN + + #ifdef IS_ESP32_TWAI + // See https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/peripherals/twai.html + // https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/TWAI + // Pins used to connect to CAN bus transceiver: + #define RX_PIN 35 + #define TX_PIN 36 + #define TRANSMIT_RATE_MS 50 + #define POLLING_RATE_MS 50 + #include "driver/twai.h" + + + #include "ODriveESP32TWAI.hpp" + #endif // IS_ESP32_TWAI /* Board-specific settings ---------------------------------------------------*/ + /* Teensy */ + #ifdef IS_TEENSY_BUILTIN + + FlexCAN_T4 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 -/* Teensy */ - -#ifdef IS_TEENSY_BUILTIN - -FlexCAN_T4 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 -/* MCP2515-based extension modules -*/ + MCP2515Class& can_intf = CAN; -#ifdef IS_MCP2515 + // chip select pin used for the MCP2515 + #define MCP2515_CS 10 -MCP2515Class& can_intf = CAN; + // interrupt pin used for the MCP2515 + // NOTE: not all Arduino pins are interruptable, check the documentation for your board! + #define MCP2515_INT 2 -// chip select pin used for the MCP2515 -#define MCP2515_CS 10 + // freqeuncy of the crystal oscillator on the MCP2515 breakout board. + // common values are: 16 MHz, 12 MHz, 8 MHz + #define MCP2515_CLK_HZ 8000000 -// 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; + } -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); -} + CAN.onReceive(receiveCallback); + return true; + } -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; - } + #endif // IS_MCP2515 - CAN.onReceive(receiveCallback); - return true; -} + /* ESP32 board using native TWAI driver */ + #ifdef IS_ESP32_TWAI -#endif // IS_MCP2515 + TWAIClass can_intf; + bool setupCan() { + if (!can_intf.begin(CAN_BAUDRATE)) { + return false; + } + return true; + } -/* Arduinos with built-in CAN */ + #endif // IS_ESP32_TWAI -#ifdef IS_ARDUINO_BUILTIN + /* Arduinos with built-in CAN */ + #ifdef IS_ARDUINO_BUILTIN -HardwareCAN& can_intf = CAN; + HardwareCAN& can_intf = CAN; -bool setupCan() { - return can_intf.begin((CanBitRate)CAN_BAUDRATE); -} + bool setupCan() { + return can_intf.begin((CanBitRate)CAN_BAUDRATE); + } -#endif + #endif /* Example sketch ------------------------------------------------------------*/ @@ -175,16 +191,17 @@ void onCanMessage(const CanMsg& msg) { } } + + 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); + long current_millis = 0; + while(!Serial){ + if ((millis() - current_millis) > 3000){ //check for connection for 3 seconds + break; //Break when connection found + } } - delay(200); - + Serial.println("Serial ok"); Serial.println("Starting ODriveCAN demo"); @@ -222,8 +239,10 @@ void setup() { Serial.println("Enabling closed loop control..."); while (odrv0_user_data.last_heartbeat.Axis_State != ODriveAxisState::AXIS_STATE_CLOSED_LOOP_CONTROL) { + odrv0.clearErrors(); delay(1); + odrv0.setState(ODriveAxisState::AXIS_STATE_CLOSED_LOOP_CONTROL); // Pump events for 150ms. This delay is needed for two reasons; @@ -240,6 +259,7 @@ void setup() { } Serial.println("ODrive running!"); + } void loop() { @@ -248,7 +268,7 @@ void loop() { // // This has been found to reduce the number of dropped messages, however it can be removed // for applications requiring loop times over 100Hz. - + float SINE_PERIOD = 2.0f; // Period of the position command sine wave in seconds float t = 0.001 * millis(); diff --git a/src/ODriveESP32TWAI.hpp b/src/ODriveESP32TWAI.hpp new file mode 100644 index 0000000..31b1240 --- /dev/null +++ b/src/ODriveESP32TWAI.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "TWAI_CAN_Class.h" // Include your custom TWAIClass header +#include "ODriveCAN.h" + +// This is a convenience struct because the TWAIClass doesn't have a +// native message type. +struct CanMsg { + uint32_t id; + uint8_t len; + uint8_t buffer[8]; +}; + +// Must be defined by the application if you want to use defaultCanReceiveCallback(). +void onCanMessage(const CanMsg& msg); + + +static inline bool sendMsg(TWAIClass& can_intf, uint32_t id, uint8_t length, const uint8_t* data) { + // Send CAN message + can_intf.prepareMessage(id, length, !data); + if (data) { + for (int i = 0; i < length; ++i) { + can_intf.write(data[i], i); + } + } + return can_intf.endPacket(); +} + +static inline void onReceive(const CanMsg& msg, ODriveCAN& odrive) { + odrive.onReceive(msg.id, msg.len, msg.buffer); +} + +static inline void pumpEvents(TWAIClass& intf) { + CanMsg msg; + int length = intf.parsePacket(); // Check if a packet is available + + if (length > 0) { + msg.id = intf.packetId(); // Retrieve the ID of the received message + // Debug print to check if ID is read correctly + //Serial.print("Received Raw Frame ID: 0x"); // DEBUG PRINT uncomment to print all messages to serial console ********************************** + //Serial.println(msg.id, HEX); // DEBUG PRINT uncomment to print all messages to serial console ********************************** + msg.len = length; + intf.readBytes(msg.buffer, length); // Retrieve the data from the message + onCanMessage(msg); // Call the user-defined callback + } +} + +CREATE_CAN_INTF_WRAPPER(TWAIClass) diff --git a/src/TWAI_CAN_Class.h b/src/TWAI_CAN_Class.h new file mode 100644 index 0000000..18301b6 --- /dev/null +++ b/src/TWAI_CAN_Class.h @@ -0,0 +1,161 @@ +#ifndef TWAI_CAN_CLASS_H +#define TWAI_CAN_CLASS_H + +class TWAIClass { +public: + TWAIClass() + : is_initialized(false) {} + ~TWAIClass() { + if (is_initialized) { + end(); + } + } + + bool begin(long baudRate) { + if (is_initialized) return false; // Already initialized + // Configure TWAI (CAN) settings + twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT((gpio_num_t)TX_PIN, (gpio_num_t)RX_PIN, TWAI_MODE_NORMAL); + // Select baud rate using switch-case. Ensure it matches ODrive configuration + twai_timing_config_t t_config; + switch (baudRate) { + case 1000000: + t_config = TWAI_TIMING_CONFIG_1MBITS(); + break; + case 800000: + t_config = TWAI_TIMING_CONFIG_800KBITS(); + break; + case 500000: + t_config = TWAI_TIMING_CONFIG_500KBITS(); + break; + case 250000: + t_config = TWAI_TIMING_CONFIG_250KBITS(); + break; + case 125000: + t_config = TWAI_TIMING_CONFIG_125KBITS(); + break; + case 100000: + t_config = TWAI_TIMING_CONFIG_100KBITS(); + break; + case 50000: + t_config = TWAI_TIMING_CONFIG_50KBITS(); + break; + case 25000: + t_config = TWAI_TIMING_CONFIG_25KBITS(); + break; + default: + Serial.println("Unsupported baud rate! Defaulting to 250Kbps."); + t_config = TWAI_TIMING_CONFIG_250KBITS(); + break; + } + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + // Install the TWAI driver + if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) { + Serial.println("TWAI Driver install failed!"); + return false; + } + + // Start TWAI driver + if (twai_start() != ESP_OK) { + Serial.println("TWAI Driver start failed!"); + twai_driver_uninstall(); + return false; + } + + // Configure TWAI Alerts (Monitor TX and Bus Errors) + uint32_t alerts_to_enable = TWAI_ALERT_TX_IDLE | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_TX_FAILED | TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_ERROR; + if (twai_reconfigure_alerts(alerts_to_enable, NULL) != ESP_OK) { + Serial.println("Failed to configure CAN Alerts!"); + return false; + } + + Serial.println("TWAI CAN Bus Started Successfully!"); + is_initialized = true; + return true; + } + + // End CAN communication + void end() { + if (is_initialized) { + twai_stop(); + twai_driver_uninstall(); + is_initialized = false; + } + } + + // Send a CAN message + int endPacket() { + if (!is_initialized || !message_ready) { + return 0; // Not initialized or no message ready + } + + if (twai_transmit(&tx_message, pdMS_TO_TICKS(100)) == ESP_OK) { + message_ready = false; + return 1; // Successfully sent message + } + + return 0; // Failed to send message + } + + // Prepare a CAN messagewrite + void prepareMessage(uint32_t id, uint8_t length, bool rtr = false) { + tx_message.identifier = id; + tx_message.data_length_code = length; + //tx_message.rtr = rtr ? TWAI_RTR : TWAI_NO_RTR; //TODO this line doesnt compile. verify if required for odrive******************************************* + + tx_message.rtr = 0; + tx_message.extd = (id > 0x7FF); // Extended frame if ID > 11 bits + message_ready = true; + } + + // Write data to the CAN message + void write(uint8_t byte, int index) { + if (index >= 0 && index < 8) { + tx_message.data[index] = byte; + } + } + + // Parse incoming CAN packet + int parsePacket() { + if (!is_initialized) { + return 0; // Not initialized + } + + if (twai_receive(&rx_message, pdMS_TO_TICKS(10)) == ESP_OK) { + return rx_message.data_length_code; // Number of bytes received + } + + return 0; // No packet received + } + + // Access packet ID + uint32_t packetId() { + return rx_message.identifier; + } + + // Access received data + void readBytes(uint8_t* buffer, uint8_t length) { + if (length > 8) length = 8; // Maximum data length + for (uint8_t i = 0; i < length; ++i) { + buffer[i] = rx_message.data[i]; + //Serial.printf(" %d = %02x,", i, rx_message.data[i]); // DEBUG PRINT uncomment to print all messages to serial console ********************************** + } + //Serial.println(""); //uncomment to print all messages to serial console ********************************** + } + + // Callback for received messages + void onReceive(void (*callback)(int)) { + receive_callback = callback; + } + +private: + bool is_initialized; + bool message_ready = false; + twai_message_t tx_message; + twai_message_t rx_message; + void (*receive_callback)(int) = nullptr; +}; + +extern TWAIClass CAN; + +#endif // TWAI_CAN_CLASS_H