TTGO T-Watch 2019
March 4, 2026 · View on GitHub
Author: Libor Tomsik, OK1CHP
License: GNU General Public License v3.0 (see LICENSE)
Inspired by: rdz_ttgo_sonde by DL9RDZ
AI assistance: Coded with help from Claude Opus (Anthropic)
Firmware for the TTGO T-Watch 2019 (ESP32 + touchscreen + Acsip S78G module) that receives and decodes Vaisala RS41 radiosondes, shows live sonde position, bearing, elevation, distance, landing prediction, and your own GPS position — all on a compact wristwatch display.
Screenshots
| Home screen | Sonde detail + compass | Landing predictor |
|---|---|---|
![]() | ![]() | ![]() |
Hardware
| Component | Details |
|---|---|
| ESP32 | TTGO T-Watch 2019 (/dev/ttyUSB0) |
| Display | 240×240 ST7789, touch |
| Power | AXP202 PMIC |
| RTC | PCF8563 |
| Motion | BMA423 |
| RF/GPS module | Acsip S78G (STM32L073 + SX1278 + Sony CXD5603GF GPS) |
The S78G module is a self-contained sub-module. It contains:
- STM32L073RZ (Cortex-M0+, 192 KB flash, 20 KB RAM) — the controller
- SX1278 (Semtech LoRa/FSK, 137–525 MHz) — the radio
- Sony CXD5603GF — GPS receiver (1.8 V I/O, NMEA @ 9600 baud)
The ESP32 talks to the S78G over UART at 115200 baud (ESP32 pins GPIO34/33 via the watch backplane connector).
Project structure
my_watch/
├── platformio.ini # ESP32 project (TTGO T-Watch board)
├── src/
│ └── main.cpp # ESP32 firmware (display, GPS, RS41 decode)
└── stm32_fw/
├── platformio.ini # STM32 project (board: acsip_s76s)
└── src/
└── main.cpp # STM32 firmware (GPS passthrough + SX1278 FSK)
UI — Five screens (cycle with button, long-press for settings)
| Screen | Content |
|---|---|
| 0 — Home | Time (large), GPS coordinates + sat count, sonde overview (bearing, distance, elevation), battery |
| 1 — FSK Control | Frequency selector (touch ±MHz buttons), START/STOP RX button, RSSI + sonde ID |
| 2 — Sonde Detail | Full sonde data (lat/lon/alt/sats/bearing/elevation/distance/RSSI/temp) + compass rose |
| 3 — Landing Predictor | Large distance (km), altitude (m), descent rate (m/s), ETA to landing |
| 4 — Settings | Brightness control (long-press button to enter) |
Firmware overview
ESP32 firmware (src/main.cpp) — flashed and working
- Live clock (font 7, 48 px), date, weekday — synced from GPS/NMEA
- GPS: lat/lon, satellite count, fix status parsed from NMEA
- RS41 ECEF→LLA decode, RSSI, temperature, descent-rate ring buffer
- 5-screen touch UI (see above)
- NVS persistence: frequency, last sonde ID + coordinates survive power-off
- Bluetooth SPP console — watch advertises as "T-Watch"; connect any BT serial terminal
- Serial console (115200 baud USB): same commands mirrored to both
STM32 firmware (stm32_fw/src/main.cpp) — flashed and working
Replaces the stock Acsip firmware:
- Backward-compatible Acsip command subset (
sip get_hw_model,gps get_data dd, …) rf fsk_start <freq_hz>— configures SX1278 for RS41 FSK and starts receivingrf fsk_stop— return to standby- Autonomously sends decoded frames to ESP32:
\n\r>> rs41_rx <624 hex> <rssi_dBm>\n - Sony CXD5603GF GPS via LPUART1 (PC10/PC11), output in Acsip DD format including time
- Auto GPS re-init watchdog: if no NMEA bytes for 30 s, re-runs
initGPS()
Serial / Bluetooth console commands
Connect via USB at 115200 baud or over Bluetooth SPP (device name: T-Watch):
help / h / ? list all commands
status / s show GPS, battery, sonde state
fskstart [MHz] start RS41 FSK receive (e.g. fskstart 403.0)
fskstop stop FSK receive
txcarrier [MHz] transmit CW carrier for frequency verification
gpsraw [sec] dump raw NMEA from GPS for N seconds (default 30)
gpsdiag diagnose GPS pins and baud rates
time HH:MM:SS set RTC time manually
date YYYY-MM-DD set RTC date manually
bridge raw serial bridge to S7XG
coldbridge power-cycle S7XG then raw bridge
raw <cmd> send raw command to S7XG (e.g. raw rf rssi)
reboot restart ESP32
| Parameter | Value |
|---|---|
| Frequency | 400.15 – 406.0 MHz (configurable per sonde) |
| Modulation | 2-FSK |
| Bitrate | 4800 bps |
| Deviation | ±4.8 kHz |
| Frame | 8-byte sync + 312 bytes |
| Sync word | 10 B6 CA 11 22 96 12 F8 |
| Scrambling | XOR with 64-byte repeating LFSR mask |
| Error check | CRC-16/CCITT-FALSE per subframe block |
Decoded data blocks:
0x79Status block: sonde serial number (8 ASCII chars), frame counter0x76GPS block: lat/lon/alt (IEEE 754 float LE), satellite count, velocity0x74PTU block: temperature (int24 LE, /100 → °C)
Typical sonde frequencies for Czech Republic / Central Europe: 403–405 MHz
Check https://radiosondy.info or https://sondehub.org for active sondes near you.
Flashing the STM32 (the important step)
What you need
- ST-LINK V2 programmer (USB dongle or clone)
- Soldering iron + thin wire to reach S78G PCB pads
- PlatformIO installed (
pip install platformio)
SWD pad locations on the S78G PCB
The pads are on the S78G sub-module board (you may need to partially disassemble the watch to access them):
| Signal | STM32 pin | S78G PCB pad |
|---|---|---|
| SWDIO | PA13 | pin 50 |
| SWCLK | PA14 | pin 51 |
| GND | GND | GND (any) |
| VCC | — | 3.3 V (from watch, leave watch powered) |
| NRST | NRST | pin 4 (optional, helps if chip is locked) |
Tip: the S78G module has castellated edge pads. The SWD signals are on the bottom row. A fine-tip iron and 0.1 mm wire-wrap wire works well.
Unlock flash (if needed)
If the STM32 has read-protection enabled (RDP level 1), you must unlock it first. This will erase the existing Acsip firmware:
# Using STM32CubeProgrammer CLI:
STM32_Programmer_CLI -c port=SWD -ob RDP=0xAA
# Or with OpenOCD:
openocd -f interface/stlink.cfg -f target/stm32l0.cfg \
-c "init; reset halt; stm32l0x unlock 0; reset; exit"
Build and flash
cd my_watch/stm32_fw
pio run -t upload
PlatformIO uses the ST-LINK automatically. If it doesn't find your programmer:
pio run -t upload --upload-port /dev/ttyACM0 # adjust as needed
Verify it worked by opening pio device monitor in the ESP32 project — you
should see [S7XG] FW: 1.0.0-rs41 instead of 1.6.5-g9.
GPS level-shift pin (may need adjustment)
The Sony CXD5603GF operates at 1.8 V logic. A level-shifter IC on the S78G PCB converts between 3.3 V (STM32) and 1.8 V (Sony). The enable pin is controlled by a STM32 GPIO.
Current setting in stm32_fw/src/main.cpp:
#define GPS_LEVEL_SHIFT PA8 // change if GPS is silent after flashing
If GPS never produces NMEA data, try PB0, PB1, or PA15 instead, rebuild
and reflash. The correct pin is not in any public schematic.
How to use RS41 tracking
- Flash STM32 firmware (above)
- Open serial terminal on ESP32 at 115200 baud
- Type
fskstart 403.0(or whatever frequency the sonde is on) - The SONDE section on the display will update live:
- Bearing: compass direction from watch to sonde (N/NE/E/SE/S/SW/W/NW + degrees)
- Elevation: angle above horizon in degrees
- Distance: slant range in km
- Altitude: sonde altitude in metres
- Sats: GPS satellites tracked by sonde
- RSSI: received signal strength
- Temp: air temperature at sonde altitude
- Type
fskstopwhen done
Finding the frequency: RS41 sondes transmit on 400.15–406 MHz. Use https://radiosondy.info, the SondeHub Tracker app, or a wideband SDR scan to find the exact frequency of sondes in your area.
Pin reference (ESP32 ↔ S78G)
These are on the T-Watch 2019 backplane — no soldering needed for ESP32 side:
| ESP32 GPIO | S78G signal | Description |
|---|---|---|
| 34 (RX) | TXD | ESP32 receives from S78G |
| 33 (TX) | RXD | ESP32 sends to S78G |
Defined in platformio.ini as GPS_RX / GPS_TX via the TTGO TWatch Library.
Known limitations / TODOs
- Watch altitude not used in elevation/distance calculation (assumed ~sea level) — small error for distant sondes
- RS41 PTU temperature offset calibration not applied (raw sensor value; reported values may be off)
- GPS does not produce NMEA data while the ST-LINK programmer is physically connected (hardware limitation of the S78G SiP)
- Bearing accuracy depends on watch having its own GPS fix; shows "bearing may be wrong" warning otherwise
Dependencies
ESP32 project
xinyuan-lilygo/TTGO TWatch Library @ 1.4.2lewisxhe/Acsip S7xG Library @ 1.0.0
STM32 project
mikalhart/TinyGPSPlus @ ^1.0.3- PlatformIO board:
acsip_s76s(STM32L073RZ, 32 MHz, 192 KB flash) - Framework:
arduino(STM32duino) - SX1278 driven via bare-metal SPI register access (no external library)
License
This project is licensed under the GNU General Public License v3.0.
See the LICENSE file, or https://www.gnu.org/licenses/gpl-3.0.html
Copyright © 2026 Libor Tomsik, OK1CHP
Useful links
- rdz_ttgo_sonde — the project that inspired this; full-featured RS41 tracker for TTGO T-Beam/T-Watch
- SondeHub Tracker — live sonde map
- Radiosondy.info — Central/Eastern Europe sonde database
- rs1729/RS decoder — reference RS41 decoder (C)
- Zephyr S76S board — confirmed pin mappings for STM32↔SX1278
- Acsip S7xG Arduino lib — original ESP32 library


