commit a89215b7ff006a5257b1f173851bb7bcb166ee42 Author: martin Date: Tue Aug 19 18:50:38 2025 +0200 Initial commit: ESP32-S3 UWB positioning system - Added anchor and tag implementations for MaUWB modules - Configured for 6.8Mbps communication with range filtering - Support for multiple tags (tag/tag2 environments) - OLED display integration for real-time measurements - Simplified code without sleep mode and OTA functionality - Complete UWBHelper library for AT command interface diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3fc5b22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# PlatformIO +.pio/ +.vscode/ +.pioenvs/ +.piolibdeps/ +.clang_complete +.gcc-flags.json + +# Build artifacts +*.bin +*.elf +*.map + +# IDE files +.vscode/ +*.code-workspace + +# OS files +.DS_Store +Thumbs.db + +# Logs +*.log + +# Temporary files +*.tmp +*.temp + +#Claude code +.Claude diff --git a/Makerfabs UWB AT Module AT Command Manual(v1.1.1).pdf b/Makerfabs UWB AT Module AT Command Manual(v1.1.1).pdf new file mode 100644 index 0000000..6141c01 Binary files /dev/null and b/Makerfabs UWB AT Module AT Command Manual(v1.1.1).pdf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f23456 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# MaUWB ESP32-S3 Positioning System + +Ultra-wideband (UWB) positioning system using ESP32-S3 and Makerfabs UWB modules. + +## Features +- ESP32-S3 based anchor and tag devices +- Real-time distance measurement with <10cm accuracy +- OLED display for status and measurements +- Multiple tag support (up to 64 tags) +- 6.8Mbps communication rate +- Serial debugging output + +## Hardware +- ESP32-S3 DevKit +- Makerfabs UWB AT Module +- SSD1306 OLED Display (128x64) + +## Environments +- `anchor`: Base station for positioning +- `tag`: Mobile device for tracking (ID 1) +- `tag2`: Mobile device for tracking (ID 2) + +## Build & Upload +```bash +# Build specific environment +pio run -e anchor +pio run -e tag + +# Upload to device +pio run -e tag -t upload + +# Monitor serial output +pio device monitor +``` + +## Configuration +- Network ID: 1234 +- Baud Rate: 115200 +- Communication: 6.8Mbps +- Range filtering: Enabled diff --git a/lib/UWBHelper/UWBHelper.cpp b/lib/UWBHelper/UWBHelper.cpp new file mode 100644 index 0000000..4b2d23e --- /dev/null +++ b/lib/UWBHelper/UWBHelper.cpp @@ -0,0 +1,198 @@ +#include "UWBHelper.h" + +UWBHelper::UWBHelper(HardwareSerial* serial, int reset) { + uwbSerial = serial; + resetPin = reset; +} + +bool UWBHelper::begin() { + pinMode(resetPin, OUTPUT); + digitalWrite(resetPin, HIGH); + + uwbSerial->begin(115200); + delay(1000); + + // Test communication + String response = sendCommand("AT?", 2000); + return isResponseOK(response); +} + +void UWBHelper::reset() { + digitalWrite(resetPin, LOW); + delay(100); + digitalWrite(resetPin, HIGH); + delay(1000); +} + +bool UWBHelper::configureDevice(int deviceId, bool isAnchor, int dataRate, int rangeFilter) { + String cmd = "AT+SETCFG=" + String(deviceId) + "," + + String(isAnchor ? 1 : 0) + "," + + String(dataRate) + "," + + String(rangeFilter); + + String response = sendCommand(cmd, 2000); + return isResponseOK(response); +} + +bool UWBHelper::setCapacity(int tagCount, int timeSlot, int extMode) { + String cmd = "AT+SETCAP=" + String(tagCount) + "," + + String(timeSlot) + "," + + String(extMode); + + String response = sendCommand(cmd, 2000); + return isResponseOK(response); +} + +bool UWBHelper::setNetwork(int networkId) { + String cmd = "AT+SETPAN=" + String(networkId); + String response = sendCommand(cmd, 2000); + return isResponseOK(response); +} + +bool UWBHelper::enableReporting(bool enable) { + String cmd = "AT+SETRPT=" + String(enable ? 1 : 0); + String response = sendCommand(cmd, 2000); + return isResponseOK(response); +} + +bool UWBHelper::saveConfiguration() { + String response = sendCommand("AT+SAVE", 2000); + return isResponseOK(response); +} + +bool UWBHelper::restartDevice() { + String response = sendCommand("AT+RESTART", 2000); + return isResponseOK(response); +} + +String UWBHelper::sendCommand(String command, int timeout) { + String response = ""; + + Serial.println("Sending: " + command); + uwbSerial->println(command); + + unsigned long startTime = millis(); + while ((millis() - startTime) < timeout) { + while (uwbSerial->available()) { + char c = uwbSerial->read(); + response += c; + } + } + + if (response.length() > 0) { + Serial.println("Response: " + response); + } + return response; +} + +bool UWBHelper::parseRangeData(String data, DeviceData devices[], int maxDevices) { + if (!data.startsWith("AT+RANGE=")) { + return false; + } + + // Parse tid (Tag ID) + int tidIndex = data.indexOf("tid:"); + if (tidIndex == -1) return false; + + int commaPos = data.indexOf(',', tidIndex); + if (commaPos == -1) return false; + + int deviceId = data.substring(tidIndex + 4, commaPos).toInt(); + + // Parse range data + int rangeIndex = data.indexOf("range:("); + if (rangeIndex == -1) return false; + + int rangeStart = rangeIndex + 7; + int rangeEnd = data.indexOf(')', rangeStart); + if (rangeEnd == -1) return false; + + String rangeData = data.substring(rangeStart, rangeEnd); + float distance = rangeData.toFloat() / 100.0; // Convert cm to meters + + // Parse RSSI data + int rssiIndex = data.indexOf("rssi:("); + float rssi = 0.0; + if (rssiIndex != -1) { + int rssiStart = rssiIndex + 6; + int rssiComma = data.indexOf(',', rssiStart); + if (rssiComma == -1) rssiComma = data.indexOf(')', rssiStart); + if (rssiComma != -1) { + rssi = data.substring(rssiStart, rssiComma).toFloat(); + } + } + + // Update device data + for (int i = 0; i < maxDevices; i++) { + if (devices[i].deviceId == deviceId || !devices[i].active) { + devices[i].deviceId = deviceId; + devices[i].distance = distance; + devices[i].rssi = rssi; + devices[i].lastUpdate = millis(); + devices[i].active = true; + return true; + } + } + + return false; +} + +String UWBHelper::getVersion() { + return sendCommand("AT+GETVER?", 2000); +} + +bool UWBHelper::isResponseOK(String response) { + return response.indexOf("OK") >= 0; +} + +void UWBHelper::printDiagnostics() { + Serial.println("=== UWB Diagnostics ==="); + Serial.println("Version: " + getVersion()); + Serial.println("Config: " + sendCommand("AT+GETCFG?", 2000)); + Serial.println("Capacity: " + sendCommand("AT+GETCAP?", 2000)); + Serial.println("Power: " + sendCommand("AT+GETPOW?", 2000)); +} + +// DistanceFilter Implementation +DistanceFilter::DistanceFilter() : index(0), filled(false) { + for (int i = 0; i < FILTER_SIZE; i++) readings[i] = 0; +} + +float DistanceFilter::addReading(float distance) { + readings[index] = distance; + index = (index + 1) % FILTER_SIZE; + if (index == 0) filled = true; + + return getFilteredValue(); +} + +float DistanceFilter::getFilteredValue() { + if (!filled && index == 0) return readings[0]; + + // Median filter + float sorted[FILTER_SIZE]; + int count = filled ? FILTER_SIZE : index; + + for (int i = 0; i < count; i++) { + sorted[i] = readings[i]; + } + + // Simple bubble sort + for (int i = 0; i < count - 1; i++) { + for (int j = 0; j < count - i - 1; j++) { + if (sorted[j] > sorted[j + 1]) { + float temp = sorted[j]; + sorted[j] = sorted[j + 1]; + sorted[j + 1] = temp; + } + } + } + + return sorted[count / 2]; // Return median +} + +void DistanceFilter::reset() { + index = 0; + filled = false; + for (int i = 0; i < FILTER_SIZE; i++) readings[i] = 0; +} \ No newline at end of file diff --git a/lib/UWBHelper/UWBHelper.h b/lib/UWBHelper/UWBHelper.h new file mode 100644 index 0000000..f02fabd --- /dev/null +++ b/lib/UWBHelper/UWBHelper.h @@ -0,0 +1,60 @@ +#ifndef UWBHELPER_H +#define UWBHELPER_H + +#include +#include + +struct DeviceData { + int deviceId; + float distance; + float rssi; + unsigned long lastUpdate; + bool active; +}; + +class UWBHelper { +private: + HardwareSerial* uwbSerial; + int resetPin; + +public: + UWBHelper(HardwareSerial* serial, int reset); + + // Initialization + bool begin(); + void reset(); + + // Configuration + bool configureDevice(int deviceId, bool isAnchor, int dataRate = 1, int rangeFilter = 1); + bool setCapacity(int tagCount, int timeSlot = 10, int extMode = 1); + bool setNetwork(int networkId); + bool enableReporting(bool enable); + bool saveConfiguration(); + bool restartDevice(); + + // Communication + String sendCommand(String command, int timeout = 2000); + bool parseRangeData(String data, DeviceData devices[], int maxDevices); + + // Utility + String getVersion(); + bool isResponseOK(String response); + void printDiagnostics(); +}; + +// Data filtering class +class DistanceFilter { +private: + static const int FILTER_SIZE = 5; + float readings[FILTER_SIZE]; + int index; + bool filled; + +public: + DistanceFilter(); + float addReading(float distance); + float getFilteredValue(); + void reset(); +}; + +#endif // UWBHELPER_H \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..ad313f7 --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "platformiotest", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..f2015c0 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,65 @@ +; PlatformIO Project Configuration File +; Build options: build flags, source filter +; Upload options: custom upload port, speed, etc. +; Library options: dependencies, extra library storages +; Advanced options: extra scripting + +[platformio] +default_envs = anchor + +; Common configuration for both anchor and tag +[env] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder +build_flags = + -DCORE_DEBUG_LEVEL=3 + -DARDUINO_USB_CDC_ON_BOOT=1 + +; Library dependencies +lib_deps = + adafruit/Adafruit GFX Library@^1.11.7 + adafruit/Adafruit SSD1306@^2.5.7 + adafruit/Adafruit BusIO@^1.14.4 + bblanchon/ArduinoJson@^6.21.3 + +; Anchor device configuration +[env:anchor] +build_src_filter = +<*> - +build_flags = + ${env.build_flags} + -DDEVICE_TYPE_ANCHOR=1 + -DUWB_INDEX=0 + -DNETWORK_ID=1234 + +; Tag device configuration +[env:tag] +build_src_filter = +<*> - +build_flags = + ${env.build_flags} + -DDEVICE_TYPE_TAG=1 + -DUWB_INDEX=1 + -DNETWORK_ID=1234 + +; Tag device 2 (copy and modify UWB_INDEX for multiple tags) +[env:tag2] +build_src_filter = +<*> - +build_flags = + ${env.build_flags} + -DDEVICE_TYPE_TAG=1 + -DUWB_INDEX=2 + -DNETWORK_ID=1234 + +; Development environment with debugging +[env:debug] +build_src_filter = +<*> - +build_flags = + ${env.build_flags} + -DDEVICE_TYPE_ANCHOR=1 + -DUWB_INDEX=0 + -DNETWORK_ID=1234 + -DDEBUG_ENABLED=1 +debug_tool = esp-prog +debug_init_break = tbreak setup diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..657e396 --- /dev/null +++ b/src/config.h @@ -0,0 +1,56 @@ +#ifndef CONFIG_H +#define CONFIG_H + +// Hardware Pin Definitions for ESP32-S3 +#define RESET_PIN 16 +#define IO_RXD2 18 +#define IO_TXD2 17 +#define I2C_SDA 39 +#define I2C_SCL 38 + +// UWB Configuration +#ifndef UWB_INDEX +#define UWB_INDEX 0 +#endif + +#ifndef NETWORK_ID +#define NETWORK_ID 1234 +#endif + +// System Configuration +#define MAX_DEVICES 10 +#define MAX_ANCHORS 8 +#define UWB_TAG_COUNT 64 + +// Timing Configuration +#define DISPLAY_UPDATE_INTERVAL 500 +#define DEVICE_TIMEOUT 5000 +#define SLEEP_DURATION 5000 + +// Display Configuration +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 64 +#define OLED_RESET -1 +#define SCREEN_ADDRESS 0x3C + +// Serial Configuration +#define SERIAL_BAUD 115200 + +// Debug Configuration +#ifdef DEBUG_ENABLED +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#define DEBUG_PRINTF(x, ...) Serial.printf(x, __VA_ARGS__) +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#define DEBUG_PRINTF(x, ...) +#endif + +// WiFi Configuration (optional) +#ifdef OTA_ENABLED +#define WIFI_SSID "YOUR_WIFI_SSID" +#define WIFI_PASSWORD "YOUR_WIFI_PASSWORD" +#endif + +#endif // CONFIG_H \ No newline at end of file diff --git a/src/main_anchor.cpp b/src/main_anchor.cpp new file mode 100644 index 0000000..4e79178 --- /dev/null +++ b/src/main_anchor.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include "config.h" +#include "UWBHelper.h" + +// Function declarations +void showStartupScreen(); +void processUWBData(); +void updateDisplay(); +void checkTimeouts(); + +// Hardware setup +HardwareSerial uwbSerial(2); +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +UWBHelper uwb(&uwbSerial, RESET_PIN); + +// Data storage +DeviceData tags[UWB_TAG_COUNT]; +DistanceFilter tagFilters[UWB_TAG_COUNT]; +String response = ""; +unsigned long lastDisplayUpdate = 0; + +void setup() { + Serial.begin(SERIAL_BAUD); + Serial.println("Starting UWB Anchor..."); + + // Initialize I2C and display + Wire.begin(I2C_SDA, I2C_SCL); + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + Serial.println(F("SSD1306 allocation failed")); + for (;;); + } + + showStartupScreen(); + + // Initialize UWB + uwbSerial.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); + delay(1000); + + if (!uwb.begin()) { + Serial.println("UWB initialization failed!"); + return; + } + + // Configure as anchor + Serial.println("Configuring as Anchor..."); + uwb.configureDevice(UWB_INDEX, true); // true = anchor + uwb.setCapacity(UWB_TAG_COUNT, 10, 1); + uwb.setNetwork(NETWORK_ID); + uwb.enableReporting(true); + uwb.saveConfiguration(); + delay(1000); + uwb.restartDevice(); + delay(2000); + + // Initialize tag data + for (int i = 0; i < UWB_TAG_COUNT; i++) { + tags[i].deviceId = -1; + tags[i].active = false; + tags[i].distance = 0.0; + tags[i].rssi = 0.0; + tags[i].lastUpdate = 0; + } + + Serial.println("Anchor ready! Waiting for tags..."); +} + +void loop() { + processUWBData(); + updateDisplay(); + checkTimeouts(); + + // Handle serial passthrough for debugging + while (Serial.available()) { + uwbSerial.write(Serial.read()); + } + + delay(10); +} + +void processUWBData() { + while (uwbSerial.available()) { + char c = uwbSerial.read(); + if (c == '\r') continue; + if (c == '\n') { + if (response.length() > 0) { + Serial.println("Received: " + response); + if (uwb.parseRangeData(response, tags, UWB_TAG_COUNT)) { + // Successfully parsed data + for (int i = 0; i < UWB_TAG_COUNT; i++) { + if (tags[i].active && (millis() - tags[i].lastUpdate < 1000)) { + Serial.printf("Tag %d: %.2fm, RSSI: %.1fdBm\n", + tags[i].deviceId, tags[i].distance, tags[i].rssi); + } + } + } + response = ""; + } + } else { + response += c; + } + } +} + +void updateDisplay() { + if (millis() - lastDisplayUpdate < DISPLAY_UPDATE_INTERVAL) return; + + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + // Header + display.setCursor(0, 0); + display.printf("Anchor %d (Net:%d)", UWB_INDEX, NETWORK_ID); + + display.setCursor(0, 10); + display.println("Active Tags:"); + + // Show active tags + int activeCount = 0; + int yPos = 20; + + for (int i = 0; i < UWB_TAG_COUNT && yPos < 55; i++) { + if (tags[i].active) { + display.setCursor(0, yPos); + display.printf("T%d: %.2fm", tags[i].deviceId, tags[i].distance); + display.setCursor(70, yPos); + display.printf("%.0fdBm", tags[i].rssi); + yPos += 8; + activeCount++; + } + } + + if (activeCount == 0) { + display.setCursor(0, 30); + display.println("No active tags"); + display.setCursor(0, 40); + display.println("Waiting..."); + } + + // Status line + display.setCursor(0, 56); + display.printf("Active: %d/%d", activeCount, UWB_TAG_COUNT); + + display.display(); + lastDisplayUpdate = millis(); +} + +void checkTimeouts() { + unsigned long currentTime = millis(); + for (int i = 0; i < UWB_TAG_COUNT; i++) { + if (tags[i].active && (currentTime - tags[i].lastUpdate > DEVICE_TIMEOUT)) { + tags[i].active = false; + Serial.printf("Tag %d timeout\n", tags[i].deviceId); + } + } +} + +void showStartupScreen() { + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 0); + display.println("MaUWB Anchor"); + + display.setCursor(0, 15); + display.printf("ID: %d", UWB_INDEX); + + display.setCursor(0, 25); + display.printf("Network: %d", NETWORK_ID); + + display.setCursor(0, 35); + display.println("6.8Mbps Mode"); + + display.setCursor(0, 45); + display.println("Initializing..."); + + display.display(); + delay(2000); +} + diff --git a/src/main_tag.cpp b/src/main_tag.cpp new file mode 100644 index 0000000..8fe45b0 --- /dev/null +++ b/src/main_tag.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include "config.h" +#include "UWBHelper.h" + +// Function declarations +void showStartupScreen(); +void processUWBData(); +void updateDisplay(); +void checkTimeouts(); + +// Hardware setup +HardwareSerial uwbSerial(2); +Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); +UWBHelper uwb(&uwbSerial, RESET_PIN); + +// Data storage +DeviceData anchors[MAX_ANCHORS]; +String response = ""; +unsigned long lastDisplayUpdate = 0; + +void setup() { + Serial.begin(SERIAL_BAUD); + + // Initialize display + Wire.begin(I2C_SDA, I2C_SCL); + if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { + Serial.println(F("SSD1306 allocation failed")); + for (;;); + } + + showStartupScreen(); + + // Initialize UWB + uwbSerial.begin(115200, SERIAL_8N1, IO_RXD2, IO_TXD2); + + if (!uwb.begin()) { + Serial.println("UWB initialization failed!"); + return; + } + + // Configure as tag + Serial.println("Configuring as Tag..."); + uwb.configureDevice(UWB_INDEX, false); // false = tag + uwb.setCapacity(10, 10, 1); + uwb.setNetwork(NETWORK_ID); + uwb.enableReporting(true); + uwb.saveConfiguration(); + delay(1000); + uwb.restartDevice(); + delay(2000); + + // Initialize anchor data + for (int i = 0; i < MAX_ANCHORS; i++) { + anchors[i].deviceId = -1; + anchors[i].active = false; + } + + Serial.println("Tag ready!"); +} + +void loop() { + processUWBData(); + updateDisplay(); + checkTimeouts(); + + // Handle serial passthrough for debugging + while (Serial.available()) { + uwbSerial.write(Serial.read()); + } + + delay(10); +} + +void processUWBData() { + while (uwbSerial.available()) { + char c = uwbSerial.read(); + if (c == '\r') continue; + if (c == '\n') { + if (response.length() > 0) { + Serial.println("Received: " + response); + if (uwb.parseRangeData(response, anchors, MAX_ANCHORS)) { + // Successfully parsed data + for (int i = 0; i < MAX_ANCHORS; i++) { + if (anchors[i].active && (millis() - anchors[i].lastUpdate < 1000)) { + Serial.printf("Anchor %d: %.2fm, RSSI: %.1fdBm\n", + anchors[i].deviceId, anchors[i].distance, anchors[i].rssi); + } + } + } + response = ""; + } + } else { + response += c; + } + } +} + +void updateDisplay() { + if (millis() - lastDisplayUpdate < DISPLAY_UPDATE_INTERVAL) return; + + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + // Header + display.setCursor(0, 0); + display.printf("Tag %d (Net:%d)", UWB_INDEX, NETWORK_ID); + + display.setCursor(0, 10); + display.println("Anchors:"); + + int activeCount = 0; + int yPos = 20; + + for (int i = 0; i < MAX_ANCHORS && yPos < 55; i++) { + if (anchors[i].active) { + display.setCursor(0, yPos); + display.printf("A%d: %.2fm", anchors[i].deviceId, anchors[i].distance); + display.setCursor(70, yPos); + display.printf("%.0fdBm", anchors[i].rssi); + yPos += 8; + activeCount++; + } + } + + if (activeCount == 0) { + display.setCursor(0, 30); + display.println("Searching..."); + display.setCursor(0, 40); + display.println("for anchors..."); + } + + // Status line + display.setCursor(0, 56); + display.printf("Found: %d/%d", activeCount, MAX_ANCHORS); + + display.display(); + lastDisplayUpdate = millis(); +} + +void checkTimeouts() { + unsigned long currentTime = millis(); + for (int i = 0; i < MAX_ANCHORS; i++) { + if (anchors[i].active && (currentTime - anchors[i].lastUpdate > DEVICE_TIMEOUT)) { + anchors[i].active = false; + } + } +} + + +void showStartupScreen() { + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 0); + display.println("MaUWB Tag"); + display.setCursor(0, 15); + display.printf("ID: %d", UWB_INDEX); + display.setCursor(0, 25); + display.printf("Network: %d", NETWORK_ID); + display.setCursor(0, 35); + display.println("Initializing..."); + display.display(); + delay(2000); +} \ No newline at end of file