From 0b36b402002e8cfedb8caa13503df5cec11da1af Mon Sep 17 00:00:00 2001 From: ogent Date: Fri, 20 Mar 2026 22:53:02 +0000 Subject: [PATCH] example revamp and #include adjustments --- examples/CPU_Temp/README.md | 13 +++ examples/CPU_Temp/main.cpp | 38 +------- examples/HC_SR04/README.md | 14 ++- examples/HC_SR04/main.cpp | 37 +------- examples/INA_current_sensor/README.md | 17 +++- examples/INA_current_sensor/main.cpp | 25 +---- examples/Magnetometer_listener/README.md | 15 +++ examples/Magnetometer_listener/main.cpp | 84 +++++++++++++++++ examples/Servo_test/README.md | 11 +++ examples/Servo_test/main.cpp | 111 +++++++---------------- examples/thermocouple-MCP9600/README.md | 18 +++- examples/thermocouple-MCP9600/main.cpp | 24 ----- lib/libArduinoDroneCAN/src/dronecan.h | 5 + src/main.cpp | 5 - 14 files changed, 202 insertions(+), 215 deletions(-) create mode 100644 examples/CPU_Temp/README.md create mode 100644 examples/Magnetometer_listener/README.md create mode 100644 examples/Magnetometer_listener/main.cpp create mode 100644 examples/Servo_test/README.md diff --git a/examples/CPU_Temp/README.md b/examples/CPU_Temp/README.md new file mode 100644 index 0000000..2d2b19c --- /dev/null +++ b/examples/CPU_Temp/README.md @@ -0,0 +1,13 @@ +# CPU Temperature Example + +This is the simplest example and a good starting point for a new node. It reads the STM32's internal temperature sensor and ADC pins, then broadcasts a DroneCAN `BatteryInfo` message at 10Hz containing: + +- `voltage` — raw ADC reading from PA1 +- `current` — raw ADC reading from PA0 +- `temperature` — MCU core temperature in degrees Celsius + +It also demonstrates reading and writing parameters via `dronecan.getParameter()` and `dronecan.setParameter()`. + +## Dependencies + +No extra libraries required. Uses the STM32 internal ADC (`AVREF`, `ATEMP`) which is available on all supported boards. diff --git a/examples/CPU_Temp/main.cpp b/examples/CPU_Temp/main.cpp index 7c1b7de..74a1a6d 100644 --- a/examples/CPU_Temp/main.cpp +++ b/examples/CPU_Temp/main.cpp @@ -1,14 +1,5 @@ - -/* -API version v1.3 -*/ - #include #include -#include -#include -#include -#include // set up your parameters here with default values. NODEID should be kept std::vector custom_parameters = { @@ -20,40 +11,13 @@ DroneCAN dronecan; uint32_t looptime = 0; -/* -This function is called when we receive a CAN message, and it's accepted by the shouldAcceptTransfer function. -We need to do boiler plate code in here to handle parameter updates and so on, but you can also write code to interact with sent messages here. -*/ -static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) -{ - DroneCANonTransferReceived(dronecan, ins, transfer); -} - -/* -For this function, we need to make sure any messages we want to receive follow the following format with -UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID as an example - */ -static bool shouldAcceptTransfer(const CanardInstance *ins, - uint64_t *out_data_type_signature, - uint16_t data_type_id, - CanardTransferType transfer_type, - uint8_t source_node_id) - -{ - return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); -} - void setup() { // the following block of code should always run first. Adjust it at your own peril! app_setup(); - IWatchdog.begin(2000000); + IWatchdog.begin(2000000); Serial.begin(115200); - dronecan.version_major = 1; - dronecan.version_minor = 0; dronecan.init( - onTransferReceived, - shouldAcceptTransfer, custom_parameters, "Beyond Robotix Node" ); diff --git a/examples/HC_SR04/README.md b/examples/HC_SR04/README.md index d9fc3ef..339aa38 100644 --- a/examples/HC_SR04/README.md +++ b/examples/HC_SR04/README.md @@ -1,6 +1,16 @@ -# HC SR04 Rangefinder +# HC-SR04 Rangefinder -Uses this library: +Reads distance from an HC-SR04 ultrasonic sensor and broadcasts it as a DroneCAN `RangeSensor` measurement at 10Hz. + +The sensor is wired to: +- Trigger: PA8 +- Echo: PA9 + +## Dependencies + +Install the HC-SR04 Arduino library by Martinsos: https://github.com/Martinsos/arduino-lib-hc-sr04 +Download the zip and place the unzipped folder in the `lib` directory of your project. Make sure there are no nested folders inside (this can happen when unzipping from GitHub). +Alternatively, install it via the PlatformIO library manager. diff --git a/examples/HC_SR04/main.cpp b/examples/HC_SR04/main.cpp index e121106..cc85117 100644 --- a/examples/HC_SR04/main.cpp +++ b/examples/HC_SR04/main.cpp @@ -1,13 +1,5 @@ - -/* -API version v1.3 -*/ - #include #include -#include -#include -#include #include std::vector custom_parameters = { @@ -18,29 +10,6 @@ DroneCAN dronecan; uint32_t looptime = 0; -/* -This function is called when we receive a CAN message, and it's accepted by the shouldAcceptTransfer function. -We need to do boiler plate code in here to handle parameter updates and so on, but you can also write code to interact with sent messages here. -*/ -static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) -{ - DroneCANonTransferReceived(dronecan, ins, transfer); -} - -/* -For this function, we need to make sure any messages we want to receive follow the following format with -UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID as an example - */ -static bool shouldAcceptTransfer(const CanardInstance *ins, - uint64_t *out_data_type_signature, - uint16_t data_type_id, - CanardTransferType transfer_type, - uint8_t source_node_id) - -{ - return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); -} - const byte triggerPin = PA8; const byte echoPin = PA9; UltraSonicDistanceSensor distanceSensor(triggerPin, echoPin); @@ -50,13 +19,9 @@ void setup() { // the following block of code should always run first. Adjust it at your own peril! app_setup(); - IWatchdog.begin(2000000); + IWatchdog.begin(2000000); Serial.begin(115200); - dronecan.version_major = 1; - dronecan.version_minor = 0; dronecan.init( - onTransferReceived, - shouldAcceptTransfer, custom_parameters, "Beyond Robotix Node" ); diff --git a/examples/INA_current_sensor/README.md b/examples/INA_current_sensor/README.md index bc6af53..8776d74 100644 --- a/examples/INA_current_sensor/README.md +++ b/examples/INA_current_sensor/README.md @@ -1,5 +1,16 @@ -# INA current sensor example +# INA239 Current Sensor -This example illustrates how an already availible library can be used to easily integrate a new sensor into dronecan. +Reads voltage, current, and temperature from an INA239 SPI current/power monitor and broadcasts them as a DroneCAN `BatteryInfo` message at 10Hz. -**this is untested code currently** I'll be able to test it at some point when I get an INA to hand. +The INA239 is initialised with a 10A max current range and a 15mΩ shunt resistor — adjust the `INA.setMaxCurrentShunt()` call in `main.cpp` to match your hardware. + +> **Note:** This example is untested. It is provided as an illustration of how to integrate an SPI sensor with the DroneCAN library. + +## Dependencies + +Install the INA239 library by RobTillaart: +https://github.com/RobTillaart/INA239 + +Download the zip and place the unzipped folder in the `lib` directory of your project. Make sure there are no nested folders inside (this can happen when unzipping from GitHub). + +Alternatively, install it via the PlatformIO library manager. diff --git a/examples/INA_current_sensor/main.cpp b/examples/INA_current_sensor/main.cpp index a3775e0..108bd0d 100644 --- a/examples/INA_current_sensor/main.cpp +++ b/examples/INA_current_sensor/main.cpp @@ -7,11 +7,7 @@ #include #include -#include -#include -#include #include "INA239.h" -#include INA239 INA(5, &SPI); @@ -22,32 +18,13 @@ DroneCAN dronecan; uint32_t looptime = 0; -static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) -{ - DroneCANonTransferReceived(dronecan, ins, transfer); -} - -static bool shouldAcceptTransfer(const CanardInstance *ins, - uint64_t *out_data_type_signature, - uint16_t data_type_id, - CanardTransferType transfer_type, - uint8_t source_node_id) - -{ - return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); -} - void setup() { // the following block of code should always run first. Adjust it at your own peril! app_setup(); - IWatchdog.begin(2000000); + IWatchdog.begin(2000000); Serial.begin(115200); - dronecan.version_major = 1; - dronecan.version_minor = 0; dronecan.init( - onTransferReceived, - shouldAcceptTransfer, custom_parameters, "Beyond Robotix Node" ); diff --git a/examples/Magnetometer_listener/README.md b/examples/Magnetometer_listener/README.md new file mode 100644 index 0000000..9fee3b8 --- /dev/null +++ b/examples/Magnetometer_listener/README.md @@ -0,0 +1,15 @@ +# Magnetometer Listener + +Demonstrates how to receive DroneCAN messages using the full callback API (`onTransferReceived` / `shouldAcceptTransfer`). + +Listens for `MagneticFieldStrength` broadcast messages on the bus and prints the X, Y, Z field values in Gauss to Serial. + +Use this as a template when you need to receive any DroneCAN message type — copy the pattern in `onTransferReceived` and `shouldAcceptTransfer`, substituting the message ID, signature, struct, and decode call for your target message. + +## When to use the callback API + +The simplified `dronecan.init(parameters, name)` used in most examples handles everything internally. Use the 4-argument form shown here when your node needs to **receive** messages from other nodes on the bus. + +## Dependencies + +No extra libraries required. diff --git a/examples/Magnetometer_listener/main.cpp b/examples/Magnetometer_listener/main.cpp new file mode 100644 index 0000000..aeebca4 --- /dev/null +++ b/examples/Magnetometer_listener/main.cpp @@ -0,0 +1,84 @@ +/* +Demonstrates receiving DroneCAN messages using the full callback API. + +Listens for MagneticFieldStrength broadcast messages and prints the X, Y, Z +field values (in Gauss) to Serial at whatever rate the sender transmits them. + +Use this as a template when you need to receive any DroneCAN message type — +copy the pattern in onTransferReceived and shouldAcceptTransfer, substituting +the message ID, signature, struct, and decode call for your target message. +*/ + +#include +#include + +std::vector custom_parameters = { + { "NODEID", DroneCAN::INT, 100, 0, 127 }, +}; + +DroneCAN dronecan; + +static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) +{ + switch (transfer->data_type_id) + { + case UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID: + { + uavcan_equipment_ahrs_MagneticFieldStrength pkt{}; + uavcan_equipment_ahrs_MagneticFieldStrength_decode(transfer, &pkt); + + Serial.print("Mag X: "); Serial.print(pkt.magnetic_field_ga[0]); + Serial.print(" Y: "); Serial.print(pkt.magnetic_field_ga[1]); + Serial.print(" Z: "); Serial.println(pkt.magnetic_field_ga[2]); + break; + } + } + + DroneCANonTransferReceived(dronecan, ins, transfer); +} + +static bool shouldAcceptTransfer(const CanardInstance *ins, + uint64_t *out_data_type_signature, + uint16_t data_type_id, + CanardTransferType transfer_type, + uint8_t source_node_id) +{ + if (transfer_type == CanardTransferTypeBroadcast) + { + switch (data_type_id) + { + case UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID: + *out_data_type_signature = UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_SIGNATURE; + return true; + } + } + + return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); +} + +void setup() +{ + // the following block of code should always run first. Adjust it at your own peril! + app_setup(); + IWatchdog.begin(2000000); + Serial.begin(115200); + dronecan.init( + onTransferReceived, + shouldAcceptTransfer, + custom_parameters, + "Beyond Robotix Listener" + ); + // end of important starting code + + // we use a while true loop instead of the arduino "loop" function since that causes issues. + while (true) + { + dronecan.cycle(); + IWatchdog.reload(); + } +} + +void loop() +{ + // Doesn't work coming from bootloader ? use while loop in setup +} diff --git a/examples/Servo_test/README.md b/examples/Servo_test/README.md new file mode 100644 index 0000000..2f0fd30 --- /dev/null +++ b/examples/Servo_test/README.md @@ -0,0 +1,11 @@ +# Servo Test + +Listens for DroneCAN `ArrayCommand` messages and drives a servo on PA8 to the commanded position. This also demonstrates how to use the callback API (`onTransferReceived` / `shouldAcceptTransfer`) to receive DroneCAN messages. + +The actuator ID this node responds to is configured via the `ACTUATOR_ID` parameter (default `0`), which can be changed at runtime via a ground station. + +`command_value` is expected in the range `-1.0` to `1.0`, which maps to `0`–`180` degrees. This matches the range sent by ArduPilot for servo outputs over DroneCAN. + +## Dependencies + +No extra libraries required. The `Servo` library is included with the STM32 Arduino core. diff --git a/examples/Servo_test/main.cpp b/examples/Servo_test/main.cpp index 60ea97e..6096836 100644 --- a/examples/Servo_test/main.cpp +++ b/examples/Servo_test/main.cpp @@ -1,52 +1,46 @@ /* -Just shows PA8 commanding a servo signal! Doesn't respond to CAN servo commands. -*/ +Listens for DroneCAN ArrayCommand messages and drives a servo on PA8 to the +commanded position. The actuator ID this node responds to is set by the +ACTUATOR_ID parameter (default 0). +command_value is expected in the range -1.0 to 1.0, which maps to 0-180 degrees. +This matches the range sent by ArduPilot for servo outputs over DroneCAN. +*/ #include #include -#include -#include -#include -#include #include +Servo myservo; -Servo myservo; - -// set up your parameters here with default values. NODEID should be kept std::vector custom_parameters = { - { "NODEID", DroneCAN::INT, 100, 0, 127 }, - { "PARM_1", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_2", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_3", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_4", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_5", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_6", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, - { "PARM_7", DroneCAN::REAL, 0.0f, 0.0f, 100.0f }, + { "NODEID", DroneCAN::INT, 100, 0, 127 }, + { "ACTUATOR_ID", DroneCAN::INT, 0, 0, 14 }, }; DroneCAN dronecan; -uint32_t looptime = 0; - -/* -This function is called when we receive a CAN message, and it's accepted by the shouldAcceptTransfer function. -We need to do boiler plate code in here to handle parameter updates and so on, but you can also write code to interact with sent messages here. -*/ static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) { - - // switch on data type ID to pass to the right handler function - // if (transfer->transfer_type == CanardTransferTypeRequest) - // check if we want to handle a specific service request switch (transfer->data_type_id) { - - case UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID: + case UAVCAN_EQUIPMENT_ACTUATOR_ARRAYCOMMAND_ID: { - uavcan_equipment_ahrs_MagneticFieldStrength pkt{}; - uavcan_equipment_ahrs_MagneticFieldStrength_decode(transfer, &pkt); + uavcan_equipment_actuator_ArrayCommand pkt{}; + uavcan_equipment_actuator_ArrayCommand_decode(transfer, &pkt); + + int actuator_id = (int)dronecan.getParameter("ACTUATOR_ID"); + + for (uint8_t i = 0; i < pkt.commands.len; i++) + { + if (pkt.commands.data[i].actuator_id == actuator_id) + { + // map -1.0..1.0 to 0..180 degrees + float angle = (pkt.commands.data[i].command_value + 1.0f) * 90.0f; + myservo.write((int)constrain(angle, 0, 180)); + break; + } + } break; } } @@ -54,85 +48,44 @@ static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) DroneCANonTransferReceived(dronecan, ins, transfer); } -/* -For this function, we need to make sure any messages we want to receive follow the following format with -UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID as an example - */ static bool shouldAcceptTransfer(const CanardInstance *ins, uint64_t *out_data_type_signature, uint16_t data_type_id, CanardTransferType transfer_type, uint8_t source_node_id) - { if (transfer_type == CanardTransferTypeBroadcast) { - // Check if we want to handle a specific broadcast packet switch (data_type_id) { - case UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_ID: - { - *out_data_type_signature = UAVCAN_EQUIPMENT_AHRS_MAGNETICFIELDSTRENGTH_SIGNATURE; + case UAVCAN_EQUIPMENT_ACTUATOR_ARRAYCOMMAND_ID: + *out_data_type_signature = UAVCAN_EQUIPMENT_ACTUATOR_ARRAYCOMMAND_SIGNATURE; return true; } - } } return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); } void setup() -{ +{ // the following block of code should always run first. Adjust it at your own peril! app_setup(); - IWatchdog.begin(2000000); + IWatchdog.begin(2000000); Serial.begin(115200); - dronecan.version_major = 1; - dronecan.version_minor = 0; dronecan.init( - onTransferReceived, - shouldAcceptTransfer, + onTransferReceived, + shouldAcceptTransfer, custom_parameters, - "Beyond Robotix Node" + "Beyond Robotix Servo" ); // end of important starting code - - - // an example of getting and setting parameters within the code - dronecan.setParameter("PARM_1", 50.0f); // set a parameter to 50.0 - Serial.print("PARM_1 value: "); - Serial.println(dronecan.getParameter("PARM_1")); myservo.attach(PA_8); - int i = 1000; - // we use a while true loop instead of the arduino "loop" function since that causes issues. while (true) { - const uint32_t now = millis(); - - // send our battery message at 10Hz - // Don't use delay() since we need to call dronecan.cycle() as much as possible - if (now - looptime > 100) - { - looptime = millis(); - - // collect MCU core temperature data - int32_t vref = __LL_ADC_CALC_VREFANALOG_VOLTAGE(analogRead(AVREF), LL_ADC_RESOLUTION_12B); - int32_t cpu_temp = __LL_ADC_CALC_TEMPERATURE(vref, analogRead(ATEMP), LL_ADC_RESOLUTION_12B); - - // construct dronecan packet - uavcan_equipment_power_BatteryInfo pkt{}; - pkt.voltage = analogRead(PA1); - pkt.current = analogRead(PA0); - pkt.temperature = cpu_temp; - - sendUavcanMsg(dronecan.canard, pkt); - - myservo.write(i++); - } - dronecan.cycle(); IWatchdog.reload(); } diff --git a/examples/thermocouple-MCP9600/README.md b/examples/thermocouple-MCP9600/README.md index 68633aa..1204019 100644 --- a/examples/thermocouple-MCP9600/README.md +++ b/examples/thermocouple-MCP9600/README.md @@ -1,9 +1,17 @@ # Thermocouple MCP9600 -This example project integrates the MCP9600 as a CAN device. +Integrates an MCP9600 I2C thermocouple amplifier as a DroneCAN node. The node is configured for a K-type thermocouple and broadcasts: -You'll need to add some dependancies: -- https://github.com/adafruit/Adafruit_MCP9600/releases/tag/2.0.4 -- https://github.com/adafruit/Adafruit_BusIO/releases/tag/1.17.2 +- `Temperature` message at 1Hz — always sent, uses the `DEVICE_ID` parameter as the device ID +- `BatteryInfo` message at 10Hz — only sent when the `BATT_EN` parameter is set to `1` -These can be installed via PlatformIO or by manually downloading the zips. The unzipped directorys will need placing in the "lib" folder. Make sure they don't have nested folders, which can happen when unzipping. +The `DEVICE_ID` and `BATT_EN` parameters can be changed at runtime via a DroneCAN ground station (e.g. Mission Planner or QGroundControl). + +The MCP9600 is expected at I2C address `0x66`. If the sensor is not found on boot, the node will log a debug message over DroneCAN and keep retrying. + +## Dependencies + +Install the following libraries. Download the zips and place the unzipped folders in the `lib` directory of your project. Make sure there are no nested folders inside (this can happen when unzipping from GitHub). Alternatively, install via the PlatformIO library manager. + +- Adafruit MCP9600: https://github.com/adafruit/Adafruit_MCP9600/releases/tag/2.0.4 +- Adafruit BusIO (required by MCP9600 library): https://github.com/adafruit/Adafruit_BusIO/releases/tag/1.17.2 diff --git a/examples/thermocouple-MCP9600/main.cpp b/examples/thermocouple-MCP9600/main.cpp index 55ca735..2e5a0a5 100644 --- a/examples/thermocouple-MCP9600/main.cpp +++ b/examples/thermocouple-MCP9600/main.cpp @@ -1,10 +1,5 @@ #include #include -#include -#include -#include -#include - #include "Adafruit_MCP9600.h" #include "Wire.h" @@ -29,32 +24,13 @@ MCP9600 specific setup #define I2C_ADDRESS (0x66) Adafruit_MCP9600 mcp; -static void onTransferReceived(CanardInstance *ins, CanardRxTransfer *transfer) -{ - DroneCANonTransferReceived(dronecan, ins, transfer); -} - -static bool shouldAcceptTransfer(const CanardInstance *ins, - uint64_t *out_data_type_signature, - uint16_t data_type_id, - CanardTransferType transfer_type, - uint8_t source_node_id) - -{ - return false || DroneCANshouldAcceptTransfer(ins, out_data_type_signature, data_type_id, transfer_type, source_node_id); -} - void setup() { // the following block of code should always run first. Adjust it at your own peril! app_setup(); IWatchdog.begin(2000000); Serial.begin(115200); - dronecan.version_major = 1; - dronecan.version_minor = 0; dronecan.init( - onTransferReceived, - shouldAcceptTransfer, custom_parameters, "BR-Node-Temperature"); // end of important starting code diff --git a/lib/libArduinoDroneCAN/src/dronecan.h b/lib/libArduinoDroneCAN/src/dronecan.h index c68831d..2b1eaa0 100644 --- a/lib/libArduinoDroneCAN/src/dronecan.h +++ b/lib/libArduinoDroneCAN/src/dronecan.h @@ -3,6 +3,11 @@ #include #include +#include +#include +#include +#include + #ifdef CANL431 #include #endif diff --git a/src/main.cpp b/src/main.cpp index 8494170..33d1daf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,5 @@ #include #include -#include -#include -#include -#include - // set up your parameters here with default values. NODEID should be kept std::vector custom_parameters = {