Skip to content

5. Managing device setting

Nathan zhou edited this page Nov 12, 2025 · 10 revisions

You might wonder what REV hardware Client and CTRE Phoenix Tuner does? How they communicate with the device?

The main difference between CTRE Tuner X and REV Client is how connection to the actual device is handled. CTRE use a more complicated implementation which had a CAN bridging service on roboRIO (phoenix diagnostic) that hardware client connect to. On the other hand, REV use USB direct to device connection which the USB interface act as a CAN adaptor (preparatory to REV).

They both had their own benefit. CTRE always require roboRIO to configure device, but you could do it wirelessly. On the other hand, REV doesn’t require roboRIO but require USB tethering to the device itself. Both manufacturer use GitHub - wpilibsuite/vendor-template to create their library while our roboRIO implementation is simpler.

do I need to create a hardware client?

The short answer is no. but we still need some massage to manage device setting. Here are some of the options.

Method 1, hard code everything:

Hard code all configuration to the device. CAN Device Number change would require re-flashing the firmware via USB after changing it on the code. Also all the settings are hardcoded. (for things like battery RFID this is completely fine).

Method 2, partially hard code (recommended):

CAN Device Number and default setting is hardcoded. However, most setting could be modified from roboRIO by API. To facilitate these, your sensor needs to echo back it’s configuration to roboRIO. RoboRIO would compare it to your code set configuration and automatically apply settings to CAN device if discrepancy is found. I actually do the exact same thing in my ToF project. These settings will lost if MCU is powered down.

To extend on Method 3, you could add a command that if received by ESP32, it would save its current setting. You can also create a command to set CAN Device Number. However, if you do this, you would need to have your robot code handle the ID change which add a lot of complication. I wouldn’t suggest doing CAN Device Number change via CAN command from roboRIO because this is just too much work. Re-flash your MCU during competition is more feasible and faster in competition.

Method 3, a very basic hardware client:

Create some serial protocol to change settings and make a custom program to interrogate with ESP32. You could do this on top of method 2, or do a partial implementation (for example, serial only used for CAN Device Number change), and all settings relating to the sensor itself are pulled from roboRIO.

Method 4, a full hardware client:

Create a full hardware client that could do everything. This is a lot of work. Unless you plan on selling a product, it might not worth the effort.

Dig deeper into method 2

Here is a basic flow chart of how this will work.

+------------------------------+
|         Power On / Reset    |
+------------------------------+
              |
              v
+---------------------------------------------+
| Check Flash Memory for Saved CAN Device ID  |
+---------------------------------------------+
         |                           |
         | Setting Found            | No Setting Found
         v                           v
+-------------------+         +--------------------+
| Use Saved Setting |         | Use Default Setting|
+-------------------+         +--------------------+
              |
              v
+-----------------------------------------+
| Start 3 Tasks (Parallel Execution):     |
| 1. Serial CAN ID Helper                 |
| 2. CAN Send                             |
| 3. CAN Receive                          |
+-----------------------------------------+

         |               |                 |
         |               |                 |
         v               v                 v

+-------------------+   +---------------------------+   +----------------------------+
| Serial CAN ID     |   |      CAN Send Task        |   |     CAN Receive Task       |
| Helper Task       |   |---------------------------|   |----------------------------|
|-------------------|   | Send current sensor config|   | On new config frame:       |
| If "change ID" cmd|   |---------------------------|   | Apply setting to sensor    |
| → Apply new ID    |   | RoboRIO compares desired  |   +----------------------------+
| If "save" cmd     |   | vs. actual config         |
| → Write to Flash  |   | If different → send frame |
+-------------------+   +---------------------------+

Currently, this is implemented in addressable LED project. CAN device number change can be done by using Arduino IDE serial monitor or Web Tool. Source code of the web tool can be found in this repository. You could also save that HTML file and use it offline. Only Chrome is supported.

image

Under this setup, CAN device number can be setted using serial command. This setting will be saved inside ESP32 and will not be overwritten, even if you flushed the firmware. The only way to clear it is use ESPTool to erase flash.

https://randomnerdtutorials.com/esp32-erase-flash-memory/

Sample code

  • Set the device number temporarily (&CANID SET xx)
  • Save it permanently to EEPROM (&CANID SAVE)
  • Retrieve current, saved, and default IDs (&CANID GET)

Full Example Code

#include <Arduino.h>
#include <EEPROM.h>

// =================== Configuration ===================
#define EEPROM_ADDRESS 64
#define DEFAULT_DEVICE_NUMBER 9     // default fallback ID
#define DEVICE_ID        0x0A       // FRC Device Type (fixed)
#define MANUFACTURER_ID  0x08       // FRC Manufacturer ID (fixed)

// Global CAN Device Number
volatile uint8_t g_deviceNumber = DEFAULT_DEVICE_NUMBER;

