Compare commits
5 commits
8cf97e0e5a
...
432f17b2be
| Author | SHA1 | Date | |
|---|---|---|---|
| 432f17b2be | |||
| 1be5b59c7a | |||
| d0e5fc9ab7 | |||
| 544d459c9b | |||
| a83684d0cb |
23 changed files with 1758 additions and 6 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -14,3 +14,6 @@
|
||||||
|
|
||||||
# Emacs dir locals (optional)
|
# Emacs dir locals (optional)
|
||||||
.dir-locals.el
|
.dir-locals.el
|
||||||
|
/hold/
|
||||||
|
.platformio_local/
|
||||||
|
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -10,3 +10,6 @@
|
||||||
[submodule "external/ArxContainer"]
|
[submodule "external/ArxContainer"]
|
||||||
path = external/ArxContainer
|
path = external/ArxContainer
|
||||||
url = https://github.com/hideakitai/ArxContainer.git
|
url = https://github.com/hideakitai/ArxContainer.git
|
||||||
|
[submodule "external/microReticulum_Firmware"]
|
||||||
|
path = external/microReticulum_Firmware
|
||||||
|
url = https://github.com/attermann/microReticulum_Firmware
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -2,3 +2,14 @@
|
||||||
microReticulum For Field Testing With LilyGo T-Beam SUPREMES
|
microReticulum For Field Testing With LilyGo T-Beam SUPREMES
|
||||||
|
|
||||||
Field Testing Only. Used to specially program a group of T-Beams, each having the others' contact information and keys, which are then deployed in the field with people moving about to capture what was successfully sent and received and at what coordinates. Data is stored on SD cards and then retrieved at the end of the test an dumped into a PostgreSQL databse for analysis.
|
Field Testing Only. Used to specially program a group of T-Beams, each having the others' contact information and keys, which are then deployed in the field with people moving about to capture what was successfully sent and received and at what coordinates. Data is stored on SD cards and then retrieved at the end of the test an dumped into a PostgreSQL databse for analysis.
|
||||||
|
|
||||||
|
## Dependency Direction
|
||||||
|
This repo is migrating from `external/microReticulum` to `external/microReticulum_Firmware`.
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
- Reuse upstream T-Beam SUPREME integration work in `microReticulum_Firmware`.
|
||||||
|
- Avoid reimplementing already-solved board integration (PMU, SD, RTC, GPS, LoRa setup).
|
||||||
|
|
||||||
|
Status:
|
||||||
|
- Migration plan is tracked in `docs/microreticulum_firmware_migration.md`.
|
||||||
|
- Existing exercises remain functional during migration.
|
||||||
|
|
|
||||||
45
docs/microreticulum_firmware_migration.md
Normal file
45
docs/microreticulum_firmware_migration.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# microReticulum Firmware Migration Plan
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Migrate this project from the current submodule:
|
||||||
|
- `external/microReticulum`
|
||||||
|
|
||||||
|
to:
|
||||||
|
- `external/microReticulum_Firmware`
|
||||||
|
|
||||||
|
so this repo consumes existing T-Beam SUPREME integration instead of duplicating it.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
- `.gitmodules` currently declares `external/microReticulum`.
|
||||||
|
- `.gitmodules` now also declares `external/microReticulum_Firmware`:
|
||||||
|
- URL: `https://github.com/attermann/microReticulum_Firmware`
|
||||||
|
- Current commit: `5dc607fc7227c46ccb19244e456782fbb7775eae`
|
||||||
|
- Exercises under `exercises/` are self-contained and currently compile independently.
|
||||||
|
|
||||||
|
## Planned Migration Steps
|
||||||
|
1. Add the new submodule at `external/microReticulum_Firmware`.
|
||||||
|
2. Keep `external/microReticulum` temporarily for side-by-side validation.
|
||||||
|
3. Inventory reusable components from `microReticulum_Firmware`:
|
||||||
|
- board init / PMU power sequencing
|
||||||
|
- LoRa interface setup
|
||||||
|
- SD/RTC/GPS integration glue
|
||||||
|
4. Refactor local firmware entry points to call upstream components where possible.
|
||||||
|
5. Update exercise docs to distinguish:
|
||||||
|
- hardware smoke tests (local exercises)
|
||||||
|
- integration paths (from `microReticulum_Firmware`)
|
||||||
|
6. After parity validation, remove or archive `external/microReticulum`.
|
||||||
|
|
||||||
|
Step-2 inventory output:
|
||||||
|
- `docs/microreticulum_firmware_step2_adoption_matrix.md`
|
||||||
|
|
||||||
|
## Validation Checklist
|
||||||
|
- Build passes for all key exercises.
|
||||||
|
- SD/RTC/GPS startup behavior remains stable.
|
||||||
|
- LoRa send/receive smoke tests still pass.
|
||||||
|
- Fieldtest beacon path compiles and boots.
|
||||||
|
|
||||||
|
## Submodule Commands Used
|
||||||
|
```bash
|
||||||
|
git submodule add https://github.com/attermann/microReticulum_Firmware external/microReticulum_Firmware
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
62
docs/microreticulum_firmware_step2_adoption_matrix.md
Normal file
62
docs/microreticulum_firmware_step2_adoption_matrix.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Step 2 Adoption Matrix
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
Repository: `microReticulumTbeam`
|
||||||
|
Submodule source: `external/microReticulum_Firmware`
|
||||||
|
|
||||||
|
## What Step 2 Means
|
||||||
|
Identify and wire the first low-risk points where this repo should consume existing board-integration logic from `microReticulum_Firmware`, instead of maintaining duplicate local assumptions.
|
||||||
|
|
||||||
|
## High-Value Upstream Sources
|
||||||
|
- `external/microReticulum_Firmware/Boards.h`
|
||||||
|
- T-Beam Supreme pin map and feature flags.
|
||||||
|
- Includes SD pins (`SD_CS=47`, `SD_CLK=36`, `SD_MISO=37`, `SD_MOSI=35`), PMU I2C pins (`I2C_SDA=42`, `I2C_SCL=41`), LoRa pins.
|
||||||
|
- `external/microReticulum_Firmware/Power.h`
|
||||||
|
- AXP2101 setup sequence for T-Beam Supreme (`BOARD_TBEAM_S_V1`), including SD rail (BLDO1), ALDO rails, charging config.
|
||||||
|
- `external/microReticulum_Firmware/platformio.ini`
|
||||||
|
- `env:ttgo-t-beam-supreme` build model and dependency pattern.
|
||||||
|
|
||||||
|
## Local Targets And First Consumers
|
||||||
|
1. `firmware/fieldtest_beacon/src/main.cpp`
|
||||||
|
- Why first: this is the integration entry point, not just a smoke test.
|
||||||
|
- Step-2 change made: SD CS now comes from `Boards.h` instead of hardcoded `10`.
|
||||||
|
|
||||||
|
2. `exercises/04_SD_card/src/main.cpp`
|
||||||
|
- Why second: duplicates PMU + SD pin assumptions already present upstream.
|
||||||
|
- Planned consume-first item: PMU rail setup pattern from `Power.h`.
|
||||||
|
|
||||||
|
3. `exercises/05_SD_Card_Watcher/src/main.cpp`
|
||||||
|
- Why third: extends `04` and should share the same PMU/pin source strategy.
|
||||||
|
- Planned consume-first item: same board/power source as `04`.
|
||||||
|
|
||||||
|
4. `exercises/06_RTC_check/src/main.cpp`
|
||||||
|
- Why fourth: depends on PMU + I2C pin assumptions that overlap upstream.
|
||||||
|
- Planned consume-first item: board I2C pin source and PMU readiness sequence.
|
||||||
|
|
||||||
|
## Current Wiring Done
|
||||||
|
- Added submodule:
|
||||||
|
- `external/microReticulum_Firmware`
|
||||||
|
- Wired one concrete consumer:
|
||||||
|
- `firmware/fieldtest_beacon/src/main.cpp` now includes `Boards.h` and uses `SD_CS`.
|
||||||
|
- `firmware/fieldtest_beacon/platformio.ini` now includes `external/microReticulum_Firmware` headers and sets `BOARD_MODEL=BOARD_TBEAM_S_V1`.
|
||||||
|
- Added local adapter:
|
||||||
|
- `shared/boards/tbeam_supreme_adapter.h`
|
||||||
|
- Exposes board pins and PMU setup based on upstream T-Beam Supreme definitions.
|
||||||
|
- Refactored exercises to consume adapter:
|
||||||
|
- `exercises/04_SD_card`
|
||||||
|
- `exercises/05_SD_Card_Watcher`
|
||||||
|
- `exercises/06_RTC_check`
|
||||||
|
|
||||||
|
## Validation Status
|
||||||
|
- `firmware/fieldtest_beacon` now builds after adding required deps:
|
||||||
|
- `ArduinoJson` 7.x
|
||||||
|
- `MsgPack`
|
||||||
|
- `Crypto` (provides `Ed25519.h` and `Curve25519.h`)
|
||||||
|
- `exercises/04_SD_card` builds.
|
||||||
|
- `exercises/05_SD_Card_Watcher` builds.
|
||||||
|
- `exercises/06_RTC_check` builds.
|
||||||
|
|
||||||
|
## Next Step Candidate
|
||||||
|
Reduce macro redefinition warnings emitted by `Boards.h` in adapter consumers:
|
||||||
|
- Option A: small local board-map header with only required constants copied from upstream board model.
|
||||||
|
- Option B: upstream contribution to make board capability macros conditional/override-safe.
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
Command:
|
||||||
|
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
Here's a sample compile & upload session:
|
||||||
|
|
||||||
|
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam/exercises/00_usb_radio_check $ pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
Processing node_a (platform: espressif32; framework: arduino; board: esp32-s3-devkitc-1)
|
||||||
|
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
Verbose mode can be enabled via `-v, --verbose` option
|
||||||
|
CONFIGURATION: https://docs.platformio.org/page/boards/espressif32/esp32-s3-devkitc-1.html
|
||||||
|
PLATFORM: Espressif 32 (6.12.0) > Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)
|
||||||
|
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
|
||||||
|
DEBUG: Current (esp-builtin) On-board (esp-builtin) External (cmsis-dap, esp-bridge, esp-prog, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa)
|
||||||
|
PACKAGES:
|
||||||
|
- framework-arduinoespressif32 @ 3.20017.241212+sha.dcc1105b
|
||||||
|
- tool-esptoolpy @ 2.40900.250804 (4.9.0)
|
||||||
|
- tool-mkfatfs @ 2.0.1
|
||||||
|
- tool-mklittlefs @ 1.203.210628 (2.3)
|
||||||
|
- tool-mkspiffs @ 2.230.0 (2.30)
|
||||||
|
- toolchain-riscv32-esp @ 8.4.0+2021r2-patch5
|
||||||
|
- toolchain-xtensa-esp32s3 @ 8.4.0+2021r2-patch5
|
||||||
|
LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf
|
||||||
|
LDF Modes: Finder ~ chain, Compatibility ~ soft
|
||||||
|
Found 34 compatible libraries
|
||||||
|
Scanning dependencies...
|
||||||
|
Dependency Graph
|
||||||
|
|-- RadioLib @ 6.6.0
|
||||||
|
|-- SPI @ 2.0.0
|
||||||
|
Building in release mode
|
||||||
|
Retrieving maximum program size .pio/build/node_a/firmware.elf
|
||||||
|
Checking size .pio/build/node_a/firmware.elf
|
||||||
|
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
|
||||||
|
RAM: [= ] 6.0% (used 19768 bytes from 327680 bytes)
|
||||||
|
Flash: [= ] 8.8% (used 294065 bytes from 3342336 bytes)
|
||||||
|
Configuring upload protocol...
|
||||||
|
AVAILABLE: cmsis-dap, esp-bridge, esp-builtin, esp-prog, espota, esptool, iot-bus-jtag, jlink, minimodule, olimex-arm-usb-ocd, olimex-arm-usb-ocd-h, olimex-arm-usb-tiny-h, olimex-jtag-tiny, tumpa
|
||||||
|
CURRENT: upload_protocol = esptool
|
||||||
|
Looking for upload port...
|
||||||
|
Using manually specified: /dev/ttyACM0
|
||||||
|
Uploading .pio/build/node_a/firmware.bin
|
||||||
|
esptool.py v4.9.0
|
||||||
|
Serial port /dev/ttyACM0
|
||||||
|
Connecting...
|
||||||
|
Chip is ESP32-S3 (QFN56) (revision v0.2)
|
||||||
|
Features: WiFi, BLE, Embedded Flash 8MB (GD)
|
||||||
|
Crystal is 40MHz
|
||||||
|
USB mode: USB-Serial/JTAG
|
||||||
|
MAC: 48:ca:43:5a:93:a0
|
||||||
|
Uploading stub...
|
||||||
|
Running stub...
|
||||||
|
Stub running...
|
||||||
|
Changing baud rate to 460800
|
||||||
|
Changed.
|
||||||
|
Configuring flash size...
|
||||||
|
Flash will be erased from 0x00000000 to 0x00003fff...
|
||||||
|
Flash will be erased from 0x00008000 to 0x00008fff...
|
||||||
|
Flash will be erased from 0x0000e000 to 0x0000ffff...
|
||||||
|
Flash will be erased from 0x00010000 to 0x00057fff...
|
||||||
|
SHA digest in image updated
|
||||||
|
Compressed 15104 bytes to 10430...
|
||||||
|
Writing at 0x00000000... (100 %)
|
||||||
|
Wrote 15104 bytes (10430 compressed) at 0x00000000 in 0.2 seconds (effective 519.1 kbit/s)...
|
||||||
|
Hash of data verified.
|
||||||
|
Compressed 3072 bytes to 146...
|
||||||
|
Writing at 0x00008000... (100 %)
|
||||||
|
Wrote 3072 bytes (146 compressed) at 0x00008000 in 0.0 seconds (effective 584.3 kbit/s)...
|
||||||
|
Hash of data verified.
|
||||||
|
Compressed 8192 bytes to 47...
|
||||||
|
Writing at 0x0000e000... (100 %)
|
||||||
|
Wrote 8192 bytes (47 compressed) at 0x0000e000 in 0.1 seconds (effective 721.9 kbit/s)...
|
||||||
|
Hash of data verified.
|
||||||
|
Compressed 294432 bytes to 164378...
|
||||||
|
Writing at 0x00010000... (9 %)
|
||||||
|
Writing at 0x0001bc31... (18 %)
|
||||||
|
Writing at 0x00024a76... (27 %)
|
||||||
|
Writing at 0x0002a8b3... (36 %)
|
||||||
|
Writing at 0x0002fd85... (45 %)
|
||||||
|
Writing at 0x000350b4... (54 %)
|
||||||
|
Writing at 0x0003b4b4... (63 %)
|
||||||
|
Writing at 0x000455f6... (72 %)
|
||||||
|
Writing at 0x0004c5eb... (81 %)
|
||||||
|
Writing at 0x00051c54... (90 %)
|
||||||
|
Writing at 0x00057b42... (100 %)
|
||||||
|
Wrote 294432 bytes (164378 compressed) at 0x00010000 in 1.9 seconds (effective 1241.1 kbit/s)...
|
||||||
|
Hash of data verified.
|
||||||
|
|
||||||
|
Leaving...
|
||||||
|
Hard resetting via RTS pin...
|
||||||
|
==================================================================================== [SUCCESS] Took 8.73 seconds ====================================================================================
|
||||||
|
|
||||||
|
Environment Status Duration
|
||||||
|
------------- -------- ------------
|
||||||
|
node_a SUCCESS 00:00:08.731
|
||||||
|
==================================================================================== 1 succeeded in 00:00:08.731 ====================================================================================
|
||||||
|
(rnsenv) jlpoole@jp /usr/local/src/microreticulum/microReticulumTbeam/exercises/00_usb_radio_check $
|
||||||
|
|
||||||
|
Here's an example of what displays in the console:
|
||||||
|
|
||||||
|
Booting LoRa test...
|
||||||
|
|
||||||
|
Initializing radio...
|
||||||
|
Radio chip: SX1262
|
||||||
|
Frequency: 915.000 MHz
|
||||||
|
SF: 7 BW: 125 CR: 5
|
||||||
|
radio.begin returned: 0
|
||||||
|
alive 0
|
||||||
|
Sending test frame...
|
||||||
|
TX state: 0
|
||||||
|
Starting receive...
|
||||||
|
startReceive returned: 0
|
||||||
|
alive 1
|
||||||
|
Sending test frame...
|
||||||
|
TX state: 0
|
||||||
|
Starting receive...
|
||||||
|
startReceive returned: 0
|
||||||
|
alive 2
|
||||||
55
exercises/02_oled_display/README.md
Normal file
55
exercises/02_oled_display/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
## Exercise 02: OLED Display
|
||||||
|
|
||||||
|
This exercise demonstrates multiple OLED rendering patterns on the LilyGO T-Beam Supreme (SH1106).
|
||||||
|
|
||||||
|
The firmware runs 3 demos in sequence, then shows a restart banner and repeats forever.
|
||||||
|
|
||||||
|
## Demo Sequence
|
||||||
|
|
||||||
|
### Demo 1: Scrolling Text
|
||||||
|
- Shows `Hello #<n>` lines.
|
||||||
|
- New lines are added below prior lines.
|
||||||
|
- Up to 5 lines are visible; old lines roll off the top.
|
||||||
|
|
||||||
|
### Demo 2: Fill Then Erase
|
||||||
|
- Displays `Count 1` through `Count 10`.
|
||||||
|
- Shows a short `Clearing screen...` message.
|
||||||
|
- Erases the OLED to demonstrate explicit clear behavior.
|
||||||
|
|
||||||
|
### Demo 3: Multi-Page Rotation
|
||||||
|
- Rotates through 3 distinct text pages.
|
||||||
|
- Each page has different content/layout to demonstrate refresh transitions.
|
||||||
|
- Includes a changing packet counter field on one page.
|
||||||
|
|
||||||
|
### Restart Banner
|
||||||
|
- Shows `Restarting 3 demos` for 5 seconds.
|
||||||
|
- Sequence restarts at Demo 1.
|
||||||
|
|
||||||
|
## Expected Serial Output
|
||||||
|
Serial output (115200) prints phase changes, for example:
|
||||||
|
- `Demo 1/3: scrolling Hello #`
|
||||||
|
- `Demo 2/3: clear screen after 10`
|
||||||
|
- `Demo 3/3: three-page rotation`
|
||||||
|
- `Restarting 3 demos`
|
||||||
|
|
||||||
|
### Build
|
||||||
|
From this directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload
|
||||||
|
Set your USB port:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Serial Monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
screen /dev/ttyACM0 115200
|
||||||
|
```
|
||||||
32
exercises/02_oled_display/platformio.ini
Normal file
32
exercises/02_oled_display/platformio.ini
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
; 20260213 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
olikraus/U8g2@^2.36.4
|
||||||
|
|
||||||
|
; Common build flags (pins from Meshtastic tbeam-s3-core variant.h)
|
||||||
|
build_flags =
|
||||||
|
-D OLED_SDA=17
|
||||||
|
-D OLED_SCL=18
|
||||||
|
-D OLED_ADDR=0x3C
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
162
exercises/02_oled_display/src/main.cpp
Normal file
162
exercises/02_oled_display/src/main.cpp
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
// 20260213 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <U8g2lib.h>
|
||||||
|
|
||||||
|
#ifndef OLED_SDA
|
||||||
|
#define OLED_SDA 17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OLED_SCL
|
||||||
|
#define OLED_SCL 18
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef OLED_ADDR
|
||||||
|
#define OLED_ADDR 0x3C
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LilyGO T-Beam Supreme uses SH1106 OLED on I2C.
|
||||||
|
U8G2_SH1106_128X64_NONAME_F_HW_I2C oled(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
|
||||||
|
|
||||||
|
static const uint8_t kMaxLines = 5;
|
||||||
|
static String g_lines[kMaxLines];
|
||||||
|
static uint32_t g_iteration = 1;
|
||||||
|
static uint32_t g_pageCounter = 1;
|
||||||
|
|
||||||
|
static void addLine(const String& line) {
|
||||||
|
for (uint8_t i = 0; i < kMaxLines - 1; ++i) {
|
||||||
|
g_lines[i] = g_lines[i + 1];
|
||||||
|
}
|
||||||
|
g_lines[kMaxLines - 1] = line;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawLines() {
|
||||||
|
oled.clearBuffer();
|
||||||
|
oled.setFont(u8g2_font_6x10_tf);
|
||||||
|
|
||||||
|
const int yStart = 12;
|
||||||
|
const int yStep = 12;
|
||||||
|
for (uint8_t i = 0; i < kMaxLines; ++i) {
|
||||||
|
if (g_lines[i].length() == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
oled.drawUTF8(0, yStart + (i * yStep), g_lines[i].c_str());
|
||||||
|
}
|
||||||
|
oled.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearLines() {
|
||||||
|
for (uint8_t i = 0; i < kMaxLines; ++i) {
|
||||||
|
g_lines[i] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showCentered(const char* line1, const char* line2 = nullptr, const char* line3 = nullptr) {
|
||||||
|
oled.clearBuffer();
|
||||||
|
oled.setFont(u8g2_font_6x10_tf);
|
||||||
|
|
||||||
|
if (line1) oled.drawUTF8(0, 16, line1);
|
||||||
|
if (line2) oled.drawUTF8(0, 32, line2);
|
||||||
|
if (line3) oled.drawUTF8(0, 48, line3);
|
||||||
|
|
||||||
|
oled.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo1_scrollingHello() {
|
||||||
|
Serial.println("Demo 1/3: scrolling Hello #");
|
||||||
|
clearLines();
|
||||||
|
for (uint8_t i = 0; i < 10; ++i) {
|
||||||
|
String line = "Hello #" + String(g_iteration++);
|
||||||
|
addLine(line);
|
||||||
|
drawLines();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo2_clearAfterTen() {
|
||||||
|
Serial.println("Demo 2/3: clear screen after 10");
|
||||||
|
clearLines();
|
||||||
|
for (uint8_t i = 1; i <= 10; ++i) {
|
||||||
|
String line = "Count " + String(i);
|
||||||
|
addLine(line);
|
||||||
|
drawLines();
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
showCentered("Demo 2", "Clearing screen...");
|
||||||
|
delay(1000);
|
||||||
|
oled.clearDisplay();
|
||||||
|
oled.sendBuffer();
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawPage(uint8_t page) {
|
||||||
|
oled.clearBuffer();
|
||||||
|
oled.setFont(u8g2_font_6x10_tf);
|
||||||
|
|
||||||
|
if (page == 0) {
|
||||||
|
oled.drawUTF8(0, 12, "Demo 3 - Page 1/3");
|
||||||
|
oled.drawUTF8(0, 28, "GPS: 37.7749 N");
|
||||||
|
oled.drawUTF8(0, 40, "LON: 122.4194 W");
|
||||||
|
oled.drawUTF8(0, 56, "Fix: 3D");
|
||||||
|
} else if (page == 1) {
|
||||||
|
oled.drawUTF8(0, 12, "Demo 3 - Page 2/3");
|
||||||
|
oled.drawUTF8(0, 28, "LoRa RSSI: -92 dBm");
|
||||||
|
oled.drawUTF8(0, 40, "LoRa SNR: +8.5 dB");
|
||||||
|
oled.drawUTF8(0, 56, "Pkts: " );
|
||||||
|
oled.setCursor(38, 56);
|
||||||
|
oled.print(g_pageCounter++);
|
||||||
|
} else {
|
||||||
|
oled.drawUTF8(0, 12, "Demo 3 - Page 3/3");
|
||||||
|
oled.drawUTF8(0, 28, "Node: TBM-SUPREME");
|
||||||
|
oled.drawUTF8(0, 40, "Mode: Field Test");
|
||||||
|
oled.drawUTF8(0, 56, "OLED refresh demo");
|
||||||
|
}
|
||||||
|
|
||||||
|
oled.sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo3_threePages() {
|
||||||
|
Serial.println("Demo 3/3: three-page rotation");
|
||||||
|
for (uint8_t round = 0; round < 3; ++round) {
|
||||||
|
drawPage(0);
|
||||||
|
delay(700);
|
||||||
|
drawPage(1);
|
||||||
|
delay(700);
|
||||||
|
drawPage(2);
|
||||||
|
delay(700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showRestartBanner() {
|
||||||
|
Serial.println("Restarting 3 demos");
|
||||||
|
showCentered("Restarting 3 demos", "Cycle begins again", "in 5 seconds...");
|
||||||
|
delay(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(250);
|
||||||
|
|
||||||
|
Wire.begin(OLED_SDA, OLED_SCL);
|
||||||
|
oled.setI2CAddress(OLED_ADDR << 1); // U8g2 expects 8-bit address.
|
||||||
|
oled.begin();
|
||||||
|
|
||||||
|
oled.clearBuffer();
|
||||||
|
oled.setFont(u8g2_font_6x10_tf);
|
||||||
|
oled.drawUTF8(0, 12, "Exercise 02 OLED");
|
||||||
|
oled.sendBuffer();
|
||||||
|
|
||||||
|
Serial.println("Exercise 02: OLED display loop");
|
||||||
|
Serial.printf("OLED SDA=%d SCL=%d ADDR=0x%02X\r\n", OLED_SDA, OLED_SCL, OLED_ADDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
demo1_scrollingHello();
|
||||||
|
demo2_clearAfterTen();
|
||||||
|
demo3_threePages();
|
||||||
|
showRestartBanner();
|
||||||
|
}
|
||||||
37
exercises/04_SD_card/README.md
Normal file
37
exercises/04_SD_card/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
## Exercise 04: SD Card
|
||||||
|
|
||||||
|
This exercise loops forever. Each cycle:
|
||||||
|
|
||||||
|
1. Prints `Sleeping 10 seconds` and waits 10 seconds.
|
||||||
|
2. Detects and mounts the SD card.
|
||||||
|
3. Prints card and filesystem info (type/size/used).
|
||||||
|
4. Writes `/Exercise_04_test.txt` with:
|
||||||
|
- `This is a test`
|
||||||
|
5. Ensures nested directories exist:
|
||||||
|
- `/test/testsub1/testsubsub1`
|
||||||
|
6. Writes nested file:
|
||||||
|
- `/test/testsub1/testsubsub1/Exercise_04_test.txt`
|
||||||
|
7. If either target file already exists, prints warning, erases it, then recreates it.
|
||||||
|
8. Demonstrates permission behavior:
|
||||||
|
- Notes FAT does not provide Unix `chmod/chown`.
|
||||||
|
- Shows access behavior via `FILE_READ` vs `FILE_WRITE` modes.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
screen /dev/ttyACM0 115200
|
||||||
|
```
|
||||||
33
exercises/04_SD_card/platformio.ini
Normal file
33
exercises/04_SD_card/platformio.ini
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
; 20260213 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
lewisxhe/XPowersLib@0.3.3
|
||||||
|
Wire
|
||||||
|
|
||||||
|
; SD pins based on T-Beam S3 core pin mapping
|
||||||
|
build_flags =
|
||||||
|
-I ../../shared/boards
|
||||||
|
-I ../../external/microReticulum_Firmware
|
||||||
|
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
228
exercises/04_SD_card/src/main.cpp
Normal file
228
exercises/04_SD_card/src/main.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
// 20260213 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <SD.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include "tbeam_supreme_adapter.h"
|
||||||
|
|
||||||
|
static SPIClass sdSpiH(HSPI);
|
||||||
|
static SPIClass sdSpiF(FSPI);
|
||||||
|
static SPIClass* g_sdSpi = nullptr;
|
||||||
|
static const char* g_sdBusName = "none";
|
||||||
|
static uint32_t g_sdFreq = 0;
|
||||||
|
static XPowersLibInterface* g_pmu = nullptr;
|
||||||
|
|
||||||
|
static const char* kRootTestFile = "/Exercise_04_test.txt";
|
||||||
|
static const char* kNestedDir = "/test/testsub1/testsubsub1";
|
||||||
|
static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_04_test.txt";
|
||||||
|
static const char* kPayload = "This is a test";
|
||||||
|
|
||||||
|
static bool initPmuForSdPower() {
|
||||||
|
return tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* cardTypeToString(uint8_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case CARD_MMC: return "MMC";
|
||||||
|
case CARD_SD: return "SDSC";
|
||||||
|
case CARD_SDHC: return "SDHC/SDXC";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz) {
|
||||||
|
SD.end();
|
||||||
|
bus.end();
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
// Keep inactive devices deselected on shared bus lines.
|
||||||
|
pinMode(tbeam_supreme::sdCs(), OUTPUT);
|
||||||
|
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||||
|
pinMode(tbeam_supreme::imuCs(), OUTPUT);
|
||||||
|
digitalWrite(tbeam_supreme::imuCs(), HIGH);
|
||||||
|
|
||||||
|
bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs());
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
Serial.printf("SD: trying bus=%s freq=%lu Hz\r\n", busName, (unsigned long)hz);
|
||||||
|
if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
|
||||||
|
Serial.println("SD: mount failed (possible non-FAT format, power, or bus issue)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
if (cardType == CARD_NONE) {
|
||||||
|
SD.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_sdSpi = &bus;
|
||||||
|
g_sdBusName = busName;
|
||||||
|
g_sdFreq = hz;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mountCard() {
|
||||||
|
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||||
|
if (tryMountWithBus(sdSpiH, "HSPI", freqs[i])) {
|
||||||
|
Serial.println("SD: card detected and mounted");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||||
|
if (tryMountWithBus(sdSpiF, "FSPI", freqs[i])) {
|
||||||
|
Serial.println("SD: card detected and mounted");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("SD: begin() failed on all bus/frequency attempts");
|
||||||
|
Serial.println(" likely card absent, bad format, pin mismatch, or hardware issue");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printCardInfo() {
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);
|
||||||
|
uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL);
|
||||||
|
uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL);
|
||||||
|
|
||||||
|
Serial.printf("SD type: %s\r\n", cardTypeToString(cardType));
|
||||||
|
Serial.printf("SD size: %llu MB\r\n", cardSizeMB);
|
||||||
|
Serial.printf("FS total: %llu MB\r\n", totalMB);
|
||||||
|
Serial.printf("FS used : %llu MB\r\n", usedMB);
|
||||||
|
Serial.printf("SPI bus: %s @ %lu Hz\r\n", g_sdBusName, (unsigned long)g_sdFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ensureDirRecursive(const char* path) {
|
||||||
|
String full(path);
|
||||||
|
if (!full.startsWith("/")) {
|
||||||
|
full = "/" + full;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 1;
|
||||||
|
while (start > 0 && start < (int)full.length()) {
|
||||||
|
int slash = full.indexOf('/', start);
|
||||||
|
String partial = (slash < 0) ? full : full.substring(0, slash);
|
||||||
|
|
||||||
|
if (!SD.exists(partial.c_str())) {
|
||||||
|
Serial.printf("Creating directory: %s\r\n", partial.c_str());
|
||||||
|
if (!SD.mkdir(partial.c_str())) {
|
||||||
|
Serial.printf("ERROR: mkdir failed for %s\r\n", partial.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slash < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = slash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rewriteFile(const char* path, const char* payload) {
|
||||||
|
if (SD.exists(path)) {
|
||||||
|
Serial.printf("WARNING: %s exists ... erasing\r\n", path);
|
||||||
|
if (!SD.remove(path)) {
|
||||||
|
Serial.printf("ERROR: failed to erase %s\r\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = SD.open(path, FILE_WRITE);
|
||||||
|
if (!f) {
|
||||||
|
Serial.printf("ERROR: failed to create %s\r\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t wrote = f.println(payload);
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
if (wrote == 0) {
|
||||||
|
Serial.printf("ERROR: write failed for %s\r\n", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("Wrote file: %s\r\n", path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void permissionsDemo(const char* path) {
|
||||||
|
Serial.println("Permissions demo:");
|
||||||
|
Serial.println(" SD/FAT does not support Unix permissions (chmod/chown).");
|
||||||
|
Serial.println(" Access control is by open mode (FILE_READ/FILE_WRITE).");
|
||||||
|
|
||||||
|
File r = SD.open(path, FILE_READ);
|
||||||
|
if (!r) {
|
||||||
|
Serial.printf(" Could not open %s as FILE_READ\r\n", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf(" FILE_READ open succeeded, size=%u bytes\r\n", (unsigned)r.size());
|
||||||
|
size_t writeInReadMode = r.print("attempt write while opened read-only");
|
||||||
|
if (writeInReadMode == 0) {
|
||||||
|
Serial.println(" As expected, write via FILE_READ handle was blocked.");
|
||||||
|
} else {
|
||||||
|
Serial.printf(" NOTE: write via FILE_READ returned %u (unexpected)\r\n", (unsigned)writeInReadMode);
|
||||||
|
}
|
||||||
|
r.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection...");
|
||||||
|
delay(5000); // Time to open Serial Monitor after reset
|
||||||
|
|
||||||
|
Serial.println("\r\n==================================================");
|
||||||
|
Serial.println("Exercise 04: SD card test loop");
|
||||||
|
Serial.println("==================================================");
|
||||||
|
Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n",
|
||||||
|
tbeam_supreme::sdCs(), tbeam_supreme::sdSck(),
|
||||||
|
tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi());
|
||||||
|
Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n",
|
||||||
|
tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl());
|
||||||
|
Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n");
|
||||||
|
|
||||||
|
initPmuForSdPower();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Serial.println("Sleeping 10 seconds");
|
||||||
|
delay(10000);
|
||||||
|
|
||||||
|
if (!mountCard()) {
|
||||||
|
Serial.println("SD step skipped this cycle.\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printCardInfo();
|
||||||
|
|
||||||
|
if (!rewriteFile(kRootTestFile, kPayload)) {
|
||||||
|
SD.end();
|
||||||
|
Serial.println("Cycle ended due to root file error.\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ensureDirRecursive(kNestedDir)) {
|
||||||
|
SD.end();
|
||||||
|
Serial.println("Cycle ended due to directory creation error.\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rewriteFile(kNestedTestFile, kPayload)) {
|
||||||
|
SD.end();
|
||||||
|
Serial.println("Cycle ended due to nested file error.\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionsDemo(kRootTestFile);
|
||||||
|
SD.end();
|
||||||
|
Serial.println("Cycle complete.\r\n");
|
||||||
|
}
|
||||||
39
exercises/05_SD_Card_Watcher/README.md
Normal file
39
exercises/05_SD_Card_Watcher/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
## Exercise 05: SD Card Watcher
|
||||||
|
|
||||||
|
This exercise continuously watches SD card presence and prints state-change events.
|
||||||
|
|
||||||
|
Watcher behavior:
|
||||||
|
|
||||||
|
1. Initializes PMU and enables SD power rail (AXP2101 BLDO1).
|
||||||
|
2. Polls for card changes with debounced state transitions.
|
||||||
|
3. Emits events only on change:
|
||||||
|
- `EVENT: card inserted/mounted`
|
||||||
|
- `EVENT: card removed/unavailable`
|
||||||
|
- `EVENT: no card detected`
|
||||||
|
4. On mount event, prints card info and runs SD write workflow.
|
||||||
|
5. Every 15 seconds while mounted, runs a periodic write/permission check.
|
||||||
|
6. Uses fast preferred probe (`HSPI @ 400k`) and occasional full fallback scan.
|
||||||
|
|
||||||
|
Files used in this exercise:
|
||||||
|
- `/Exercise_05_test.txt`
|
||||||
|
- `/test/testsub1/testsubsub1/Exercise_05_test.txt`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
screen /dev/ttyACM0 115200
|
||||||
|
```
|
||||||
33
exercises/05_SD_Card_Watcher/platformio.ini
Normal file
33
exercises/05_SD_Card_Watcher/platformio.ini
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
; 20260213 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
lewisxhe/XPowersLib@0.3.3
|
||||||
|
Wire
|
||||||
|
|
||||||
|
; SD pins based on T-Beam S3 core pin mapping
|
||||||
|
build_flags =
|
||||||
|
-I ../../shared/boards
|
||||||
|
-I ../../external/microReticulum_Firmware
|
||||||
|
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
366
exercises/05_SD_Card_Watcher/src/main.cpp
Normal file
366
exercises/05_SD_Card_Watcher/src/main.cpp
Normal file
|
|
@ -0,0 +1,366 @@
|
||||||
|
// 20260213 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <FS.h>
|
||||||
|
#include <SD.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include "tbeam_supreme_adapter.h"
|
||||||
|
|
||||||
|
static SPIClass sdSpiH(HSPI);
|
||||||
|
static SPIClass sdSpiF(FSPI);
|
||||||
|
static SPIClass* g_sdSpi = nullptr;
|
||||||
|
static const char* g_sdBusName = "none";
|
||||||
|
static uint32_t g_sdFreq = 0;
|
||||||
|
static XPowersLibInterface* g_pmu = nullptr;
|
||||||
|
|
||||||
|
static const char* kRootTestFile = "/Exercise_05_test.txt";
|
||||||
|
static const char* kNestedDir = "/test/testsub1/testsubsub1";
|
||||||
|
static const char* kNestedTestFile = "/test/testsub1/testsubsub1/Exercise_05_test.txt";
|
||||||
|
static const char* kPayload = "This is a test";
|
||||||
|
|
||||||
|
enum class WatchState : uint8_t {
|
||||||
|
UNKNOWN = 0,
|
||||||
|
ABSENT,
|
||||||
|
MOUNTED
|
||||||
|
};
|
||||||
|
|
||||||
|
static WatchState g_watchState = WatchState::UNKNOWN;
|
||||||
|
static uint8_t g_presentVotes = 0;
|
||||||
|
static uint8_t g_absentVotes = 0;
|
||||||
|
static uint32_t g_lastPollMs = 0;
|
||||||
|
static uint32_t g_lastFullScanMs = 0;
|
||||||
|
static uint32_t g_lastPeriodicActionMs = 0;
|
||||||
|
static const uint32_t kPollIntervalAbsentMs = 1000;
|
||||||
|
static const uint32_t kPollIntervalMountedMs = 2000;
|
||||||
|
static const uint32_t kFullScanIntervalMs = 10000;
|
||||||
|
static const uint32_t kPeriodicActionMs = 15000;
|
||||||
|
static const uint8_t kVotesToPresent = 2;
|
||||||
|
//static const uint8_t kVotesToAbsent = 4;
|
||||||
|
static const uint8_t kVotesToAbsent = 5; // More votes needed to declare absent to prevent false removes on transient errors.
|
||||||
|
|
||||||
|
//static const uint32_t kStartupWarmupMs = 800;
|
||||||
|
static const uint32_t kStartupWarmupMs = 1500; // Longer warmup to allow PMU and card stabilization after power-on.
|
||||||
|
static uint32_t g_logSeq = 0;
|
||||||
|
|
||||||
|
static void logf(const char* fmt, ...) {
|
||||||
|
char msg[192];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool initPmuForSdPower() {
|
||||||
|
return tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* cardTypeToString(uint8_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case CARD_MMC: return "MMC";
|
||||||
|
case CARD_SD: return "SDSC";
|
||||||
|
case CARD_SDHC: return "SDHC/SDXC";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, bool verbose) {
|
||||||
|
SD.end();
|
||||||
|
bus.end();
|
||||||
|
delay(10);
|
||||||
|
|
||||||
|
// Keep inactive devices deselected on shared bus lines.
|
||||||
|
pinMode(tbeam_supreme::sdCs(), OUTPUT);
|
||||||
|
digitalWrite(tbeam_supreme::sdCs(), HIGH);
|
||||||
|
pinMode(tbeam_supreme::imuCs(), OUTPUT);
|
||||||
|
digitalWrite(tbeam_supreme::imuCs(), HIGH);
|
||||||
|
|
||||||
|
bus.begin(tbeam_supreme::sdSck(), tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi(), tbeam_supreme::sdCs());
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
if (verbose) {
|
||||||
|
logf("SD: trying bus=%s freq=%lu Hz", busName, (unsigned long)hz);
|
||||||
|
}
|
||||||
|
if (!SD.begin(tbeam_supreme::sdCs(), bus, hz)) {
|
||||||
|
if (verbose) {
|
||||||
|
logf("SD: mount failed (possible non-FAT format, power, or bus issue)");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
if (cardType == CARD_NONE) {
|
||||||
|
SD.end();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_sdSpi = &bus;
|
||||||
|
g_sdBusName = busName;
|
||||||
|
g_sdFreq = hz;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mountPreferred(bool verbose) {
|
||||||
|
return tryMountWithBus(sdSpiH, "HSPI", 400000, verbose);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool mountCard() {
|
||||||
|
const uint32_t freqs[] = {400000, 1000000, 4000000, 10000000};
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||||
|
if (tryMountWithBus(sdSpiH, "HSPI", freqs[i], true)) {
|
||||||
|
logf("SD: card detected and mounted");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (uint8_t i = 0; i < (sizeof(freqs) / sizeof(freqs[0])); ++i) {
|
||||||
|
if (tryMountWithBus(sdSpiF, "FSPI", freqs[i], true)) {
|
||||||
|
logf("SD: card detected and mounted");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("SD: begin() failed on all bus/frequency attempts");
|
||||||
|
logf(" likely card absent, bad format, pin mismatch, or hardware issue");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printCardInfo() {
|
||||||
|
uint8_t cardType = SD.cardType();
|
||||||
|
uint64_t cardSizeMB = SD.cardSize() / (1024ULL * 1024ULL);
|
||||||
|
uint64_t totalMB = SD.totalBytes() / (1024ULL * 1024ULL);
|
||||||
|
uint64_t usedMB = SD.usedBytes() / (1024ULL * 1024ULL);
|
||||||
|
|
||||||
|
logf("SD type: %s", cardTypeToString(cardType));
|
||||||
|
logf("SD size: %llu MB", cardSizeMB);
|
||||||
|
logf("FS total: %llu MB", totalMB);
|
||||||
|
logf("FS used : %llu MB", usedMB);
|
||||||
|
logf("SPI bus: %s @ %lu Hz", g_sdBusName, (unsigned long)g_sdFreq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ensureDirRecursive(const char* path) {
|
||||||
|
String full(path);
|
||||||
|
if (!full.startsWith("/")) {
|
||||||
|
full = "/" + full;
|
||||||
|
}
|
||||||
|
|
||||||
|
int start = 1;
|
||||||
|
while (start > 0 && start < (int)full.length()) {
|
||||||
|
int slash = full.indexOf('/', start);
|
||||||
|
String partial = (slash < 0) ? full : full.substring(0, slash);
|
||||||
|
|
||||||
|
if (!SD.exists(partial.c_str())) {
|
||||||
|
logf("Creating directory: %s", partial.c_str());
|
||||||
|
if (!SD.mkdir(partial.c_str())) {
|
||||||
|
logf("ERROR: mkdir failed for %s", partial.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slash < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = slash + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rewriteFile(const char* path, const char* payload) {
|
||||||
|
if (SD.exists(path)) {
|
||||||
|
logf("WARNING: %s exists ... erasing", path);
|
||||||
|
if (!SD.remove(path)) {
|
||||||
|
logf("ERROR: failed to erase %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = SD.open(path, FILE_WRITE);
|
||||||
|
if (!f) {
|
||||||
|
logf("ERROR: failed to create %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t wrote = f.println(payload);
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
if (wrote == 0) {
|
||||||
|
logf("ERROR: write failed for %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("Wrote file: %s", path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void permissionsDemo(const char* path) {
|
||||||
|
logf("Permissions demo:");
|
||||||
|
logf(" SD/FAT does not support Unix permissions (chmod/chown).");
|
||||||
|
logf(" Access control is by open mode (FILE_READ/FILE_WRITE).");
|
||||||
|
|
||||||
|
File r = SD.open(path, FILE_READ);
|
||||||
|
if (!r) {
|
||||||
|
logf(" Could not open %s as FILE_READ", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logf(" FILE_READ open succeeded, size=%u bytes", (unsigned)r.size());
|
||||||
|
size_t writeInReadMode = r.print("attempt write while opened read-only");
|
||||||
|
if (writeInReadMode == 0) {
|
||||||
|
logf(" As expected, write via FILE_READ handle was blocked.");
|
||||||
|
} else {
|
||||||
|
logf(" NOTE: write via FILE_READ returned %u (unexpected)", (unsigned)writeInReadMode);
|
||||||
|
}
|
||||||
|
r.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool verifyMountedCard() {
|
||||||
|
File root = SD.open("/", FILE_READ);
|
||||||
|
if (!root) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
root.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void runCardWorkflow() {
|
||||||
|
printCardInfo();
|
||||||
|
|
||||||
|
if (!rewriteFile(kRootTestFile, kPayload)) {
|
||||||
|
logf("Watcher action: root file write failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ensureDirRecursive(kNestedDir)) {
|
||||||
|
logf("Watcher action: directory creation failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!rewriteFile(kNestedTestFile, kPayload)) {
|
||||||
|
logf("Watcher action: nested file write failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionsDemo(kRootTestFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setStateMounted() {
|
||||||
|
if (g_watchState != WatchState::MOUNTED) {
|
||||||
|
logf("EVENT: card inserted/mounted");
|
||||||
|
runCardWorkflow();
|
||||||
|
g_lastPeriodicActionMs = millis();
|
||||||
|
}
|
||||||
|
g_watchState = WatchState::MOUNTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setStateAbsent() {
|
||||||
|
if (g_watchState == WatchState::MOUNTED) {
|
||||||
|
logf("EVENT: card removed/unavailable");
|
||||||
|
} else if (g_watchState != WatchState::ABSENT) {
|
||||||
|
logf("EVENT: no card detected");
|
||||||
|
}
|
||||||
|
SD.end();
|
||||||
|
g_watchState = WatchState::ABSENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("[WATCHER: startup]");
|
||||||
|
Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection...");
|
||||||
|
delay(5000); // Time to open Serial Monitor after reset
|
||||||
|
|
||||||
|
Serial.println("\r\n==================================================");
|
||||||
|
Serial.println("Exercise 05: SD Card Watcher");
|
||||||
|
Serial.println("==================================================");
|
||||||
|
Serial.printf("Pins: CS=%d SCK=%d MISO=%d MOSI=%d\r\n",
|
||||||
|
tbeam_supreme::sdCs(), tbeam_supreme::sdSck(),
|
||||||
|
tbeam_supreme::sdMiso(), tbeam_supreme::sdMosi());
|
||||||
|
Serial.printf("PMU I2C: SDA1=%d SCL1=%d\r\n",
|
||||||
|
tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl());
|
||||||
|
Serial.println("Note: SD must be FAT16/FAT32 for Arduino SD library.\r\n");
|
||||||
|
|
||||||
|
initPmuForSdPower();
|
||||||
|
logf("Watcher: waiting %lu ms for SD rail/card stabilization", (unsigned long)kStartupWarmupMs);
|
||||||
|
delay(kStartupWarmupMs);
|
||||||
|
|
||||||
|
// Warm-up attempts before first status decision.
|
||||||
|
bool warmMounted = false;
|
||||||
|
for (uint8_t i = 0; i < 3; ++i) {
|
||||||
|
if (mountPreferred(false)) {
|
||||||
|
warmMounted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay(200);
|
||||||
|
}
|
||||||
|
if (warmMounted) {
|
||||||
|
logf("Watcher: startup warmup mount succeeded");
|
||||||
|
setStateMounted();
|
||||||
|
} else {
|
||||||
|
logf("Watcher: startup warmup did not mount card");
|
||||||
|
setStateAbsent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
const uint32_t now = millis();
|
||||||
|
const uint32_t pollInterval = (g_watchState == WatchState::MOUNTED)
|
||||||
|
? kPollIntervalMountedMs
|
||||||
|
: kPollIntervalAbsentMs;
|
||||||
|
if ((uint32_t)(now - g_lastPollMs) < pollInterval) {
|
||||||
|
delay(10);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
g_lastPollMs = now;
|
||||||
|
|
||||||
|
if (g_watchState == WatchState::MOUNTED) {
|
||||||
|
if (verifyMountedCard()) {
|
||||||
|
if ((uint32_t)(now - g_lastPeriodicActionMs) >= kPeriodicActionMs) {
|
||||||
|
logf("Watcher: periodic mounted check action");
|
||||||
|
runCardWorkflow();
|
||||||
|
g_lastPeriodicActionMs = now;
|
||||||
|
}
|
||||||
|
g_presentVotes = 0;
|
||||||
|
g_absentVotes = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One immediate remount attempt prevents false remove events on transient SPI errors.
|
||||||
|
if (mountPreferred(false) && verifyMountedCard()) {
|
||||||
|
g_presentVotes = 0;
|
||||||
|
g_absentVotes = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_absentVotes++;
|
||||||
|
g_presentVotes = 0;
|
||||||
|
if (g_absentVotes >= kVotesToAbsent) {
|
||||||
|
setStateAbsent();
|
||||||
|
g_absentVotes = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mounted = mountPreferred(false);
|
||||||
|
if (!mounted && (uint32_t)(now - g_lastFullScanMs) >= kFullScanIntervalMs) {
|
||||||
|
g_lastFullScanMs = now;
|
||||||
|
logf("Watcher: preferred probe failed, running full scan");
|
||||||
|
mounted = mountCard();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
g_presentVotes++;
|
||||||
|
g_absentVotes = 0;
|
||||||
|
if (g_presentVotes >= kVotesToPresent) {
|
||||||
|
setStateMounted();
|
||||||
|
g_presentVotes = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
g_absentVotes++;
|
||||||
|
g_presentVotes = 0;
|
||||||
|
if (g_absentVotes >= kVotesToAbsent) {
|
||||||
|
setStateAbsent();
|
||||||
|
g_absentVotes = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
exercises/06_RTC_check/README.md
Normal file
43
exercises/06_RTC_check/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
## Exercise 06: RTC Check (PCF8563)
|
||||||
|
|
||||||
|
This exercise validates RTC read/write and power-off persistence on the T-Beam Supreme.
|
||||||
|
|
||||||
|
It:
|
||||||
|
- Initializes PMU + I2C bus used by RTC.
|
||||||
|
- Reads RTC at startup.
|
||||||
|
- Prints RTC every 10 seconds.
|
||||||
|
- Accepts serial commands:
|
||||||
|
- `show`
|
||||||
|
- `set YYYY-MM-DD HH:MM:SS`
|
||||||
|
- `help`
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
pio run -e node_a -t upload --upload-port /dev/ttyACM0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monitor
|
||||||
|
|
||||||
|
```bash
|
||||||
|
screen /dev/ttyACM0 115200
|
||||||
|
```
|
||||||
|
|
||||||
|
## Suggested Persistence Test
|
||||||
|
|
||||||
|
1. Set the RTC:
|
||||||
|
- `set 2026-02-14 17:30:00`
|
||||||
|
2. Confirm:
|
||||||
|
- `show`
|
||||||
|
3. Power off the unit for a few minutes.
|
||||||
|
4. Power on and run:
|
||||||
|
- `show`
|
||||||
|
5. Compare expected elapsed time vs RTC output.
|
||||||
32
exercises/06_RTC_check/platformio.ini
Normal file
32
exercises/06_RTC_check/platformio.ini
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
; 20260214 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = node_a
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
monitor_speed = 115200
|
||||||
|
lib_deps =
|
||||||
|
lewisxhe/XPowersLib@0.3.3
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-I ../../shared/boards
|
||||||
|
-I ../../external/microReticulum_Firmware
|
||||||
|
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||||
|
-D RTC_I2C_ADDR=0x51
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
[env:node_a]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"A\"
|
||||||
|
|
||||||
|
[env:node_b]
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D NODE_LABEL=\"B\"
|
||||||
224
exercises/06_RTC_check/src/main.cpp
Normal file
224
exercises/06_RTC_check/src/main.cpp
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
// 20260214 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include "tbeam_supreme_adapter.h"
|
||||||
|
|
||||||
|
#ifndef RTC_I2C_ADDR
|
||||||
|
#define RTC_I2C_ADDR 0x51
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static XPowersLibInterface* g_pmu = nullptr;
|
||||||
|
static uint32_t g_logSeq = 0;
|
||||||
|
static uint32_t g_lastPrintMs = 0;
|
||||||
|
static String g_cmdBuf;
|
||||||
|
static bool g_lastWasCR = false;
|
||||||
|
|
||||||
|
struct RtcDateTime {
|
||||||
|
uint16_t year;
|
||||||
|
uint8_t month;
|
||||||
|
uint8_t day;
|
||||||
|
uint8_t hour;
|
||||||
|
uint8_t minute;
|
||||||
|
uint8_t second;
|
||||||
|
uint8_t weekday;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t toBcd(uint8_t v) {
|
||||||
|
return ((v / 10U) << 4U) | (v % 10U);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t fromBcd(uint8_t b) {
|
||||||
|
return ((b >> 4U) * 10U) + (b & 0x0FU);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void logf(const char* fmt, ...) {
|
||||||
|
char msg[196];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
Serial.printf("[%10lu][%06lu] %s\r\n", (unsigned long)millis(), (unsigned long)g_logSeq++, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool initPmuForRtc() {
|
||||||
|
return tbeam_supreme::initPmuForPeripherals(g_pmu, &Serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rtcRead(RtcDateTime& out, bool& lowVoltageFlag) {
|
||||||
|
Wire1.beginTransmission(RTC_I2C_ADDR);
|
||||||
|
Wire1.write(0x02); // seconds register
|
||||||
|
if (Wire1.endTransmission(false) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t need = 7;
|
||||||
|
uint8_t got = Wire1.requestFrom((int)RTC_I2C_ADDR, (int)need);
|
||||||
|
if (got != need) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t sec = Wire1.read();
|
||||||
|
uint8_t min = Wire1.read();
|
||||||
|
uint8_t hour = Wire1.read();
|
||||||
|
uint8_t day = Wire1.read();
|
||||||
|
uint8_t weekday = Wire1.read();
|
||||||
|
uint8_t month = Wire1.read();
|
||||||
|
uint8_t year = Wire1.read();
|
||||||
|
|
||||||
|
lowVoltageFlag = (sec & 0x80U) != 0;
|
||||||
|
out.second = fromBcd(sec & 0x7FU);
|
||||||
|
out.minute = fromBcd(min & 0x7FU);
|
||||||
|
out.hour = fromBcd(hour & 0x3FU);
|
||||||
|
out.day = fromBcd(day & 0x3FU);
|
||||||
|
out.weekday = fromBcd(weekday & 0x07U);
|
||||||
|
out.month = fromBcd(month & 0x1FU);
|
||||||
|
uint8_t yy = fromBcd(year);
|
||||||
|
bool century = (month & 0x80U) != 0;
|
||||||
|
out.year = century ? (1900U + yy) : (2000U + yy);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rtcWrite(const RtcDateTime& in) {
|
||||||
|
bool century = in.year < 2000;
|
||||||
|
uint8_t yy = (uint8_t)(in.year % 100);
|
||||||
|
uint8_t monthReg = toBcd(in.month) & 0x1FU;
|
||||||
|
if (century) {
|
||||||
|
monthReg |= 0x80U;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wire1.beginTransmission(RTC_I2C_ADDR);
|
||||||
|
Wire1.write(0x02); // seconds register
|
||||||
|
Wire1.write(toBcd(in.second) & 0x7FU);
|
||||||
|
Wire1.write(toBcd(in.minute) & 0x7FU);
|
||||||
|
Wire1.write(toBcd(in.hour) & 0x3FU);
|
||||||
|
Wire1.write(toBcd(in.day) & 0x3FU);
|
||||||
|
Wire1.write(toBcd(in.weekday) & 0x07U);
|
||||||
|
Wire1.write(monthReg);
|
||||||
|
Wire1.write(toBcd(yy));
|
||||||
|
return Wire1.endTransmission() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool parseSetCommand(const String& line, RtcDateTime& dt) {
|
||||||
|
int y, mo, d, h, mi, s;
|
||||||
|
if (sscanf(line.c_str(), "set %d-%d-%d %d:%d:%d", &y, &mo, &d, &h, &mi, &s) != 6) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < 1900 || y > 2099) return false;
|
||||||
|
if (mo < 1 || mo > 12) return false;
|
||||||
|
if (d < 1 || d > 31) return false;
|
||||||
|
if (h < 0 || h > 23) return false;
|
||||||
|
if (mi < 0 || mi > 59) return false;
|
||||||
|
if (s < 0 || s > 59) return false;
|
||||||
|
|
||||||
|
dt.year = (uint16_t)y;
|
||||||
|
dt.month = (uint8_t)mo;
|
||||||
|
dt.day = (uint8_t)d;
|
||||||
|
dt.hour = (uint8_t)h;
|
||||||
|
dt.minute = (uint8_t)mi;
|
||||||
|
dt.second = (uint8_t)s;
|
||||||
|
dt.weekday = 0; // Not critical for this persistence check.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printRtcNow(const char* label) {
|
||||||
|
RtcDateTime now{};
|
||||||
|
bool lowV = false;
|
||||||
|
if (!rtcRead(now, lowV)) {
|
||||||
|
logf("%s: RTC read failed", label);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logf("%s: %04u-%02u-%02u %02u:%02u:%02u weekday=%u%s",
|
||||||
|
label,
|
||||||
|
(unsigned)now.year, (unsigned)now.month, (unsigned)now.day,
|
||||||
|
(unsigned)now.hour, (unsigned)now.minute, (unsigned)now.second,
|
||||||
|
(unsigned)now.weekday,
|
||||||
|
lowV ? " [LOW_VOLTAGE_FLAG]" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleCommand(const String& raw) {
|
||||||
|
String line = raw;
|
||||||
|
line.trim();
|
||||||
|
if (line.length() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == "help") {
|
||||||
|
logf("Commands:");
|
||||||
|
logf(" show");
|
||||||
|
logf(" set YYYY-MM-DD HH:MM:SS");
|
||||||
|
logf(" help");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (line == "show") {
|
||||||
|
printRtcNow("RTC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RtcDateTime dt{};
|
||||||
|
if (parseSetCommand(line, dt)) {
|
||||||
|
if (rtcWrite(dt)) {
|
||||||
|
logf("RTC set succeeded");
|
||||||
|
printRtcNow("RTC");
|
||||||
|
} else {
|
||||||
|
logf("RTC set failed");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logf("Unknown command: %s", line.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println("Sleeping for 5 seconds to allow Serial Monitor connection...");
|
||||||
|
delay(5000);
|
||||||
|
|
||||||
|
Serial.println("\r\n==================================================");
|
||||||
|
Serial.println("Exercise 06: RTC check (PCF8563)");
|
||||||
|
Serial.println("==================================================");
|
||||||
|
Serial.printf("RTC I2C: SDA1=%d SCL1=%d ADDR=0x%02X\r\n",
|
||||||
|
tbeam_supreme::i2cSda(), tbeam_supreme::i2cScl(), RTC_I2C_ADDR);
|
||||||
|
|
||||||
|
initPmuForRtc();
|
||||||
|
|
||||||
|
logf("Type 'help' for commands.");
|
||||||
|
logf("Power-off persistence test:");
|
||||||
|
logf(" 1) set time");
|
||||||
|
logf(" 2) power off for a few minutes");
|
||||||
|
logf(" 3) power on and run 'show' before any GPS sync");
|
||||||
|
printRtcNow("RTC startup");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
while (Serial.available() > 0) {
|
||||||
|
char c = (char)Serial.read();
|
||||||
|
if (c == '\r' || c == '\n') {
|
||||||
|
// Handle CR, LF, and CRLF/LFCR cleanly as one line ending.
|
||||||
|
if ((c == '\n' && g_lastWasCR) || (c == '\r' && !g_lastWasCR && g_cmdBuf.length() == 0)) {
|
||||||
|
g_lastWasCR = (c == '\r');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
handleCommand(g_cmdBuf);
|
||||||
|
g_cmdBuf = "";
|
||||||
|
g_lastWasCR = (c == '\r');
|
||||||
|
} else {
|
||||||
|
g_lastWasCR = false;
|
||||||
|
g_cmdBuf += c;
|
||||||
|
if (g_cmdBuf.length() > 120) {
|
||||||
|
g_cmdBuf = "";
|
||||||
|
logf("Input line too long, buffer cleared");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t nowMs = millis();
|
||||||
|
if ((uint32_t)(nowMs - g_lastPrintMs) >= 10000) {
|
||||||
|
g_lastPrintMs = nowMs;
|
||||||
|
printRtcNow("RTC periodic");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ There are three buttons
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
From Antenna to USB port:
|
Listed in order from Antenna to USB port:
|
||||||
1) RESET
|
1) RESET
|
||||||
2) POWER
|
2) POWER
|
||||||
3) BOOT
|
3) BOOT
|
||||||
|
|
@ -16,15 +16,22 @@ The RESET button only needs to be depressed for less than 1 second. Doing so wi
|
||||||
If your unit is powered OFF, simply depress the POWER button will start the unit. If the unit is powered ON, then depressing the POWER button and holding it down for 6 second will cause the unit to power down. The OLED display will go dark when the unit has successfully been powered down.
|
If your unit is powered OFF, simply depress the POWER button will start the unit. If the unit is powered ON, then depressing the POWER button and holding it down for 6 second will cause the unit to power down. The OLED display will go dark when the unit has successfully been powered down.
|
||||||
|
|
||||||
## BOOT
|
## BOOT
|
||||||
A) TODO: what happens when unit is powered ON?
|
A) TODO: what happens when unit is powered ON and you depress BOOT?
|
||||||
B) TODO: what happens when unit is powered OFF?
|
B) TODO: what happens when unit is powered OFF and you depress BOOT?
|
||||||
C) To upload new binary image: depress the BOOT button and hold down, then while holding down the BOOT button disconnect the USB line and then reconnect the USB line and then lift up on the BOOT button. This will cause the unit to await an upload of an image to be stored in its FLASH memory. See further how to flash a new image in the section "Flashing Binary". Note: although the uploader may state it is trying to reset after successful installation of the new image, it seems that software attempt does not work and you have to manually depress the unit's RESET button to force a RESET. Remember, if you want to see the boot's posting, you should have your terminal ready to run "screen" so you capture the initial postings after you click RESET.
|
C) To upload new binary image: depress the BOOT button and hold down, then while holding down the BOOT button disconnect the USB line and then reconnect the USB line and then lift up on the BOOT button. This sequence will cause the unit to await an upload of an image over USB to be stored in its FLASH memory. You can place the unit in this mode and then several minutes later perform the upload from your console, the unit will duly await an upload session. See further how to flash a new image in the section "Flashing Binary". Note: although the uploader may state it is trying to reset after successful installation of the new image, it seems that software attempt does not work and you have to manually depress the unit's RESET button to force a RESET. Remember, if you want to see the boot's posting, you should have your terminal ready to run "screen" so you capture the initial postings after you click RESET.
|
||||||
|
# Button Protocol For Uploading New Image
|
||||||
|
Pushing BOOT when the unit is currently running will not do anything. You have to shut the unit down first. So:
|
||||||
|
1) Depress POWER button for 6 seconds
|
||||||
|
2) Depress BOOT button and while depressed, click RESET button
|
||||||
|
3) Lift up BOOT button
|
||||||
|
The unit is now waiting for the serial console so commence an upload. You are not time restricted, so you can go through the above 3 steps and then 5 minutes later proceed with an upload in the command console.
|
||||||
|
|
||||||
# Exercises
|
# Exercises
|
||||||
These are progressve tests you can run to confirm how to access the unit's functionality and validate your workbench.
|
These are progressve tests you can run to confirm how to access the unit's functionality and validate your workbench. Each exercise has it's own README.md
|
||||||
|
|
||||||
Exercise 00: 00_usb_radio_check
|
Exercise 00: 00_usb_radio_check
|
||||||
|
|
||||||
Exercise 01: ASCII ping-pong over LoRa (serial only)
|
Exercise 01: ASCII ping-pong over LoRa (serial only). Using two units, each sends a message to the other and you monitor both units through two screen consoles.
|
||||||
|
|
||||||
Exercise 02: Add OLED display
|
Exercise 02: Add OLED display
|
||||||
|
|
||||||
|
|
@ -74,3 +81,8 @@ shared board pin headers in one place:
|
||||||
boards/
|
boards/
|
||||||
tbeam-s3-core_pins.h
|
tbeam-s3-core_pins.h
|
||||||
|
|
||||||
|
# Features
|
||||||
|
## SD Card
|
||||||
|
You can read and write to SD cards that are FAT formatted. (Can they be Linux formatted?)
|
||||||
|
## Real Time Clock ("RTC")
|
||||||
|
## Microphone
|
||||||
|
|
|
||||||
1
external/microReticulum_Firmware
vendored
Submodule
1
external/microReticulum_Firmware
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 5dc607fc7227c46ccb19244e456782fbb7775eae
|
||||||
27
firmware/fieldtest_beacon/platformio.ini
Normal file
27
firmware/fieldtest_beacon/platformio.ini
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
; platformio.ini
|
||||||
|
; 20260212 ChatGPT
|
||||||
|
; $Id$
|
||||||
|
; $HeadURL$
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = tbeam_supreme
|
||||||
|
|
||||||
|
[env:tbeam_supreme]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1 ; <-- change to your exact board if needed
|
||||||
|
monitor_speed = 115200
|
||||||
|
|
||||||
|
; Pull in microReticulum from your repo tree
|
||||||
|
build_flags =
|
||||||
|
-I ../../external/microReticulum/src
|
||||||
|
-I ../../external/microReticulum_Firmware
|
||||||
|
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||||
|
-D FIELDTEST_BEACON=1
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
bblanchon/ArduinoJson@~7.4.2
|
||||||
|
hideakitai/MsgPack@~0.4.2
|
||||||
|
rweather/Crypto@^0.4.0
|
||||||
|
; SD stack usually comes with Arduino core
|
||||||
|
; Add your LoRa radio library here (RadioLib, SX126x-Arduino, etc.)
|
||||||
117
firmware/fieldtest_beacon/src/main.cpp
Normal file
117
firmware/fieldtest_beacon/src/main.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
// firmware/fieldtest_beacon/src/main.cpp
|
||||||
|
// 20260212 ChatGPT
|
||||||
|
// $Id$
|
||||||
|
// $HeadURL$
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
#include <SD.h>
|
||||||
|
#include "Boards.h"
|
||||||
|
|
||||||
|
// Include microReticulum headers from your external tree
|
||||||
|
#include "Identity.h"
|
||||||
|
#include "Reticulum.h"
|
||||||
|
#include "Destination.h"
|
||||||
|
#include "Transport.h"
|
||||||
|
|
||||||
|
// ---------- User-tunable ----------
|
||||||
|
static const uint32_t BEACON_INTERVAL_MS = 15000;
|
||||||
|
static const char* PATH_IDENTITY_BIN = "/provisioning/identity.bin";
|
||||||
|
static const char* PATH_LABEL_TXT = "/provisioning/label.txt";
|
||||||
|
static const char* LOG_PATH = "/logs/fieldtest.log";
|
||||||
|
|
||||||
|
// ---------- Globals ----------
|
||||||
|
static uint32_t g_iter = 0;
|
||||||
|
static uint32_t g_next_tx = 0;
|
||||||
|
static File g_log;
|
||||||
|
|
||||||
|
// Source board pin mapping from microReticulum_Firmware board definitions.
|
||||||
|
static const int SD_CS_PIN = SD_CS;
|
||||||
|
|
||||||
|
// Simple line logger (append-only)
|
||||||
|
static void log_line(const String& line) {
|
||||||
|
if (!g_log) return;
|
||||||
|
g_log.println(line);
|
||||||
|
g_log.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read whole file into a buffer
|
||||||
|
static bool read_file(const char* path, std::vector<uint8_t>& out) {
|
||||||
|
File f = SD.open(path, FILE_READ);
|
||||||
|
if (!f) return false;
|
||||||
|
size_t n = f.size();
|
||||||
|
out.resize(n);
|
||||||
|
if (n > 0) f.read(out.data(), n);
|
||||||
|
f.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String read_text_file(const char* path) {
|
||||||
|
File f = SD.open(path, FILE_READ);
|
||||||
|
if (!f) return String("");
|
||||||
|
String s = f.readString();
|
||||||
|
f.close();
|
||||||
|
s.trim();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(250);
|
||||||
|
|
||||||
|
// ---- SD init ----
|
||||||
|
if (!SD.begin(SD_CS_PIN)) {
|
||||||
|
Serial.println("SD init failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_log = SD.open(LOG_PATH, FILE_APPEND);
|
||||||
|
if (!g_log) {
|
||||||
|
Serial.println("Failed to open log file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const String label = read_text_file(PATH_LABEL_TXT);
|
||||||
|
log_line("BOOT\tlabel=" + label);
|
||||||
|
|
||||||
|
std::vector<uint8_t> id_bytes;
|
||||||
|
if (!read_file(PATH_IDENTITY_BIN, id_bytes)) {
|
||||||
|
log_line(String("ERROR\tmissing_identity\tpath=") + PATH_IDENTITY_BIN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log_line(String("IDENTITY_OK\tbytes=") + id_bytes.size());
|
||||||
|
|
||||||
|
// ---- Load Identity into microReticulum ----
|
||||||
|
// TODO: adjust to the actual API in your microReticulum version
|
||||||
|
// Example intent:
|
||||||
|
// Identity ident = Identity::fromBytes(id_bytes.data(), id_bytes.size());
|
||||||
|
// Reticulum rns;
|
||||||
|
// rns.setIdentity(ident);
|
||||||
|
// rns.begin(...);
|
||||||
|
//
|
||||||
|
// Also: initialize your LoRa Interface and attach it to Reticulum here.
|
||||||
|
|
||||||
|
log_line("RNS_INIT_OK");
|
||||||
|
|
||||||
|
g_next_tx = millis() + 3000; // wait a moment before first TX
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
// TODO: run microReticulum polling / tick function, and radio interface polling
|
||||||
|
|
||||||
|
const uint32_t now = millis();
|
||||||
|
if ((int32_t)(now - g_next_tx) >= 0) {
|
||||||
|
g_next_tx = now + BEACON_INTERVAL_MS;
|
||||||
|
|
||||||
|
// Build payload: timestamp + iterator (GPS later)
|
||||||
|
const uint32_t epoch_guess = (uint32_t) (time(nullptr)); // placeholder; GPS later
|
||||||
|
const String payload = String("t=") + epoch_guess + " i=" + g_iter++;
|
||||||
|
|
||||||
|
// TODO: send to peer(s) (Destination derived from peers list)
|
||||||
|
// bool ok = destination.send(payload_bytes, len);
|
||||||
|
|
||||||
|
log_line(String("TX\t") + payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(5);
|
||||||
|
}
|
||||||
71
shared/boards/tbeam_supreme_adapter.h
Normal file
71
shared/boards/tbeam_supreme_adapter.h
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <XPowersLib.h>
|
||||||
|
#include "Boards.h"
|
||||||
|
|
||||||
|
namespace tbeam_supreme {
|
||||||
|
|
||||||
|
inline int i2cSda() { return I2C_SDA; }
|
||||||
|
inline int i2cScl() { return I2C_SCL; }
|
||||||
|
inline int sdSck() { return SD_CLK; }
|
||||||
|
inline int sdMiso() { return SD_MISO; }
|
||||||
|
inline int sdMosi() { return SD_MOSI; }
|
||||||
|
inline int sdCs() { return SD_CS; }
|
||||||
|
inline int imuCs() { return IMU_CS; }
|
||||||
|
|
||||||
|
inline bool initPmuForPeripherals(XPowersLibInterface*& pmu, Print* out = nullptr) {
|
||||||
|
if (BOARD_MODEL != BOARD_TBEAM_S_V1) {
|
||||||
|
if (out) out->println("PMU adapter: BOARD_MODEL is not T-Beam Supreme");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wire1.begin(i2cSda(), i2cScl());
|
||||||
|
|
||||||
|
if (!pmu) {
|
||||||
|
pmu = new XPowersAXP2101(Wire1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pmu->init()) {
|
||||||
|
if (out) out->println("PMU adapter: AXP2101 init failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match microReticulum_Firmware tbeam supreme rail setup.
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_ALDO4);
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_ALDO3);
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_DCDC3, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_DCDC3);
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_ALDO2, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_ALDO2);
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_ALDO1, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_ALDO1);
|
||||||
|
pmu->setPowerChannelVoltage(XPOWERS_BLDO1, 3300);
|
||||||
|
pmu->enablePowerOutput(XPOWERS_BLDO1);
|
||||||
|
|
||||||
|
pmu->disablePowerOutput(XPOWERS_DCDC2);
|
||||||
|
pmu->disablePowerOutput(XPOWERS_DCDC5);
|
||||||
|
pmu->disablePowerOutput(XPOWERS_DLDO1);
|
||||||
|
pmu->disablePowerOutput(XPOWERS_DLDO2);
|
||||||
|
pmu->disablePowerOutput(XPOWERS_VBACKUP);
|
||||||
|
|
||||||
|
pmu->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||||
|
pmu->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||||
|
pmu->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||||
|
pmu->clearIrqStatus();
|
||||||
|
pmu->disableTSPinMeasure();
|
||||||
|
pmu->enableVbusVoltageMeasure();
|
||||||
|
pmu->enableBattVoltageMeasure();
|
||||||
|
|
||||||
|
if (out) {
|
||||||
|
out->printf("PMU adapter: AXP2101 ready, BLDO1(SD)=%s\r\n",
|
||||||
|
pmu->isPowerChannelEnable(XPOWERS_BLDO1) ? "ON" : "OFF");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pmu->isPowerChannelEnable(XPOWERS_BLDO1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace tbeam_supreme
|
||||||
Loading…
Add table
Add a link
Reference in a new issue