Skip to content

Make AddressChanger interactive #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
264 changes: 230 additions & 34 deletions examples/Utilities/AddressChanger/AddressChanger.ino
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,256 @@

#include "Wire.h"

// Setting new_address to 0 means that the module will get back its original address
const uint8_t new_address = 0;
struct DetectedModulino {
uint8_t addr;
String modulinoType;
String pinstrap;
String defaultAddr;
};

#define MAX_DEVICES 16
DetectedModulino rows[MAX_DEVICES];
int numRows = 0;

uint8_t address;

void setup() {
Wire1.begin();
Serial.begin(115200);
delay(1000);
if (new_address != 0 && (new_address < 8 || new_address > 0x77)) {
Serial.println("Address outside valid range");
while (1);

delay(600);
discoverDevices();
}

bool waitingInput = false;
void loop() {
if (numRows == 0) return;
if (Serial.available() == 0 && waitingInput) return;

if (Serial.available() > 0) {
String hex1 = Serial.readStringUntil(' '); // Read until space (or other delimiter)
String hex2 = Serial.readStringUntil('\n'); // Read until newline
Serial.println("> " + hex1 + " " + hex2); // Print what the user inserted.

int num1 = parseHex(hex1); // Parse the first hex number
int num2 = parseHex(hex2); // Parse the second hex number
if (num1 == -1 || num2 == -1) {
Serial.println("Error: Incomplete or invalid input. Please enter two hexadecimal numbers");
return;
}

bool success = updateI2cAddress(num1, num2);
if (!success) return; // If the update failed, skip discovery and messages, and wait for input again.

discoverDevices();
waitingInput = false;
}

Serial.println("Enter the current address, space, and new address (ex. \"0x20 0x30\" or \"20 2A\"):");
Serial.println(" - Enter \"<addr> 0\" to reset the device at <addr> to its default address.");
Serial.println(" - Enter \"0 0\" to reset all devices to the default address.");
waitingInput = true;
}

// Updates the device at current address to new address. Supports broadcasting and setting default address (0).
// Returns true if the update was successful, false otherwise.
bool updateI2cAddress(int curAddress, int newAddress) {
uint8_t data[40] = { 'C', 'F', newAddress * 2 };
memset(data + 3, 0, sizeof(data) - 3); // Zero the rest of the buffer.

// Validate the current address, it must match a detected device.
if (curAddress != 0 && !findRow(curAddress)) {
Serial.println("Error: current address 0x" + String(curAddress, HEX) + " not found in the devices list\n");
return false;
}

if (curAddress != 0 && isFixedAddrDevice(curAddress)) {
Serial.println("Error: address 0x" + String(curAddress, HEX) + " is a non configurable device\n");
return false;
}

// Validate the new address.
if (newAddress != 0 && (newAddress < 8 || newAddress > 0x77)) {
Serial.println("Error: new address 0x" + String(newAddress, HEX) + " must be from 0x08 to 0x77\n");
return false;
}

if (curAddress == 0) {
Serial.print("Updating all devices (broadcast 0x00) to 0x" + String(newAddress, HEX));
} else {
Serial.print("Updating the device address from 0x" + String(curAddress, HEX) + " to 0x" + String(newAddress, HEX));
}
if (newAddress == 0) Serial.print(" (default address)");
Serial.print("...");

Wire1.beginTransmission(curAddress);
Wire1.write(data, 40);
Wire1.endTransmission();

delay(500);

if (newAddress == 0) {
Serial.println(" done\n");
return true;
} else {
Wire1.requestFrom(newAddress, 1);
if (Wire1.available()) {
Serial.println(" done\n");
return true;
} else {
Serial.println(" error\n");
return false;
}
}
}

// Function to parse hex number (with or without 0x prefix)
int parseHex(String hexStr) {
hexStr.trim();

if (hexStr.length() == 0) {
return -1;
}

if (hexStr.startsWith("0x") || hexStr.startsWith("0X")) {
hexStr = hexStr.substring(2); // Remove the "0x" prefix
}
// Search for devices and wait for user confirmation
for (int i = 8; i < 128; i++) {
Wire1.beginTransmission(i);
auto err = Wire1.endTransmission();
if (err == 0) {
Serial.print("Found device at ");
Serial.println(i);
address = i;
Serial.println("Press 'c' to configure te new address");

// Validate that the remaining string contains only valid hexadecimal characters (0-9, A-F, a-f)
for (int i = 0; i < hexStr.length(); i++) {
if (!isHexDigit(hexStr.charAt(i))) {
return -1;
}
}

return strtol(hexStr.c_str(), NULL, 16);
}

bool isHexDigit(char c) {
return ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'));
}

void discoverDevices() {
char buffer[64];
Serial.println("ADDR\tMODULINO\tPINSTRAP\tDEFAULT ADDR"); // Table heading.

numRows = 0;

// Discover all modulino devices connected to the I2C bus.
for (int addr = 8; addr < 128; addr++) {
Wire1.beginTransmission(addr);
if (Wire1.endTransmission() != 0) continue;

if (numRows >= MAX_DEVICES) {
Serial.println("Too many devices connected, maximum supported is" + String(MAX_DEVICES));
return;
}

// Some addresses represent non configurable devices (no MCU on it). Handle them as a special case.
if (isFixedAddrDevice(addr)) {
snprintf(buffer, 64, "0x%02X (cannot change)", addr);
addRow(addr, fixedAddrToName(addr), "-", String(buffer));

continue; // Stop here, do not try to communicate with this device.
}

{
uint8_t pinstrap = 0; // Variable to store the pinstrap (device type)
Wire1.beginTransmission(addr); // Begin I2C transmission to the current address
Wire1.write(0x00); // Send a request to the device (assuming 0x00 is the register for device type)
Wire1.endTransmission(); // End transmission

delay(50); // Delay to allow for the device to respond

Wire1.requestFrom(addr, 1); // Request 1 byte from the device at the current address
if (Wire1.available()) {
pinstrap = Wire1.read(); // Read the device type (pinstrap)
} else {
// If an error happens in the range 0x78 to 0x7F, ignore it.
if (addr >= 0x78) continue;
Serial.println("Failed to read device type at address 0x" + String(addr, HEX));
}

snprintf(buffer, 64, "0x%02X", pinstrap);
auto hexPinstrap = String(buffer);

snprintf(buffer, 64, "0x%02X", pinstrap / 2); // Default address is half pinstrap.
auto defaultAddr = String(buffer);
if (addr != pinstrap / 2) defaultAddr += " *"; // Mark devices with modified address.

addRow(addr, pinstrapToName(pinstrap), hexPinstrap, defaultAddr);
}
}

// Print the results.
for (int i = 0; i < numRows; i++) {
char buffer[16];
snprintf(buffer, 16, "0x%02X", rows[i].addr);

Serial.print(fixedWidth(buffer, 8));
Serial.print(fixedWidth(rows[i].modulinoType, 16));
Serial.print(fixedWidth(rows[i].pinstrap, 16));
Serial.println(fixedWidth(rows[i].defaultAddr, 12));
}
}

void addRow(uint8_t address, String modulinoType, String pinstrap, String defaultAddr) {
if (numRows >= MAX_DEVICES) return;

rows[numRows].addr = address;
rows[numRows].modulinoType = modulinoType;
rows[numRows].pinstrap = pinstrap;
rows[numRows].defaultAddr = defaultAddr;
numRows++; // Increment the row counter
}

bool findRow(uint8_t address) {
for (int i = 0; i < numRows; i++) {
if (rows[i].addr == address) return true;
}
return false;
}


// Function to add padding to the right to ensure each field has a fixed width
String fixedWidth(String str, int width) {
for (int i = str.length(); i < width; i++) str += ' ';
return str;
}

String pinstrapToName(uint8_t pinstrap) {
switch (pinstrap) {
case 0x3C:
return "BUZZER";
return "Buzzer";
case 0x7C:
return "BUTTONS";
return "Buttons";
case 0x76:
case 0x74:
return "ENCODER";
return "Encoder";
case 0x6C:
return "SMARTLEDS";
return "Smartleds";
}
return "UNKNOWN";
}

void loop() {
// put your main code here, to run repeatedly:
if (Serial.available()) {
if (Serial.read() == 'c') {
Serial.print("Assigning new address to ");
Serial.println(address);
uint8_t data[40] = { 'C', 'F', new_address * 2 };
Wire1.beginTransmission(address);
Wire1.write(data, 40);
Wire1.endTransmission();
delay(1000);
Wire1.requestFrom(new_address, 1);
Serial.println("Device type " + pinstrapToName(Wire1.read()) + " at new address " + String(new_address));
}
String fixedAddrToName(uint8_t address) {
switch (address) {
case 0x29:
return "Distance";
case 0x44:
return "Thermo";
case 0x6A:
case 0x6B:
return "Movement";
}
return "UNKNOWN";
}

bool isFixedAddrDevice(uint8_t addr) {
// List of non-configurable devices, recognized by their fixed I2C address.
const uint8_t fixedAddr[] = { 0x29, 0x44, 0x6A, 0x6B };

for (int i = 0; i < sizeof(fixedAddr) / sizeof(fixedAddr[0]); i++) {
if (addr == fixedAddr[i]) return true;
}
return false;
}