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 screenSonde detail + compassLanding predictor
HomeSondeLanding

Hardware

ComponentDetails
ESP32TTGO T-Watch 2019 (/dev/ttyUSB0)
Display240×240 ST7789, touch
PowerAXP202 PMIC
RTCPCF8563
MotionBMA423
RF/GPS moduleAcsip 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)

ScreenContent
0 — HomeTime (large), GPS coordinates + sat count, sonde overview (bearing, distance, elevation), battery
1 — FSK ControlFrequency selector (touch ±MHz buttons), START/STOP RX button, RSSI + sonde ID
2 — Sonde DetailFull sonde data (lat/lon/alt/sats/bearing/elevation/distance/RSSI/temp) + compass rose
3 — Landing PredictorLarge distance (km), altitude (m), descent rate (m/s), ETA to landing
4 — SettingsBrightness 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 receiving
  • rf 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

ParameterValue
Frequency400.15 – 406.0 MHz (configurable per sonde)
Modulation2-FSK
Bitrate4800 bps
Deviation±4.8 kHz
Frame8-byte sync + 312 bytes
Sync word10 B6 CA 11 22 96 12 F8
ScramblingXOR with 64-byte repeating LFSR mask
Error checkCRC-16/CCITT-FALSE per subframe block

Decoded data blocks:

  • 0x79 Status block: sonde serial number (8 ASCII chars), frame counter
  • 0x76 GPS block: lat/lon/alt (IEEE 754 float LE), satellite count, velocity
  • 0x74 PTU 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):

SignalSTM32 pinS78G PCB pad
SWDIOPA13pin 50
SWCLKPA14pin 51
GNDGNDGND (any)
VCC3.3 V (from watch, leave watch powered)
NRSTNRSTpin 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

  1. Flash STM32 firmware (above)
  2. Open serial terminal on ESP32 at 115200 baud
  3. Type fskstart 403.0 (or whatever frequency the sonde is on)
  4. 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
  5. Type fskstop when 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 GPIOS78G signalDescription
34 (RX)TXDESP32 receives from S78G
33 (TX)RXDESP32 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.2
  • lewisxhe/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