From ef73a14d9ae133297ab8589460f44f06421ab3da Mon Sep 17 00:00:00 2001 From: gospar Date: Fri, 21 Nov 2025 12:47:58 +0100 Subject: [PATCH 1/7] inita can implementation --- src/comms/can/CANCommander.cpp | 191 +++++++++++++++++++++++++++++++++ src/comms/can/CANCommander.h | 77 +++++++++++++ src/comms/can/README.md | 62 +++++++++++ 3 files changed, 330 insertions(+) create mode 100644 src/comms/can/CANCommander.cpp create mode 100644 src/comms/can/CANCommander.h create mode 100644 src/comms/can/README.md diff --git a/src/comms/can/CANCommander.cpp b/src/comms/can/CANCommander.cpp new file mode 100644 index 0000000..544facd --- /dev/null +++ b/src/comms/can/CANCommander.cpp @@ -0,0 +1,191 @@ +#include "CANCommander.h" + +CANCommander::CANCommander(HardwareCAN& can, uint8_t addr, bool echo_enabled) + : _can(&can), address(addr), echo(echo_enabled) { +} + +CANCommander::~CANCommander() { +} + +void CANCommander::init() { + // Filter to accept only our address (broadcast filtering handled in software) + // Mask the address bits [28:21] - the most significant 8 bits + uint32_t address_mask = 0xFF << CAN_ADDRESS_SHIFT; // Mask bits [28:21] (8 bits for address) + uint32_t our_address_filter = (uint32_t)address << CAN_ADDRESS_SHIFT; + + // Use MASK_EXTENDED for 29-bit extended CAN IDs + // This will only accept messages where the address bits match + _can->filter(CanFilter(MASK_EXTENDED, our_address_filter, address_mask, FILTER_ANY_FRAME)); + + // Accept all messages (promiscuous mode for debugging) + //_can->filter(CanFilter(ACCEPT_ALL)); + + _can->begin(1000000); // 1 Mbps CAN speed +} + +void CANCommander::addMotor(FOCMotor* motor) { + if (numMotors < CANCOMMANDER_MAX_MOTORS) + motors[numMotors++] = motor; +} + +void CANCommander::run() { + // Process incoming CAN messages + if (_can->available() > 0) { + CanMsg msg = _can->read(); + if (!msg.isEmpty()) { + handleCANMessage(msg); + } + } +} + +void CANCommander::handleCANMessage(CanMsg& msg) { + // Parse CAN ID to extract fields (address is now in MSBs) + uint8_t msg_addr = (msg.id >> CAN_ADDRESS_SHIFT) & 0xFF; + uint8_t packet_type = (msg.id >> CAN_PACKET_TYPE_SHIFT) & 0xF; + uint8_t reg = (msg.id >> CAN_REGISTER_SHIFT) & 0xFF; + uint8_t motor_idx = (msg.id >> CAN_MOTOR_INDEX_SHIFT) & 0xF; + + // Check if message is for us (hardware filter should handle this, but double-check) + if (msg_addr != address && msg_addr != 0xFF) // 0xFF = broadcast + return; + + lastcommanderror = commanderror; + lastcommandregister = curRegister; + commanderror = false; + curRegister = reg; + + // Use motor index from CAN ID if provided, otherwise keep current + if (motor_idx < numMotors) { + curMotor = motor_idx; + } + + // Copy data to rx_buffer + rx_available = msg.data_length; + rx_pos = 0; + memcpy(rx_buffer, msg.data, msg.data_length); + + switch (packet_type) { + case CAN_READ_REQUEST: + // Read register and send response + sendRegisterResponse(reg, msg_addr); + break; + + case CAN_WRITE_REQUEST: + // Write to register + if (commsToRegister(reg)) { + if (echo) { + // Echo back the value + sendRegisterResponse(reg, msg_addr); + } + } else { + commanderror = true; + } + break; + + case CAN_SYNC: + // Sync request - respond with sync ack + tx_pos = 0; + tx_buffer[tx_pos++] = 0x01; // Sync ack + uint32_t sync_id = (address << CAN_ADDRESS_SHIFT) | + (CAN_SYNC << CAN_PACKET_TYPE_SHIFT); + CanMsg sync_msg = CanMsg(CanExtendedId(sync_id), tx_pos, tx_buffer); + _can->write(sync_msg); + break; + } +} + +void CANCommander::sendRegisterResponse(uint8_t reg, uint8_t dest_addr) { + // Prepare transmit buffer + tx_pos = 0; + + // Write register data to buffer + if (registerToComms(reg)) { + // Build CAN ID for response (address in MSBs) + uint32_t can_id = (address << CAN_ADDRESS_SHIFT) | + (CAN_READ_RESPONSE << CAN_PACKET_TYPE_SHIFT) | + (reg << CAN_REGISTER_SHIFT) | + (curMotor << CAN_MOTOR_INDEX_SHIFT); + + // Send response + CanMsg msg = CanMsg(CanExtendedId(can_id), tx_pos, tx_buffer); + _can->write(msg); + } +} + +bool CANCommander::commsToRegister(uint8_t reg) { + return SimpleFOCRegisters::regs->commsToRegister(*this, reg, motors[curMotor]); +} + +bool CANCommander::registerToComms(uint8_t reg) { + switch (reg) { + case SimpleFOCRegister::REG_STATUS: + *this << (uint8_t)curMotor; + *this << (uint8_t)lastcommandregister; + *this << (uint8_t)(lastcommanderror ? 1 : 0); + if (curMotor < numMotors) + *this << (uint8_t)motors[curMotor]->motor_status; + return true; + case SimpleFOCRegister::REG_NUM_MOTORS: + *this << numMotors; + return true; + default: + if (curMotor >= numMotors) { + commanderror = true; + return false; + } + return SimpleFOCRegisters::regs->registerToComms(*this, reg, motors[curMotor]); + } +} + +// RegisterIO interface implementation +RegisterIO& CANCommander::operator<<(uint8_t value) { + if (tx_pos < 8) { + tx_buffer[tx_pos++] = value; + } + return *this; +} + +RegisterIO& CANCommander::operator<<(uint32_t value) { + if (tx_pos + 4 <= 8) { + memcpy(&tx_buffer[tx_pos], &value, 4); + tx_pos += 4; + } + return *this; +} + +RegisterIO& CANCommander::operator<<(float value) { + if (tx_pos + 4 <= 8) { + memcpy(&tx_buffer[tx_pos], &value, 4); + tx_pos += 4; + } + return *this; +} + +RegisterIO& CANCommander::operator>>(uint8_t& value) { + if (rx_pos < rx_available) { + value = rx_buffer[rx_pos++]; + } else { + commanderror = true; + } + return *this; +} + +RegisterIO& CANCommander::operator>>(uint32_t& value) { + if (rx_pos + 4 <= rx_available) { + memcpy(&value, &rx_buffer[rx_pos], 4); + rx_pos += 4; + } else { + commanderror = true; + } + return *this; +} + +RegisterIO& CANCommander::operator>>(float& value) { + if (rx_pos + 4 <= rx_available) { + memcpy(&value, &rx_buffer[rx_pos], 4); + rx_pos += 4; + } else { + commanderror = true; + } + return *this; +} diff --git a/src/comms/can/CANCommander.h b/src/comms/can/CANCommander.h new file mode 100644 index 0000000..0b79777 --- /dev/null +++ b/src/comms/can/CANCommander.h @@ -0,0 +1,77 @@ +#ifndef CANCOMMANDER_H +#define CANCOMMANDER_H + +#include "SimpleFOC.h" +#include "../SimpleFOCRegisters.h" +#include "../RegisterIO.h" +#include "SimpleCANio.h" + +#if !defined(CANCOMMANDER_MAX_MOTORS) +#define CANCOMMANDER_MAX_MOTORS 16 +#endif + +// CAN packet structure for register-based protocol +// CAN ID bits allocation (Optimized for hardware filtering): +// [27:20] - Node Address (8 bits) - MOST SIGNIFICANT for efficient filtering +// [19:16] - Packet Type (4 bits) +// [15:8] - Register number (8 bits) - see SimpleFOCRegisters for the register numbers +// [7:0] - Motor Index (8 bits) - Selects motor 0-256 on the node + +#define CAN_ADDRESS_SHIFT 20 // Address in MSBs for efficient filtering +#define CAN_PACKET_TYPE_SHIFT 16 // Packet type after address +#define CAN_REGISTER_SHIFT 8 // Register number +#define CAN_MOTOR_INDEX_SHIFT 0 // Motor selection (0-256) + +enum CANPacketType : uint8_t { + CAN_READ_REQUEST = 0x1, + CAN_WRITE_REQUEST = 0x2, + CAN_READ_RESPONSE = 0x3, + CAN_SYNC = 0xF +}; + +class CANCommander : public RegisterIO +{ +public: + CANCommander(HardwareCAN& can, uint8_t address = 0, bool echo = false); + virtual ~CANCommander(); + + void addMotor(FOCMotor* motor); + virtual void init(); + virtual void run(); + + // RegisterIO interface implementation + RegisterIO& operator<<(float value) override; + RegisterIO& operator<<(uint32_t value) override; + RegisterIO& operator<<(uint8_t value) override; + RegisterIO& operator>>(float& value) override; + RegisterIO& operator>>(uint32_t& value) override; + RegisterIO& operator>>(uint8_t& value) override; + + bool echo = false; + uint8_t address = 0; + +protected: + virtual bool commsToRegister(uint8_t reg); + virtual bool registerToComms(uint8_t reg); + virtual void handleCANMessage(CanMsg& msg); + virtual void sendRegisterResponse(uint8_t reg, uint8_t dest_addr); + + HardwareCAN* _can; + FOCMotor* motors[CANCOMMANDER_MAX_MOTORS]; + uint8_t numMotors = 0; + uint8_t curMotor = 0; + uint8_t curRegister = REG_STATUS; + + bool commanderror = false; + bool lastcommanderror = false; + uint8_t lastcommandregister = REG_STATUS; + + // Transmit/receive buffers + uint8_t tx_buffer[8]; + uint8_t tx_pos = 0; + uint8_t rx_buffer[8]; + uint8_t rx_pos = 0; + uint8_t rx_available = 0; +}; + +#endif \ No newline at end of file diff --git a/src/comms/can/README.md b/src/comms/can/README.md new file mode 100644 index 0000000..d241ef0 --- /dev/null +++ b/src/comms/can/README.md @@ -0,0 +1,62 @@ +# CAN Communication Protocol + +This document describes the CAN-based communication protocol for SimpleFOC motor control using the CANCommander class. + +## Overview + +The protocol uses **29-bit Extended CAN IDs** with a register-based architecture for efficient motor control and monitoring. The CAN ID structure is optimized for hardware filtering by placing the most significant bits (address) at the top. + +## CAN ID Structure + +The 29-bit CAN Extended ID is divided into the following fields: + +``` +Bit [28] - Not used +Bits [27:20] - Node Address (8 bits) - MSBs for efficient hardware filtering +Bits [19:16] - Packet Type (4 bits) - Request/Response type +Bits [15:8] - Register Number (8 bits) - Target register (see register table) +Bits [7:0] - Motor Index (8 bits) - Motor selection (0-255) +``` + +### Bit Shifts (Defined in CANCommander.h) + +```cpp +#define CAN_ADDRESS_SHIFT 20 // Address in MSBs for efficient filtering +#define CAN_PACKET_TYPE_SHIFT 16 // Packet type after address +#define CAN_REGISTER_SHIFT 8 // Register number +#define CAN_MOTOR_INDEX_SHIFT 0 // Motor selection (0-256) +``` + +## Packet Types + +The protocol defines the following packet types: + +| Value | Name | Description | +|-------|-------------------|--------------------------------------------------| +| 0x1 | CAN_READ_REQUEST | Request to read a register value | +| 0x2 | CAN_WRITE_REQUEST | Request to write a value to a register | +| 0x3 | CAN_READ_RESPONSE | Response containing register data | +| 0xF | CAN_SYNC | Synchronization message | + +## Protocol Operation + +### Reading a Register + +1. **Master** sends a `CAN_READ_REQUEST` with the target register number +2. **Node** responds with a `CAN_READ_RESPONSE` containing the register data + +### Writing to a Register + +1. **Master** sends a `CAN_WRITE_REQUEST` with register number and data payload +2. **Node** processes the write request +3. If echo is enabled, **Node** sends a `CAN_READ_RESPONSE` with the updated value + +### Addressing + +- **Unicast**: Direct message to a specific node address (0x00 - 0xFE) +- **Broadcast**: Address 0xFF sends to all nodes on the bus +- Hardware filtering is configured to accept only messages for the node's address or broadcast + +## Register Map + +The registers are defined in `SimpleFOCRegisters.h`. From 9966a776f8987c3e196dfc1a4d3b8bfe3e0e632f Mon Sep 17 00:00:00 2001 From: gospar Date: Fri, 21 Nov 2025 12:52:48 +0100 Subject: [PATCH 2/7] can example --- .../comms/can/can_example/can_example.ino | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/comms/can/can_example/can_example.ino diff --git a/examples/comms/can/can_example/can_example.ino b/examples/comms/can/can_example/can_example.ino new file mode 100644 index 0000000..90e5d4a --- /dev/null +++ b/examples/comms/can/can_example/can_example.ino @@ -0,0 +1,51 @@ +#include "Arduino.h" +#include "SimpleFOC.h" +#include "SimpleFOCDrivers.h" +#include "SimpleCANio.h" +#include "comms/can/CANCommander.h" + +// can pins +#define CAN_RX PA11 +#define CAN_TX PA12 +#define CAN_ENABLE PA10 +#define CAN_ID 1 + +// 3pwm pins +#define PHA PA6 +#define PHB PA7 +#define PHC PA8 + +BLDCMotor motor = BLDCMotor(7); // 7 pole pairs +BLDCDriver3PWM driver = BLDCDriver3PWM(PHA, PHB, PHC); + +//CANio can(PIN_CAN0_RX, PIN_CAN0_TX); // Create CAN object +CANio can(CAN_RX, CAN_TX, NC, CAN_ENABLE); // Create CAN object + +CANCommander commander(can, CAN_ID); +void setup() +{ + Serial.begin(115200); + SimpleFOCDebug::enable(&Serial); + + commander.init(); + commander.addMotor(&motor); + delay(5000); + + motor.linkDriver(&driver); + driver.voltage_power_supply = 12; + driver.init(); + + motor.init(); + motor.initFOC(); + + Serial.println("Setup complete!"); + delay(10); +} + +void loop() +{ + motor.loopFOC(); + motor.move(); + + commander.run(); +} \ No newline at end of file From 550339f52ea9bc1f32ddbdd5b6aa6e05bce7bcf2 Mon Sep 17 00:00:00 2001 From: gospar Date: Fri, 21 Nov 2025 12:56:15 +0100 Subject: [PATCH 3/7] NC in example --- examples/comms/can/can_example/can_example.ino | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/comms/can/can_example/can_example.ino b/examples/comms/can/can_example/can_example.ino index 90e5d4a..8c5e581 100644 --- a/examples/comms/can/can_example/can_example.ino +++ b/examples/comms/can/can_example/can_example.ino @@ -5,9 +5,10 @@ #include "comms/can/CANCommander.h" // can pins -#define CAN_RX PA11 -#define CAN_TX PA12 -#define CAN_ENABLE PA10 +#define CAN_RX NC // define the RX pin +#define CAN_TX NC // define the TX pin +#define CAN_SHDN NC // define the Shhutdown pin (inverse of enable) if needed +#define CAN_ENABLE NC // define the ENABLE pin if needed #define CAN_ID 1 // 3pwm pins From 572f2ec14bb65e5705afece3d299597fb3e260b1 Mon Sep 17 00:00:00 2001 From: gospar Date: Mon, 24 Nov 2025 15:37:13 +0100 Subject: [PATCH 4/7] allow for custom regs --- .../can/can_custom_regs/can_custom_regs.ino | 73 +++++++++++++++++++ src/comms/SimpleFOCRegisters.cpp | 54 +++++++++++++- src/comms/SimpleFOCRegisters.h | 21 +++++- src/comms/can/CANCommander.cpp | 31 ++++---- src/comms/can/CANCommander.h | 10 ++- 5 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 examples/comms/can/can_custom_regs/can_custom_regs.ino diff --git a/examples/comms/can/can_custom_regs/can_custom_regs.ino b/examples/comms/can/can_custom_regs/can_custom_regs.ino new file mode 100644 index 0000000..0ccb9eb --- /dev/null +++ b/examples/comms/can/can_custom_regs/can_custom_regs.ino @@ -0,0 +1,73 @@ +#include "Arduino.h" +#include "SimpleFOC.h" +#include "SimpleFOCDrivers.h" +#include "SimpleCANio.h" +#include "comms/can/CANCommander.h" + +// can pins +#define CAN_RX NC // define the RX pin +#define CAN_TX NC // define the TX pin +#define CAN_SHDN NC // define the Shhutdown pin (inverse of enable) if needed +#define CAN_ENABLE NC // define the ENABLE pin if needed +#define CAN_ID 1 + +// 3pwm pins +#define PHA PA6 +#define PHB PA7 +#define PHC PA8 + +BLDCMotor motor = BLDCMotor(7); // 7 pole pairs +BLDCDriver3PWM driver = BLDCDriver3PWM(PHA, PHB, PHC); + +//CANio can(PIN_CAN0_RX, PIN_CAN0_TX); // Create CAN object +CANio can(CAN_RX, CAN_TX, NC, CAN_ENABLE); // Create CAN object + +CANCommander commander(can, CAN_ID); +void setup() +{ + Serial.begin(115200); + SimpleFOCDebug::enable(&Serial); + + commander.init(); + commander.addMotor(&motor); + + // add custom register to control built-in LED + pinMode(LED_BUILTIN, OUTPUT); + commander.addCustomRegister( + 0xF0, // register id (should be > 0xE0) + 1, // size in bytes + [](RegisterIO& comms, FOCMotor* motor) -> bool { + // Read handler for custom register 0xF0 + uint8_t customValue = digitalRead(LED_BUILTIN); + comms << customValue; + return true; + }, + [](RegisterIO& comms, FOCMotor* motor) -> bool { + // Write handler for custom register 0xF0 + uint8_t receivedValue; + comms >> receivedValue; + digitalWrite(LED_BUILTIN, receivedValue ? HIGH : LOW); + return true; + } + ); + + delay(5000); + + motor.linkDriver(&driver); + driver.voltage_power_supply = 12; + driver.init(); + + motor.init(); + motor.initFOC(); + + Serial.println("Setup complete!"); + delay(10); +} + +void loop() +{ + motor.loopFOC(); + motor.move(); + + commander.run(); +} \ No newline at end of file diff --git a/src/comms/SimpleFOCRegisters.cpp b/src/comms/SimpleFOCRegisters.cpp index c91feeb..be9866b 100644 --- a/src/comms/SimpleFOCRegisters.cpp +++ b/src/comms/SimpleFOCRegisters.cpp @@ -1,6 +1,7 @@ #include "./SimpleFOCRegisters.h" #include "BLDCMotor.h" +#include "communication/SimpleFOCDebug.h" #include "./telemetry/Telemetry.h" @@ -322,8 +323,14 @@ bool SimpleFOCRegisters::registerToComms(RegisterIO& comms, uint8_t reg, FOCMoto case SimpleFOCRegister::REG_NUM_MOTORS: case SimpleFOCRegister::REG_MOTOR_ADDRESS: case SimpleFOCRegister::REG_ENABLE_ALL: - default: return false; + default: + // check the custom registers + for (uint8_t i=0; ireg_id) { + return customRegisters[i]->readHandler(comms, motor); + } + } } return true; }; @@ -591,9 +598,17 @@ bool SimpleFOCRegisters::commsToRegister(RegisterIO& comms, uint8_t reg, FOCMoto case SimpleFOCRegister::REG_NUM_MOTORS: case SimpleFOCRegister::REG_MOTOR_ADDRESS: case SimpleFOCRegister::REG_ENABLE_ALL: - default: return false; + default: + // check the custom registers + for (uint8_t i=0; ireg_id) { + return customRegisters[i]->writeHandler(comms, motor); + } + } } + // if we reach here, the register is not found + SIMPLEFOC_DEBUG("Write to unknown register: ", reg); return false; }; @@ -691,10 +706,43 @@ uint8_t SimpleFOCRegisters::sizeOfRegister(uint8_t reg){ return 1; case SimpleFOCRegister::REG_DRIVER_ENABLE: case SimpleFOCRegister::REG_ENABLE_ALL: // write-only - default: // unknown register or write only register (no output) or can't handle in superclass + return 0; + default: + // register not in the predefined regs, check the custom ones + for (uint8_t i=0; ireg_id == reg) { + return customRegisters[i]->size; + } + } + // if the code commes here the register is either + // unknown register or write only register (no output) or can't handle in superclass + SIMPLEFOC_DEBUG("Size of unknown register requested: ", reg); return 0; } }; +bool SimpleFOCRegisters::addCustomRegister(uint8_t reg, uint8_t size, RegisterReadHandler readHandler, RegisterWriteHandler writeHandler) { + // check if we have space + if (customRegisterCount >= MAX_CUSTOM_REGISTERS) { + SIMPLEFOC_DEBUG("Custom register count exceeded maximum (32): ", reg); + return false; + } + // check if the register has valid id + if (reg < REG_CUSTOM_START) { + SIMPLEFOC_DEBUG("Custom register ID invalid (should be >= 0xE0): ", reg); + return false; + } + // check if the register is already registered + for (uint8_t i=0; ireg_id == reg) { + SIMPLEFOC_DEBUG("Custom register ID already registered: ", reg); + return false; + } + } + // add the custom register + customRegisters[customRegisterCount++] = new CustomRegisterHandler{reg, size, readHandler, writeHandler}; + return true; +} + SimpleFOCRegisters* SimpleFOCRegisters::regs = new SimpleFOCRegisters(); diff --git a/src/comms/SimpleFOCRegisters.h b/src/comms/SimpleFOCRegisters.h index 7def8e1..3932059 100644 --- a/src/comms/SimpleFOCRegisters.h +++ b/src/comms/SimpleFOCRegisters.h @@ -10,7 +10,8 @@ // does not change the version, but removing or changing the meaning of existing registers does, or changing the number of an existing register. #define SIMPLEFOC_REGISTERS_VERSION 0x01 - +#define MAX_CUSTOM_REGISTERS 32 +#define REG_CUSTOM_START 0xE0 typedef enum : uint8_t { REG_STATUS = 0x00, // RO - 1 byte (motor status) @@ -104,6 +105,18 @@ typedef enum : uint8_t { } SimpleFOCRegister; +// Custom register handlers +typedef bool (*RegisterReadHandler)(RegisterIO& comms, FOCMotor* motor); +typedef bool (*RegisterWriteHandler)(RegisterIO& comms, FOCMotor* motor); +// Custom register handler structure +struct CustomRegisterHandler { + uint8_t reg_id; + uint8_t size; + RegisterReadHandler readHandler; + RegisterWriteHandler writeHandler; +}; + + class SimpleFOCRegisters { public: SimpleFOCRegisters(); @@ -113,4 +126,10 @@ class SimpleFOCRegisters { virtual bool commsToRegister(RegisterIO& comms, uint8_t reg, FOCMotor* motor); static SimpleFOCRegisters* regs; + + // Method to add custom register handlers + bool addCustomRegister(uint8_t reg, uint8_t size, RegisterReadHandler readHandler, RegisterWriteHandler writeHandler); + protected: + CustomRegisterHandler* customRegisters[MAX_CUSTOM_REGISTERS] = {nullptr}; + uint8_t customRegisterCount = 0; }; diff --git a/src/comms/can/CANCommander.cpp b/src/comms/can/CANCommander.cpp index 544facd..42be4ee 100644 --- a/src/comms/can/CANCommander.cpp +++ b/src/comms/can/CANCommander.cpp @@ -1,26 +1,29 @@ #include "CANCommander.h" -CANCommander::CANCommander(HardwareCAN& can, uint8_t addr, bool echo_enabled) - : _can(&can), address(addr), echo(echo_enabled) { +CANCommander::CANCommander(HardwareCAN& can, uint8_t addr, bool echo_enabled, int baudrate, bool no_filter) + : _can(&can), address(addr), echo(echo_enabled), baudrate(baudrate), no_filter(no_filter) { } CANCommander::~CANCommander() { } void CANCommander::init() { - // Filter to accept only our address (broadcast filtering handled in software) - // Mask the address bits [28:21] - the most significant 8 bits - uint32_t address_mask = 0xFF << CAN_ADDRESS_SHIFT; // Mask bits [28:21] (8 bits for address) - uint32_t our_address_filter = (uint32_t)address << CAN_ADDRESS_SHIFT; - // Use MASK_EXTENDED for 29-bit extended CAN IDs - // This will only accept messages where the address bits match - _can->filter(CanFilter(MASK_EXTENDED, our_address_filter, address_mask, FILTER_ANY_FRAME)); - - // Accept all messages (promiscuous mode for debugging) - //_can->filter(CanFilter(ACCEPT_ALL)); - - _can->begin(1000000); // 1 Mbps CAN speed + if(no_filter) { + // Accept all messages (promiscuous mode) + _can->filter(CanFilter(ACCEPT_ALL)); + return; + }else{ + // Filter to accept only our address (broadcast filtering handled in software) + // Mask the address bits [28:21] - the most significant 8 bits + uint32_t address_mask = 0xFF << CAN_ADDRESS_SHIFT; // Mask bits [28:21] (8 bits for address) + uint32_t our_address_filter = (uint32_t)address << CAN_ADDRESS_SHIFT; + + // Use MASK_EXTENDED for 29-bit extended CAN IDs + // This will only accept messages where the address bits match + _can->filter(CanFilter(MASK_EXTENDED, our_address_filter, address_mask, FILTER_ANY_FRAME)); + } + _can->begin(baudrate); // 1 Mbps CAN speed } void CANCommander::addMotor(FOCMotor* motor) { diff --git a/src/comms/can/CANCommander.h b/src/comms/can/CANCommander.h index 0b79777..7ebcd5a 100644 --- a/src/comms/can/CANCommander.h +++ b/src/comms/can/CANCommander.h @@ -32,7 +32,7 @@ enum CANPacketType : uint8_t { class CANCommander : public RegisterIO { public: - CANCommander(HardwareCAN& can, uint8_t address = 0, bool echo = false); + CANCommander(HardwareCAN& can, uint8_t address = 0, bool echo = false, int baudrate = 1000000, bool no_filter = false); virtual ~CANCommander(); void addMotor(FOCMotor* motor); @@ -49,6 +49,14 @@ class CANCommander : public RegisterIO bool echo = false; uint8_t address = 0; + int baudrate = 1000000; + bool no_filter = false; + + bool addCustomRegister(uint8_t reg, uint8_t size, + RegisterReadHandler readHandler, + RegisterWriteHandler writeHandler) { + return SimpleFOCRegisters::regs->addCustomRegister(reg, size, readHandler, writeHandler); + } protected: virtual bool commsToRegister(uint8_t reg); From f0b9b785120e76e969dbd1520abd1ab140bf354a Mon Sep 17 00:00:00 2001 From: gospar Date: Mon, 24 Nov 2025 16:49:04 +0100 Subject: [PATCH 5/7] filter mask for extended can stm32 --- src/comms/can/CANCommander.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/comms/can/CANCommander.cpp b/src/comms/can/CANCommander.cpp index 42be4ee..a33c6d8 100644 --- a/src/comms/can/CANCommander.cpp +++ b/src/comms/can/CANCommander.cpp @@ -12,7 +12,6 @@ void CANCommander::init() { if(no_filter) { // Accept all messages (promiscuous mode) _can->filter(CanFilter(ACCEPT_ALL)); - return; }else{ // Filter to accept only our address (broadcast filtering handled in software) // Mask the address bits [28:21] - the most significant 8 bits From 7fa6670bea5f1d7eaa6b1b29d80b00547db32996 Mon Sep 17 00:00:00 2001 From: gospar Date: Mon, 1 Dec 2025 08:27:20 +0100 Subject: [PATCH 6/7] update info --- src/comms/can/CANCommander.cpp | 2 +- src/comms/can/README.md | 101 +++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/comms/can/CANCommander.cpp b/src/comms/can/CANCommander.cpp index a33c6d8..9ad6cbe 100644 --- a/src/comms/can/CANCommander.cpp +++ b/src/comms/can/CANCommander.cpp @@ -11,7 +11,7 @@ void CANCommander::init() { if(no_filter) { // Accept all messages (promiscuous mode) - _can->filter(CanFilter(ACCEPT_ALL)); + _can->filter(CanFilter(MASK_ACCEPT_ALL)); }else{ // Filter to accept only our address (broadcast filtering handled in software) // Mask the address bits [28:21] - the most significant 8 bits diff --git a/src/comms/can/README.md b/src/comms/can/README.md index d241ef0..c248606 100644 --- a/src/comms/can/README.md +++ b/src/comms/can/README.md @@ -60,3 +60,104 @@ The protocol defines the following packet types: ## Register Map The registers are defined in `SimpleFOCRegisters.h`. + +## Frame Diagram (Extended ID + Data) + +``` +CAN Extended ID (29 bits) +┌────────┬──────────────┬──────────────┬──────────────┬─────────────────┐ +│ Bit 28 │ 27 ........20│ 19 ....... 16│ 15 ........ 8│ 7 ........ 0 | +│ (Rsv) │ Address (8) │ Type (4) │ Register (8) │ Motor index (8) | +└────────┴──────────────┴──────────────┴──────────────┴─────────────────┘ +Data (0..8 bytes) +┌─────────────────────────────────────────────────────┐ +│ Payload (register-specific encoding: byte/float/u32)│ +└─────────────────────────────────────────────────────┘ +``` + +Small payloads (status, single byte flags) use only first bytes. Multi-byte values (float, uint32_t) are little-endian raw copies. + +## Implemented Registers (Summary) + +Selected register groups (see header for full list): + +| Group | Examples | +|-------|----------| +| Status & Control | `REG_STATUS`, `REG_ENABLE`, `REG_CONTROL_MODE`, `REG_TORQUE_MODE` | +| Motion Targets | `REG_TARGET` (position/velocity/torque depending on mode) | +| Sensor Feedback | `REG_ANGLE`, `REG_VELOCITY`, `REG_SENSOR_ANGLE`, `REG_SENSOR_VELOCITY` | +| Phase / Driver | `REG_PHASE_VOLTAGE`, `REG_PHASE_STATE`, `REG_DRIVER_ENABLE` | +| Telemetry Config | `REG_TELEMETRY_REG`, `REG_TELEMETRY_CTRL`, `REG_TELEMETRY_DOWNSAMPLE`, `REG_ITERATIONS_SEC` | +| FOC Internal | `REG_VOLTAGE_Q/D`, `REG_CURRENT_Q/D`, `REG_CURRENT_ABC`, `REG_CURRENT_DC` | +| PID Velocity | `REG_VEL_PID_P/I/D/LIM/RAMP`, `REG_VEL_LPF_T` | +| PID Position | `REG_ANG_PID_P/I/D/LIM/RAMP`, `REG_ANG_LPF_T` | +| PID Current Q/D | `REG_CURQ_PID_*`, `REG_CURD_PID_*`, `REG_CURQ_LPF_T`, `REG_CURD_LPF_T` | +| Limits | `REG_VOLTAGE_LIMIT`, `REG_CURRENT_LIMIT`, `REG_VELOCITY_LIMIT`, `REG_PWM_FREQUENCY` | +| Motor Params | `REG_PHASE_RESISTANCE`, `REG_KV`, `REG_INDUCTANCE`, current gains & offsets | +| System Info | `REG_NUM_MOTORS`, `REG_SYS_TIME` | +| Custom Range | `REG_CUSTOM_START (0xE0)` .. +32 via registration | + +Registers are read-only (RO), write-only (WO) or read/write (R/W) as indicated in the header. + +## Adding a Custom Register + +1. Pick an ID >= `REG_CUSTOM_START` (0xE0) +2. Define handlers: +```cpp +bool myRead(RegisterIO& io, FOCMotor* m) { io << (float)m->shaft_velocity; return true; } +bool myWrite(RegisterIO& io, FOCMotor* m) { float v; io >> v; m->target = v; return true; } +``` +3. Register with the commander (after `init()`): +```cpp +commander.addCustomRegister(0xE0, 4, myRead, myWrite); // size in bytes +``` +4. Access via normal CAN read/write frames using that register number. + +## CANCommander Core Features + +| Feature | Description | +|---------|-------------| +| Multi-motor addressing | Up to 16 motors per node (`motor_idx` field) | +| Register abstraction | Uniform access to control, telemetry, tuning parameters | +| Broadcast & Unicast | Address 0xFF = broadcast; per-node filtering in hardware | +| Echo-on-write | Optional read-back response for writes (`echo` flag) | +| Sync packets | Lightweight synchronization (`CAN_SYNC`) | +| Compact frames | Up to 2 floats per frame (8 data bytes) | +| Custom extension | User-defined registers without modifying core tables | +| Error tracking | Status register includes last error & register accessed | + +## Typical Exchange + +| Action | ID Fields | Data | +|--------|-----------|------| +| Read register | addr, READ_REQUEST, reg, motor | (empty) | +| Response | addr, READ_RESPONSE, reg, motor | encoded value | +| Write register | addr, WRITE_REQUEST, reg, motor | new value bytes | +| Optional echo | addr, READ_RESPONSE, reg, motor | echoed value | +| Sync | addr/broadcast, SYNC | optional ack byte | + +## Filtering +Nodes may enable strict filtering (default) or promiscuous (`no_filter=true`). Filtering masks only the address bits; broadcast (0xFF) frames pass via software check. + +## Notes +- All numeric types are raw binary (IEEE754 float, uint32_t little-endian). +- Multi-frame transfers are not used; split larger conceptual sets into separate reads. +- Keep custom register sizes ≤8 bytes for single-frame responses. +- Version tag: `SIMPLEFOC_REGISTERS_VERSION` changes only on incompatible register layout updates. + +## Minimal Usage Snippet +```cpp +CANio can(rxPin, txPin); +CANCommander commander(can, 0x12); // address 0x12 +commander.init(); +commander.addMotor(&motor); +// in loop: +commander.run(); +``` + +Add custom: +```cpp +commander.addCustomRegister(0xE1, 4, myRead, myWrite); +``` + +Refer to `SimpleFOCRegisters.h` for the authoritative register list. From d1928ab9b3254917d803e56ead85daa4f71e5929 Mon Sep 17 00:00:00 2001 From: gospar Date: Mon, 1 Dec 2025 08:34:29 +0100 Subject: [PATCH 7/7] more info about registers --- src/comms/README.md | 5 +- src/comms/REGISTERS.md | 119 ++++++++++++++++++++++++++++++++++++++++ src/comms/can/README.md | 3 +- 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/comms/REGISTERS.md diff --git a/src/comms/README.md b/src/comms/README.md index 30fb84d..ce7df38 100644 --- a/src/comms/README.md +++ b/src/comms/README.md @@ -29,6 +29,9 @@ Implementations are available for either ASCII based protocol (TextIO) or binary [SimpleFOCRegisters.h](./SimpleFOCRegisters.h) contains a list of registers known to SimpleFOC. These registers can be read and/or written and code is provided to serialize/deserialize them. +**📋 Complete register reference**: See [REGISTERS.md](./REGISTERS.md) for the full table with access types, data types, and sizes. + The SimpleFOC packet based IO (PacketCommander, Telemetry), I2CCommander and SettingsStorage as well as our Python API [PySimpleFOC](https://github.com/simplefoc/pysimplefoc) are all based on this register abstraction, and therefore share the same register names/ids. -If implementing your own communications protocol, we encourage you to base it on the Register abstraction if appropriate. This will provide you with ready to use code to access/write the register values, and will make your solution easier to use due to the shared common conventions. \ No newline at end of file +If implementing your own communications protocol, we encourage you to base it on the Register abstraction if appropriate. This will provide you with ready to use code to access/write the register values, and will make your solution easier to use due to the shared common conventions. + diff --git a/src/comms/REGISTERS.md b/src/comms/REGISTERS.md new file mode 100644 index 0000000..03f0aed --- /dev/null +++ b/src/comms/REGISTERS.md @@ -0,0 +1,119 @@ +# SimpleFOC CAN Registers Reference + +This document summarizes the registers defined in `SimpleFOCRegisters.h`, their access type (RO/RW/WO), data type, and size in bytes. Sizes reflect payload bytes in a single CAN frame. + +Note: Multi-value registers fit in one frame (≤ 8 bytes) only where indicated; larger compound values are still sent as single frames using compact encoding as implemented by `SimpleFOCRegisters`. + +## Legend +- RO: Read-only +- RW: Read/Write +- WO: Write-only +- u8: uint8_t (1 byte) +- u32: uint32_t (4 bytes) +- f32: float (4 bytes) +- 3x f32: three floats (12 bytes, may be sent across multiple frames by higher-level tooling) + +## Registers + +| ID | Name | Access | Type | Size | +|----|------|--------|------|------| +| 0x00 | REG_STATUS | RO | u8×4 (motor, last_reg, last_err, status) | 4 | +| 0x01 | REG_TARGET | RW | f32 | 4 | +| 0x03 | REG_ENABLE_ALL | WO | u8 | 1 | +| 0x04 | REG_ENABLE | RW | u8 | 1 | +| 0x05 | REG_CONTROL_MODE | RW | u8 | 1 | +| 0x06 | REG_TORQUE_MODE | RW | u8 | 1 | +| 0x07 | REG_MODULATION_MODE | RW | u8 | 1 | +| 0x09 | REG_ANGLE | RO | f32 | 4 | +| 0x10 | REG_POSITION | RO | u32 + f32 (turns + rad) | 8 | +| 0x11 | REG_VELOCITY | RO | f32 | 4 | +| 0x12 | REG_SENSOR_ANGLE | RO | f32 | 4 | +| 0x13 | REG_SENSOR_MECHANICAL_ANGLE | RO | f32 | 4 | +| 0x14 | REG_SENSOR_VELOCITY | RO | f32 | 4 | +| 0x15 | REG_SENSOR_TIMESTAMP | RO | u32 | 4 | +| 0x16 | REG_PHASE_VOLTAGE | RW | 3x f32 | 12 | +| 0x17 | REG_PHASE_STATE | RW | u8×3 | 3 | +| 0x18 | REG_DRIVER_ENABLE | RW | u8 | 1 | +| 0x1A | REG_TELEMETRY_REG | RW | u8 + n×(u8+u8) | variable | +| 0x1B | REG_TELEMETRY_CTRL | RW | u8 | 1 | +| 0x1C | REG_TELEMETRY_DOWNSAMPLE | RW | u32 | 4 | +| 0x1D | REG_ITERATIONS_SEC | RO | u32 | 4 | +| 0x20 | REG_VOLTAGE_Q | RO | f32 | 4 | +| 0x21 | REG_VOLTAGE_D | RO | f32 | 4 | +| 0x22 | REG_CURRENT_Q | RO | f32 | 4 | +| 0x23 | REG_CURRENT_D | RO | f32 | 4 | +| 0x24 | REG_CURRENT_A | RO | f32 | 4 | +| 0x25 | REG_CURRENT_B | RO | f32 | 4 | +| 0x26 | REG_CURRENT_C | RO | f32 | 4 | +| 0x27 | REG_CURRENT_ABC | RO | 3x f32 | 12 | +| 0x28 | REG_CURRENT_DC | RO | f32 | 4 | +| 0x30 | REG_VEL_PID_P | RW | f32 | 4 | +| 0x31 | REG_VEL_PID_I | RW | f32 | 4 | +| 0x32 | REG_VEL_PID_D | RW | f32 | 4 | +| 0x33 | REG_VEL_PID_LIM | RW | f32 | 4 | +| 0x34 | REG_VEL_PID_RAMP | RW | f32 | 4 | +| 0x35 | REG_VEL_LPF_T | RW | f32 | 4 | +| 0x36 | REG_ANG_PID_P | RW | f32 | 4 | +| 0x37 | REG_ANG_PID_I | RW | f32 | 4 | +| 0x38 | REG_ANG_PID_D | RW | f32 | 4 | +| 0x39 | REG_ANG_PID_LIM | RW | f32 | 4 | +| 0x3A | REG_ANG_PID_RAMP | RW | f32 | 4 | +| 0x3B | REG_ANG_LPF_T | RW | f32 | 4 | +| 0x40 | REG_CURQ_PID_P | RW | f32 | 4 | +| 0x41 | REG_CURQ_PID_I | RW | f32 | 4 | +| 0x42 | REG_CURQ_PID_D | RW | f32 | 4 | +| 0x43 | REG_CURQ_PID_LIM | RW | f32 | 4 | +| 0x44 | REG_CURQ_PID_RAMP | RW | f32 | 4 | +| 0x45 | REG_CURQ_LPF_T | RW | f32 | 4 | +| 0x46 | REG_CURD_PID_P | RW | f32 | 4 | +| 0x47 | REG_CURD_PID_I | RW | f32 | 4 | +| 0x48 | REG_CURD_PID_D | RW | f32 | 4 | +| 0x49 | REG_CURD_PID_LIM | RW | f32 | 4 | +| 0x4A | REG_CURD_PID_RAMP | RW | f32 | 4 | +| 0x4B | REG_CURD_LPF_T | RW | f32 | 4 | +| 0x50 | REG_VOLTAGE_LIMIT | RW | f32 | 4 | +| 0x51 | REG_CURRENT_LIMIT | RW | f32 | 4 | +| 0x52 | REG_VELOCITY_LIMIT | RW | f32 | 4 | +| 0x53 | REG_DRIVER_VOLTAGE_LIMIT | RW | f32 | 4 | +| 0x54 | REG_PWM_FREQUENCY | RW | u32 | 4 | +| 0x55 | REG_DRIVER_VOLTAGE_PSU | RW | f32 | 4 | +| 0x56 | REG_VOLTAGE_SENSOR_ALIGN | RW | f32 | 4 | +| 0x5F | REG_MOTION_DOWNSAMPLE | RW | u32 | 4 | +| 0x60 | REG_ZERO_ELECTRIC_ANGLE | RO | f32 | 4 | +| 0x61 | REG_SENSOR_DIRECTION | RO | u8 | 1 | +| 0x62 | REG_ZERO_OFFSET | RW | f32 | 4 | +| 0x63 | REG_POLE_PAIRS | RO | u32 | 4 | +| 0x64 | REG_PHASE_RESISTANCE | RW | f32 | 4 | +| 0x65 | REG_KV | RW | f32 | 4 | +| 0x66 | REG_INDUCTANCE | RW | f32 | 4 | +| 0x67 | REG_CURA_GAIN | RW | f32 | 4 | +| 0x68 | REG_CURB_GAIN | RW | f32 | 4 | +| 0x69 | REG_CURC_GAIN | RW | f32 | 4 | +| 0x6A | REG_CURA_OFFSET | RW | f32 | 4 | +| 0x6B | REG_CURB_OFFSET | RW | f32 | 4 | +| 0x6C | REG_CURC_OFFSET | RW | f32 | 4 | +| 0x70 | REG_NUM_MOTORS | RO | u8 | 1 | +| 0x71 | REG_SYS_TIME | RO | u32 | 4 | +| 0x7F | REG_MOTOR_ADDRESS | RW | f32 | 4 | + +## Custom Registers +- Range starts at `REG_CUSTOM_START (0xE0)` and supports up to `MAX_CUSTOM_REGISTERS` (32). +- Register handlers provide size and read/write callbacks: + +```cpp +bool addCustomRegister(uint8_t reg, uint8_t size, + RegisterReadHandler readHandler, + RegisterWriteHandler writeHandler); +``` + +Example: +```cpp +bool myRead(RegisterIO& io, FOCMotor* m) { io << (float)m->shaft_velocity; return true; } +bool myWrite(RegisterIO& io, FOCMotor* m) { float v; io >> v; m->target = v; return true; } +commander.addCustomRegister(0xE0, 4, myRead, myWrite); +``` + +## Notes +- Floats and integers are transmitted as raw little-endian binary. +- Multi-component values (e.g., 3x floats) may exceed 8 bytes; higher layers should split or compress if needed. +- The version constant `SIMPLEFOC_REGISTERS_VERSION` changes only on incompatible schema updates. diff --git a/src/comms/can/README.md b/src/comms/can/README.md index c248606..418ade0 100644 --- a/src/comms/can/README.md +++ b/src/comms/can/README.md @@ -87,7 +87,6 @@ Selected register groups (see header for full list): | Motion Targets | `REG_TARGET` (position/velocity/torque depending on mode) | | Sensor Feedback | `REG_ANGLE`, `REG_VELOCITY`, `REG_SENSOR_ANGLE`, `REG_SENSOR_VELOCITY` | | Phase / Driver | `REG_PHASE_VOLTAGE`, `REG_PHASE_STATE`, `REG_DRIVER_ENABLE` | -| Telemetry Config | `REG_TELEMETRY_REG`, `REG_TELEMETRY_CTRL`, `REG_TELEMETRY_DOWNSAMPLE`, `REG_ITERATIONS_SEC` | | FOC Internal | `REG_VOLTAGE_Q/D`, `REG_CURRENT_Q/D`, `REG_CURRENT_ABC`, `REG_CURRENT_DC` | | PID Velocity | `REG_VEL_PID_P/I/D/LIM/RAMP`, `REG_VEL_LPF_T` | | PID Position | `REG_ANG_PID_P/I/D/LIM/RAMP`, `REG_ANG_LPF_T` | @@ -99,6 +98,8 @@ Selected register groups (see header for full list): Registers are read-only (RO), write-only (WO) or read/write (R/W) as indicated in the header. +**📋 Complete register reference**: See [REGISTERS.md](../REGISTERS.md) for the full table with access types, data types, and sizes. + ## Adding a Custom Register 1. Pick an ID >= `REG_CUSTOM_START` (0xE0)