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
This commit is contained in:
martin 2025-08-19 18:50:38 +02:00
commit a89215b7ff
10 changed files with 806 additions and 0 deletions

30
.gitignore vendored Normal file
View file

@ -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

Binary file not shown.

40
README.md Normal file
View file

@ -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

198
lib/UWBHelper/UWBHelper.cpp Normal file
View file

@ -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;
}

60
lib/UWBHelper/UWBHelper.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef UWBHELPER_H
#define UWBHELPER_H
#include <Arduino.h>
#include <HardwareSerial.h>
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

6
node_modules/.package-lock.json generated vendored Normal file
View file

@ -0,0 +1,6 @@
{
"name": "platformiotest",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

65
platformio.ini Normal file
View file

@ -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 = +<*> -<main_tag.cpp>
build_flags =
${env.build_flags}
-DDEVICE_TYPE_ANCHOR=1
-DUWB_INDEX=0
-DNETWORK_ID=1234
; Tag device configuration
[env:tag]
build_src_filter = +<*> -<main_anchor.cpp>
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 = +<*> -<main_anchor.cpp>
build_flags =
${env.build_flags}
-DDEVICE_TYPE_TAG=1
-DUWB_INDEX=2
-DNETWORK_ID=1234
; Development environment with debugging
[env:debug]
build_src_filter = +<*> -<main_tag.cpp>
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

56
src/config.h Normal file
View file

@ -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

183
src/main_anchor.cpp Normal file
View file

@ -0,0 +1,183 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#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);
}

168
src/main_tag.cpp Normal file
View file

@ -0,0 +1,168 @@
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#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);
}