Note
IMUTask and IMU-related functionality is not fully implemented; the device does not currently use an IMU to determine reticle position.
A modular, real-time weapon optic system built on an ESP32, using Arduino and FreeRTOS, plus some libraries from Adafruit.
The device reads from a UART rangefinder (TOFFuture XT-S1e), an I2C IMU, and displays a reticle on an SSD1306 OLED Display. The system is based on a system of producer and consumer tasks which handle sensor fusion and device communication.
-
FreeRTOS Tasks
- RangefinderTask (UART Communication)
- IMUTask (I2C Communication with IMU)
- FusionTask (sensor fusion)
- DisplayTask (SSD1306 Communicatino)
- SerialDebugTask
-
Modular structure
-
Timestamped sensor data using
xTaskGetTickCount() -
Queue-based communication between tasks
-
Reticle controller supporting:
- Standard mode (single reticle)
- Shotgun mode (reticle circle expands/shrinks based on distance)
-
ESP32 + Arduino SDK build compatibility (Arduino IDE or CLI)
/main
├── DisplayTask.cpp / .hpp
├── FusionTask.cpp / .hpp
├── IMUTask.cpp / .hpp
├── RangefinderTask.cpp / .hpp
├── ReticleController.cpp / .hpp
├── main.cpp (or main.ino entry point)
└── main.hpp
/libraries
├── Adafruit_BusIO
├── Adafruit_GFX_Library
├── Adafruit_SSD1306
Each task, aside from the SerialDebug task, is implemented in its own .cpp/.hpp pair for better separation.
| Task | Function |
|---|---|
| RangefinderTask | Reads UART buffer from Rangefinder and sends to queue |
| IMUTask | Reads IMU data and sends to queue |
| FusionTask | Synchronizes sensor timestamps and computes reticle position |
| DisplayTask | Draws reticle using Adadfruit SSD1306 driver |
| SerialDebugTask | Consumes from debug message queue and prints over serial |
- Tasks communicate via FreeRTOS queues.
- All sensor data includes a value and a timestamp
- FusionTask performs loose synchronization based on timestamps.
-
Arduino IDE
-
Arduino ESP32 board support package
-
Libraries:
- Adafruit_GFX
- Adafruit_SSD1306
- Adafruit_BusIO
Use the Arduino IDE to compile / upload the project
The default pin assignments for this project are as follows:
-
Rangefinder:
#define RNG_TXD 13 // D13 #define RNG_RXD 12 // D12
-
SSD1306 OLED: default Arduino "Wire" pins (SDA, SCL)
The XT-S1e supports multiple refresh rate options, but selecting between them is currently not possible. More to come
- Normal Mode: floating square reticle (vertical position based on distance)
- Shotgun Mode: expanding circle (size based on distance)
This project is designed to be testable:
Sensor parsers are pure C++ classes, and queues can be filled with mock data to test functionality. However, no unit testing scripts are available at this time.
It is best to test the device by uploading it onto the target device and visually confirming functionality
The optics aspect of the project was definitely the most difficult and I spent many hours simulating, learning about optics, and researching components that I could reasonably source. At least 75% of my time went into the optics, which was probably around 30-35 hours just for designing and researching.
Optics is a field I have been interested in for a while but one that I don't have any experience in. The majority of my time was spent just reading and learning (hyperphysics was a great resource for the subject).
Finding a good rangefinder module was also a challenge, but I was able to find one for around 30-45 dollars with decent range (30m). Alternative well-known devices such as ones from Garmin cost over $120 for a similar device with 40m range, so I spent a lot of time trying to find a comparable device for less.
- Fully implement IMU processing
- More advanced sensor fusion that takes data timestamp into account
- Add configuration buttons
- Selectable rangefinder refresh rate
- Configuration system (queue based)
- Add persistent settings (NVS)

