Merge pull request #3112 from Hacuchino-hash/add-meshtracker-x1

Add support for Seeed SenseCAP MeshTracker X1 (LR2021)
This commit is contained in:
ripplebiz
2026-08-06 16:04:18 +10:00
committed by GitHub
8 changed files with 676 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v7.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"],
["0x2886", "0x0057"]
],
"usb_product": "X1-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_Mesh-Tracker-X1",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "7.3.0",
"sd_fwid": "0x0123"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52.cfg"
},
"frameworks": ["arduino"],
"name": "Seeed SenseCAP MeshTracker X1",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/SenseCAP-MeshTracker-X1-for-Meshtastic-p-6793.html",
"vendor": "Seeed Studio"
}
@@ -0,0 +1,23 @@
#include <Arduino.h>
#include <Wire.h>
#include "MeshTrackerX1Board.h"
void MeshTrackerX1Board::begin() {
NRF52BoardDCDC::begin();
btn_prev_state = LOW; // button is active HIGH
#ifdef BUTTON_PIN
pinMode(BATTERY_PIN, INPUT);
pinMode(BUTTON_PIN, INPUT_PULLDOWN);
pinMode(LED_PIN, OUTPUT);
#endif
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
Wire.begin();
delay(10); // give lr2021 some time to power up
}
@@ -0,0 +1,87 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
#include <helpers/NRF52Board.h>
class MeshTrackerX1Board : public NRF52BoardDCDC {
protected:
uint8_t btn_prev_state;
public:
MeshTrackerX1Board() : NRF52Board("X1_OTA") {}
void begin();
uint16_t getBattMilliVolts() override {
#ifdef BATTERY_PIN
#ifdef PIN_BAT_ADC_EN
digitalWrite(PIN_BAT_ADC_EN, HIGH);
#endif
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12);
delay(10);
float volts = (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096;
analogReference(AR_DEFAULT); // put back to default
analogReadResolution(10);
return volts * 1000;
#else
return 0;
#endif
}
const char* getManufacturerName() const override {
return "Seeed SenseCAP MeshTracker X1";
}
int buttonStateChanged() {
#ifdef BUTTON_PIN
uint8_t v = digitalRead(BUTTON_PIN);
if (v != btn_prev_state) {
btn_prev_state = v;
return (v == USER_BTN_PRESSED) ? 1 : -1;
}
#endif
return 0;
}
void powerOff() override {
#ifdef HAS_GPS
digitalWrite(GPS_VRTC_EN, LOW);
digitalWrite(GPS_RESET, LOW);
digitalWrite(GPS_SLEEP_INT, LOW);
digitalWrite(GPS_RTC_INT, LOW);
digitalWrite(GPS_EN, LOW);
#endif
#ifdef PIN_DRV_EN
digitalWrite(PIN_DRV_EN, LOW);
#endif
#ifdef PIN_BAT_ADC_EN
digitalWrite(PIN_BAT_ADC_EN, LOW);
#endif
#ifdef PIN_3V3_EN
digitalWrite(PIN_3V3_EN, LOW);
#endif
// set led on and wait for button release before poweroff
#ifdef LED_PIN
digitalWrite(LED_PIN, HIGH);
#endif
#ifdef BUTTON_PIN
while(digitalRead(BUTTON_PIN));
#endif
#ifdef LED_PIN
digitalWrite(LED_PIN, LOW);
#endif
#ifdef BUTTON_PIN
nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_PULLDOWN, NRF_GPIO_PIN_SENSE_HIGH);
#endif
sd_power_system_off();
}
};
+117
View File
@@ -0,0 +1,117 @@
[MeshTracker_X1]
extends = nrf52_base
board = seeed-mesh-tracker-x1
board_build.ldscript = boards/nrf52840_s140_v7.ld
build_flags = ${nrf52_base.build_flags}
-I src/helpers/nrf52
-I lib/nrf52/s140_nrf52_7.3.0_API/include
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
-I variants/meshtracker_x1
-I src/helpers/ui
-D MESH_TRACKER_X1
-D PIN_USER_BTN=6
-D USER_BTN_PRESSED=HIGH
-D PIN_STATUS_LED=24
-D RADIO_CLASS=CustomLR2021
-D USE_LR2021
-D WRAPPER_CLASS=CustomLR2021Wrapper
-D LORA_TX_POWER=22
-D P_LORA_BUSY=7 ; P0.7
-D P_LORA_SCLK=11 ; P0.11
-D P_LORA_NSS=12 ; P0.12
-D P_LORA_DIO_1=33 ; P1.1
-D P_LORA_MISO=40 ; P1.8
-D P_LORA_MOSI=41 ; P1.9
-D P_LORA_RESET=42 ; P1.10
-D LR2021_IRQ_DIO=8 ; P1.1 is connected to LR2021 DIO8
-D LR2021_TCXO_VOLTAGE=1.6f
-D ENV_INCLUDE_GPS=1
build_src_filter = ${nrf52_base.build_src_filter}
+<helpers/*.cpp>
+<../variants/meshtracker_x1>
debug_tool = jlink
upload_protocol = nrfutil
[env:MeshTracker_X1_repeater]
extends = MeshTracker_X1
build_flags = ${MeshTracker_X1.build_flags}
-I examples/companion_radio/ui-orig
-D ADVERT_NAME='"MeshTracker X1 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<../examples/simple_repeater>
lib_deps = ${MeshTracker_X1.lib_deps}
stevemarple/MicroNMEA @ ^2.0.6
[env:MeshTracker_X1_room_server]
extends = MeshTracker_X1
build_flags = ${MeshTracker_X1.build_flags}
-I examples/companion_radio/ui-orig
-D ADVERT_NAME='"MeshTracker X1 Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<../examples/simple_room_server>
lib_deps = ${MeshTracker_X1.lib_deps}
stevemarple/MicroNMEA @ ^2.0.6
[env:MeshTracker_X1_companion_radio_usb]
extends = MeshTracker_X1
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
board_upload.maximum_size = 708608
build_flags = ${MeshTracker_X1.build_flags}
-I examples/companion_radio/ui-orig
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=NullDisplayDriver
-D PIN_BUZZER=25
-D ENABLE_USB_INTERFACE
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<helpers/ui/buzzer.cpp>
+<helpers/ui/NullDisplayDriver.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-orig/*.cpp>
lib_deps = ${MeshTracker_X1.lib_deps}
densaugeo/base64 @ ~1.4.0
stevemarple/MicroNMEA @ ^2.0.6
end2endzone/NonBlockingRTTTL@^1.3.0
[env:MeshTracker_X1_companion_radio_ble]
extends = MeshTracker_X1
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
board_upload.maximum_size = 708608
build_flags = ${MeshTracker_X1.build_flags}
-I examples/companion_radio/ui-orig
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_TX_POWER=0
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=NullDisplayDriver
-D PIN_BUZZER=25
-D ADVERT_NAME='"@@MAC"'
build_src_filter = ${MeshTracker_X1.build_src_filter}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/ui/buzzer.cpp>
+<helpers/ui/NullDisplayDriver.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-orig/*.cpp>
lib_deps = ${MeshTracker_X1.lib_deps}
densaugeo/base64 @ ~1.4.0
stevemarple/MicroNMEA @ ^2.0.6
end2endzone/NonBlockingRTTTL@^1.3.0
+116
View File
@@ -0,0 +1,116 @@
#include <Arduino.h>
#include "target.h"
#include <helpers/sensors/MicroNMEALocationProvider.h>
MeshTrackerX1Board board;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
WRAPPER_CLASS radio_driver(radio, board);
VolatileRTCClock rtc_clock;
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
MeshTrackerX1SensorManager sensors = MeshTrackerX1SensorManager(nmea);
#ifdef DISPLAY_CLASS
NullDisplayDriver display;
#endif
bool radio_init() {
return radio.std_init(&SPI);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}
void MeshTrackerX1SensorManager::start_gps() {
gps_active = true;
// this init sequence comes from seeed examples and deals with all gps pins
pinMode(GPS_EN, OUTPUT);
digitalWrite(GPS_EN, HIGH);
delay(10);
pinMode(GPS_VRTC_EN, OUTPUT);
digitalWrite(GPS_VRTC_EN, HIGH);
delay(10);
pinMode(GPS_RESET, OUTPUT);
digitalWrite(GPS_RESET, HIGH);
delay(10);
digitalWrite(GPS_RESET, LOW);
pinMode(GPS_SLEEP_INT, OUTPUT);
digitalWrite(GPS_SLEEP_INT, HIGH);
pinMode(GPS_RTC_INT, OUTPUT);
digitalWrite(GPS_RTC_INT, LOW);
}
void MeshTrackerX1SensorManager::sleep_gps() {
gps_active = false;
digitalWrite(GPS_VRTC_EN, HIGH); // keep RTC alive for faster fix on wake
digitalWrite(GPS_EN, LOW);
digitalWrite(GPS_RESET, LOW);
digitalWrite(GPS_SLEEP_INT, HIGH);
digitalWrite(GPS_RTC_INT, LOW);
}
void MeshTrackerX1SensorManager::stop_gps() {
gps_active = false;
digitalWrite(GPS_VRTC_EN, LOW);
digitalWrite(GPS_EN, LOW);
digitalWrite(GPS_RESET, LOW);
digitalWrite(GPS_SLEEP_INT, HIGH);
digitalWrite(GPS_RTC_INT, LOW);
}
bool MeshTrackerX1SensorManager::begin() {
// init GPS
Serial1.begin(GPS_BAUD_RATE);
return true;
}
bool MeshTrackerX1SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission?
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude);
}
return true;
}
void MeshTrackerX1SensorManager::loop() {
static long next_gps_update = 0;
_nmea->loop();
if (millis() > next_gps_update) {
if (gps_active && _nmea->isValid()) {
node_lat = ((double)_nmea->getLatitude())/1000000.;
node_lon = ((double)_nmea->getLongitude())/1000000.;
node_altitude = ((double)_nmea->getAltitude()) / 1000.0;
}
next_gps_update = millis() + 1000;
}
}
int MeshTrackerX1SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
const char* MeshTrackerX1SensorManager::getSettingName(int i) const {
return i == 0 ? "gps" : NULL;
}
const char* MeshTrackerX1SensorManager::getSettingValue(int i) const {
if (i == 0) {
return gps_active ? "1" : "0";
}
return NULL;
}
bool MeshTrackerX1SensorManager::setSettingValue(const char* name, const char* value) {
if (strcmp(name, "gps") == 0) {
if (strcmp(value, "0") == 0) {
sleep_gps(); // sleep for faster fix !
} else {
start_gps();
}
return true;
}
return false; // not supported
}
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include "MeshTrackerX1Board.h"
#include <helpers/radiolib/CustomLR2021Wrapper.h>
#include <helpers/ArduinoHelpers.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/LocationProvider.h>
#ifdef DISPLAY_CLASS
#include "NullDisplayDriver.h"
#endif
class MeshTrackerX1SensorManager: public SensorManager {
bool gps_active = false;
LocationProvider * _nmea;
void start_gps();
void sleep_gps();
void stop_gps();
public:
MeshTrackerX1SensorManager(LocationProvider &nmea): _nmea(&nmea) { }
bool begin() override;
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
void loop() override;
int getNumSettings() const override;
const char* getSettingName(int i) const override;
const char* getSettingValue(int i) const override;
bool setSettingValue(const char* name, const char* value) override;
LocationProvider* getLocationProvider() { return _nmea; }
};
#ifdef DISPLAY_CLASS
extern NullDisplayDriver display;
#endif
extern MeshTrackerX1Board board;
extern WRAPPER_CLASS radio_driver;
extern VolatileRTCClock rtc_clock;
extern MeshTrackerX1SensorManager sensors;
bool radio_init();
mesh::LocalIdentity radio_new_identity();
+106
View File
@@ -0,0 +1,106 @@
/*
* variant.cpp - Seeed SenseCAP MeshTracker X1 (nRF52840 + Semtech LR2021)
*/
#include "variant.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] =
{
0, // P0.00
1, // P0.01
2, // P0.02, AIN0 BATTERY_PIN
3, // P0.03, LED_RED
4, // P0.04, AIN2 VCC_ADC
5, // P0.05, AIN3 EXT_PWR_DETECT
6, // P0.06, PIN_BUTTON1
7, // P0.07, LORA_BUSY
8, // P0.08, GPS_RESET
9, // P0.09, PIN_RTC_EN
10, // P0.10
11, // P0.11, PIN_SPI_SCK
12, // P0.12, PIN_SPI_NSS
13, // P0.13, PIN_SERIAL1_TX
14, // P0.14, PIN_SERIAL1_RX
15, // P0.15
16, // P0.16, PIN_SERIAL2_TX
17, // P0.17, PIN_SERIAL2_RX
18, // P0.18
19, // P0.19
20, // P0.20
21, // P0.21
22, // P0.22
23, // P0.23
24, // P0.24, LED_GREEN
25, // P0.25, BUZZER_PIN
26, // P0.26
27, // P0.27
28, // P0.28, LED_BLUE
29, // P0.29, GPS_RTC_INT
30, // P0.30, GPS_SLEEP_INT
31, // P0.31
32, // P1.00
33, // P1.01, LORA_DIO_1 (LR2021 DIO8 IRQ)
34, // P1.02
35, // P1.03, EXT_CHRG_DETECT
36, // P1.04, CHARGE_DONE
37, // P1.05, PIN_DRV_EN
38, // P1.06, PIN_BAT_ADC_EN
39, // P1.07, PIN_3V3_EN
40, // P1.08, PIN_SPI_MISO
41, // P1.09, PIN_SPI_MOSI
42, // P1.10, LORA_RESET
43, // P1.11, GPS_EN
44, // P1.12
45, // P1.13, GPS_VRTC_EN
46, // P1.14, PIN_WIRE_SCL
47, // P1.15, PIN_WIRE_SDA
255, // NRFX_SPIM_PIN_NOT_USED
};
void initVariant()
{
pinMode(BATTERY_PIN, INPUT);
pinMode(EXT_CHRG_DETECT, INPUT);
pinMode(EXT_PWR_DETECT, INPUT);
pinMode(PIN_BUTTON1, INPUT_PULLDOWN);
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
pinMode(PIN_BAT_ADC_EN, OUTPUT);
digitalWrite(PIN_BAT_ADC_EN, HIGH);
pinMode(PIN_RTC_EN, OUTPUT);
digitalWrite(PIN_RTC_EN, LOW);
pinMode(PIN_DRV_EN, OUTPUT);
digitalWrite(PIN_DRV_EN, LOW);
pinMode(LED_RED, OUTPUT);
digitalWrite(LED_RED, LOW);
pinMode(LED_GREEN, OUTPUT);
digitalWrite(LED_GREEN, LOW);
pinMode(LED_BLUE, OUTPUT);
digitalWrite(LED_BLUE, LOW);
pinMode(GPS_EN, OUTPUT);
digitalWrite(GPS_EN, LOW);
pinMode(GPS_VRTC_EN, OUTPUT);
digitalWrite(GPS_VRTC_EN, HIGH);
pinMode(GPS_RESET, OUTPUT);
digitalWrite(GPS_RESET, LOW);
pinMode(GPS_SLEEP_INT, OUTPUT);
digitalWrite(GPS_SLEEP_INT, HIGH);
pinMode(GPS_RTC_INT, OUTPUT);
digitalWrite(GPS_RTC_INT, LOW);
pinMode(BUZZER_PIN, INPUT_PULLDOWN);
}
+123
View File
@@ -0,0 +1,123 @@
/*
* variant.h - Seeed SenseCAP MeshTracker X1 (nRF52840 + Semtech LR2021)
*/
#pragma once
#include "WVariant.h"
////////////////////////////////////////////////////////////////////////////////
// Low frequency clock source
#define USE_LFXO // 32.768 kHz crystal oscillator
#define VARIANT_MCK (64000000ul)
////////////////////////////////////////////////////////////////////////////////
// Power
#define NRF_APM // detect usb power
#define PIN_3V3_EN (39) // P1.7 Power to sensors
#define PIN_BAT_ADC_EN (38) // P1.6 Power to battery ADC divider
#define PIN_RTC_EN (9) // P0.9 Power to RTC
#define BATTERY_PIN (2) // P0.2/AIN0
#define BATTERY_IMMUTABLE
#define ADC_MULTIPLIER (2.0F)
#define EXT_CHRG_DETECT (35) // P1.3, LOW while charging
#define EXT_PWR_DETECT (5) // P0.5
#define ADC_RESOLUTION (14)
#define BATTERY_SENSE_RES (12)
#define AREF_VOLTAGE (3.0)
////////////////////////////////////////////////////////////////////////////////
// Number of pins
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (6)
#define NUM_ANALOG_OUTPUTS (0)
////////////////////////////////////////////////////////////////////////////////
// UART pin definition
#define PIN_SERIAL1_RX (14) // P0.14 (GPS)
#define PIN_SERIAL1_TX (13) // P0.13 (GPS)
#define PIN_SERIAL2_RX (17) // P0.17
#define PIN_SERIAL2_TX (16) // P0.16
////////////////////////////////////////////////////////////////////////////////
// I2C pin definition
#define HAS_WIRE (1)
#define WIRE_INTERFACES_COUNT (1)
#define PIN_WIRE_SDA (47) // P1.15
#define PIN_WIRE_SCL (46) // P1.14
#define I2C_NO_RESCAN
////////////////////////////////////////////////////////////////////////////////
// SPI pin definition
#define SPI_INTERFACES_COUNT (1)
#define PIN_SPI_MISO (40) // P1.8
#define PIN_SPI_MOSI (41) // P1.9
#define PIN_SPI_SCK (11) // P0.11
#define PIN_SPI_NSS (12) // P0.12
////////////////////////////////////////////////////////////////////////////////
// Builtin LEDs (RGB)
#define LED_BUILTIN (-1)
#define LED_RED (3) // P0.3
#define LED_GREEN (24) // P0.24
#define LED_BLUE (28) // P0.28
#define LED_PIN LED_GREEN
#define LED_STATE_ON HIGH
////////////////////////////////////////////////////////////////////////////////
// Builtin buttons
#define PIN_BUTTON1 (6) // P0.6, active HIGH (internal pull-down)
#define BUTTON_PIN PIN_BUTTON1
////////////////////////////////////////////////////////////////////////////////
// LR2021
#define LORA_DIO_1 (33) // P1.1, IRQ line (LR2021 DIO8)
#define LORA_NSS (PIN_SPI_NSS) // P0.12
#define LORA_RESET (42) // P1.10
#define LORA_BUSY (7) // P0.7
#define LORA_SCLK (PIN_SPI_SCK) // P0.11
#define LORA_MISO (PIN_SPI_MISO) // P1.8
#define LORA_MOSI (PIN_SPI_MOSI) // P1.9
////////////////////////////////////////////////////////////////////////////////
// GPS (Airoha, dual-band L1+L5)
#define HAS_GPS 1
#define GPS_RX_PIN PIN_SERIAL1_RX
#define GPS_TX_PIN PIN_SERIAL1_TX
#define GPS_BAUD_RATE (115200)
#define GPS_EN (43) // P1.11, active HIGH
#define GPS_RESET (8) // P0.8, active HIGH
#define GPS_VRTC_EN (45) // P1.13
#define GPS_SLEEP_INT (30) // P0.30
#define GPS_RTC_INT (29) // P0.29
////////////////////////////////////////////////////////////////////////////////
// Haptic driver (DRV2605 on I2C)
#define PIN_DRV_EN (37) // P1.5, power to haptic driver
////////////////////////////////////////////////////////////////////////////////
// Buzzer
#define BUZZER_PIN (25) // P0.25, pwm output