// =================== EEPROM Functions ===================
void EEPROMReadCANID() {
  uint8_t saved = EEPROM.read(0);
  g_deviceNumber = (saved <= 63) ? saved : DEFAULT_DEVICE_NUMBER;
}

void EEPROMSaveCANID() {
  EEPROM.write(0, (uint8_t)g_deviceNumber);
  EEPROM.commit();
}

// =================== Serial CAN ID Helper ===================
void TaskCANIDHelper(void* parameter) {
  Serial.println("[CANID] Helper task started. Use &CANID SET xx / SAVE / GET");

  while (true) {
    if (Serial.available()) {
      String line = Serial.readStringUntil('\n');
      line.trim();

      // --- Set live CAN ID ---
      if (line.startsWith("&CANID SET ")) {
        int val = line.substring(11).toInt();
        if (val >= 0 && val <= 63) {
          g_deviceNumber = (uint8_t)val;
          Serial.printf("[CANID] Running DEVICE_NUMBER set to %d\n", (int)g_deviceNumber);
        } else {
          Serial.println("[CANID] Invalid value. Must be 0–63.");
        }
      }

      // --- Save to EEPROM and reboot ---
      else if (line.equals("&CANID SAVE")) {
        EEPROMSaveCANID();
        Serial.println("[CANID] Saved to EEPROM. Rebooting...");
        delay(1000);
        ESP.restart();
      }

      // --- Query CAN ID status ---
      else if (line.equals("&CANID GET")) {
        uint8_t eepromVal = EEPROM.read(0);
        Serial.printf("[CANID] Current=%d, EEPROM=%d, Default=%d\n",
                      (int)g_deviceNumber, (int)eepromVal, (int)DEFAULT_DEVICE_NUMBER);
      }
    }

    // Small delay to avoid tight loop
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

// =================== CAN ID Encoding Helper ===================
static inline uint32_t encode_id(uint8_t dt, uint8_t man, uint16_t api, uint8_t dn) {
  return ((uint32_t)dt << 24) | ((uint32_t)man << 16) | ((uint32_t)api << 6) | (dn & 0x3F);
}

static inline uint32_t make_can_id(uint16_t api) {
  return encode_id(DEVICE_ID, MANUFACTURER_ID, api, (uint8_t)g_deviceNumber);
}

// =================== Setup ===================
void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("\n=== ESP32 FRC CAN ID Helper Example ===");

  // Initialize EEPROM and load CAN ID
  EEPROM.begin(EEPROM_ADDRESS);
  EEPROMReadCANID();
  Serial.printf("[BOOT] DEVICE_NUMBER=%d\n", (int)g_deviceNumber);

  // Start helper task for serial command parsing
  xTaskCreatePinnedToCore(TaskCANIDHelper, "TaskCANIDHelper", 4096, nullptr, 1, nullptr, APP_CPU_NUM);
  Serial.println("[BOOT] Setup complete. Ready for &CANID commands.");
}

void loop() {
  // Nothing here — all logic handled in FreeRTOS task
}

How It Works

1. EEPROM Initialization

When the ESP32 boots, it reads the saved device number from EEPROM:

EEPROM.begin(EEPROM_ADDRESS);
EEPROMReadCANID();

If no valid value is found, it defaults to:

#define DEFAULT_DEVICE_NUMBER 9

2. Serial Command Processing

The helper task continuously monitors serial input:

  • Reads complete lines using Serial.readStringUntil('\n')
  • Matches them against &CANID commands
  • Updates or saves the ID accordingly

Each valid command prints confirmation via Serial.printf.

3. Saving the ID

When &CANID SAVE is issued:

  • The current g_deviceNumber is written to EEPROM
  • The ESP32 reboots
  • On reboot, the stored value is reloaded and applied globally

4. Dynamic CAN ID Updates

Whenever a CAN message is built using make_can_id(), it automatically uses the current live device number:

uint32_t id = make_can_id(0x185); // Example API ID

This allows immediate application of new IDs even before rebooting.

Example Serial Output

=== ESP32 FRC CAN ID Helper Example ===
[BOOT] DEVICE_NUMBER=9
[BOOT] Setup complete. Ready for &CANID commands.
[CANID] Helper task started. Use &CANID SET xx / SAVE / GET

> &CANID GET
[CANID] Current=9, EEPROM=9, Default=9

> &CANID SET 12
[CANID] Running DEVICE_NUMBER set to 12

> &CANID SAVE
[CANID] Saved to EEPROM. Rebooting...

... reboot ...
[BOOT] DEVICE_NUMBER=12

Notes and Best Practices

  • Each ESP32 node on the CAN bus must have a unique device number (0–63).
  • The EEPROM can wear out after many write cycles; avoid frequent saves.
  • Always use Serial.begin(115200) to match Web Tool baud rate.
  • The helper task can safely run alongside other FreeRTOS tasks like CAN RX/TX.
  • The CAN ID will update live immediately after &CANID SET, even without reboot. However &CANID SAVE will trigger a reboot.

Clone this wiki locally