GitHub - El-EnderJ/NeoCalculator: This project is a Graphing Scientific Calculator based on the ESP32 microcontroller, designed to compete with commercial models (Casio fx-991EX, TI-84) by means of a color TFT screen, natural display (correctly rendered mathematical formulas) and a modular application architecture.
Pangram verdict · v3.3
We believe that this document is a mix of AI-generated, AI-assisted, and human-written content
AI likelihood · overall
MixedArticle text · 1,354 words · 7 segments analyzed
ImportantTechnical Status: Major Architectural Refactor We are currently migrating the core math engine to Giac and implementing STIX Two Math for LaTeX-quality rendering. If you are compiling from main, you might experience UI glitches. For a stable experience, please check the Releases section.
Table of Contents
Webpage What is NumOS? Key Features System Architecture CAS Engine Hardware Quick Start User Manual — EquationsApp Project Structure Build Stats Critical Hardware Fixes Project Status Technology Stack Comparison with Commercial Calculators Documentation Contributing
What is NumOS? NumOS is an open-source scientific and graphing calculator operating system built on the ESP32-S3 N16R8 microcontroller (16 MB Flash QIO + 8 MB PSRAM OPI). The project aims to become the best open-source calculator in the world, rivalling the Casio fx-991EX ClassWiz, the NumWorks, the TI-84 Plus CE, and the HP Prime G2. NumOS delivers:
Giac-backed CAS Engine — Symbolic math now runs through Giac C++ via src/math/giac/GiacBridge.cpp (The Big Switch). Legacy CAS-S3 modules remain documented as historical milestones and optional local tooling. Natural Display V.P.A.M. — Formulae rendered as they appear on paper: real stacked fractions, radical symbols (√), genuine superscripts, 2D navigation with a structural smart cursor. Modern LVGL 9.x Interface — Smooth transitions, animated splash screen, NumWorks-style launcher. Recent launcher refactor: the launcher now uses LVGL Flex ROW_WRAP (dynamic rows) with fixed card sizing instead of a static grid descriptor. See docs/UI_CHANGES.md for developer migration notes and docs/fluid2d_plan.md for an example app (Fluid2D) integrated into the new APPS[] schema. Custom Numeric Math Engine — Complete pipeline: Tokenizer → Shunting-Yard Parser → RPN Evaluator + Visual AST, implemented from scratch in C++17. Modular App Architecture — Each application is a self-contained module with explicit lifecycle (begin/end/load/handleKey), orchestrated by SystemApp.
Key Features
Feature Description
Giac CAS Backend Symbolic evaluation through GiacBridge with UART parser/eval flow validated on hardware. Migration milestones completed: -DDOUBLEVAL, 64 KB loop stack, real-style complex_mode(false) with preserved i^2=-1 behavior
Unified Calculus App Symbolic $d/dx$ differentiation (17 rules) and numerical/symbolic $\int dx$ integration (Slagle heuristic: table lookup, linearity, u-substitution, integration by parts/LIATE), tab-based mode switching, automatic simplification, and detailed step-by-step output
EquationsApp Solves linear, quadratic, and 2×2 systems (linear + non-linear via Sylvester resultant) with full step-by-step display
Bridge Designer Real-time structural bridge simulator with Verlet integration physics, stress analysis (green→red beam visualisation), snap-to-grid editor, wood/steel/cable materials, and truck/car load testing — PSRAM-backed, 60 Hz fixed timestep
Particle Lab Powder-Toy-class sandbox: 30+ materials (Sand, Water, Lava, LN2, Wire, Iron, Titan, C4, Clone), spark electronics with Joule heating, phase transitions, reaction matrix (Water+Lava=Stone+Steam), Bresenham line tool, material palette overlay, LittleFS save/load
Settings App System-wide toggles for complex number output (ON/OFF), decimal precision selector (6/8/10/12 digits), and angle-mode display
Natural Display Real fractions, radicals, exponents, 2D cursors — mathematical rendering as it appears on paper
Graphing: y=f(x) Real-time function plotter with zoom, pan, and value table
85+ CAS Unit Tests Comprehensive test suite for the CAS, enable/disable via compile-time flag
PSRAMAllocator CAS uses PSRAMAllocator<T> to isolate memory usage in the 8 MB PSRAM OPI
Variables A–Z + Ans Persistent storage via LittleFS — 216 bytes in /vars.dat
SerialBridge Full calculator control from PC via Serial Monitor without physical
hardware
SerialBridge Debug Immediate byte echo, 5-second heartbeat, 8-event circular buffer
Photo gallery Neural Network Simulator:
Fluid 2D Simulator:
Periodic Table (Chemistry App):
Grapher App:
Steps (Equations App) (WIP, still in development, Alpha):
Calculus App:
Probability (Gaussian Distribution):
Python App:
Bridge Designer:
Circuit Simulator (Circuit Core, Alpha):
Particle Lab (Powder Toy like):
Optics Lab:
System Architecture
flowchart TB subgraph esp[ESP32-S3 N16R8] main["main.cpp: setup() → PSRAM, TFT, LVGL, Splash, SystemApp; loop(): lv_timer_handler(), app.update(), serial.poll()"] system["SystemApp (Dispatcher)"] main --> system end
subgraph apps[Applications] mm["MainMenu (LVGL)"] calc["CalculationApp (Natural VPAM, History)"] grapher["GrapherApp (y=f(x), Zoom & Pan)"] eq["EquationsApp (CAS)"] calculus["CalculusApp (d/dx, ∫dx)"] settings["SettingsApp"] end
system --> mm system --> calc system --> grapher system --> eq system --> calculus system --> settings
math["Math Engine: Tokenizer · Parser · Evaluator · ExprNode · VariableContext · EquationSolver"] procas["CAS Engine: CASInt · CASRational · SymExpr DAG · SymSimplify · SymDiff · SymIntegrate"]
system --> math math
--> procas
display["Display Layer: DisplayDriver · LVGL flush DMA · ILI9341 @ 10 MHz"] input["Input Layer: KeyMatrix 5x10 · SerialBridge · LvglKeypad · LittleFS"]
system --> display system --> input
display -->|SPI @ 10 MHz| ili["ILI9341 IPS 3.2 in — 320x240 · 16 bpp"] ili -.-> esp
Loading
CAS Engine The CAS (Computer Algebra System) now uses Giac C++ as the canonical symbolic backend. The migration is routed through src/math/giac/GiacBridge.cpp and consumed by the UART command path in src/input/SerialBridge.cpp. Legacy CAS-S3 internals documented below remain as historical milestones and optional local components, but symbolic truth for current backend flows comes from Giac. Giac Migration Milestones
Big Switch complete: custom symbolic backend replaced by Giac as canonical CAS. Embedded numeric stabilization complete with -DDOUBLEVAL. Stack stabilization complete with -DARDUINO_LOOP_STACK_SIZE=65536. Real-style defaults complete: complex_mode(false) and preserved imaginary unit behavior (i^2 = -1). UART command path certified on hardware for sum, int, solve, and simplify.
CAS Pipeline (Derivatives)
flowchart TB user["User input (CalculusApp): x^3 + sin(x)"] user --> me["Math Engine: Parser + Tokenizer"] me --> af["ASTFlattener: MathAST → SymExpr DAG"] af --> sd["SymDiff → d/dx: 3x^2 + cos(x)"] sd --> ss["SymSimplify (8-pass fixed-point)"] ss --> sea["SymExprToAST: SymExpr →
MathAST (Natural Display)"] sea --> canvas["MathCanvas renders: 3x^2 + cos(x)"]
Loading
CAS Pipeline (Integrals)
flowchart TB userInt["User input (CalculusApp, ∫dx mode): x · cos(x)"] userInt --> af2["ASTFlattener → SymExpr DAG"] af2 --> sint["SymIntegrate (Slagle): table → linearity → u-sub → parts (LIATE)"] sint --> ss2["SymSimplify"] ss2 --> conv["SymExprToAST::convertIntegral()"] conv --> canvas2["MathCanvas renders: x·sin(x) + cos(x) + C"]
Loading
CAS Components
Module File Responsibility
CASInt cas/CASInt.h Hybrid BigInt: int64_t fast-path + mbedtls_mpi on overflow
CASRational cas/CASRational.h/.cpp Overflow-safe exact fraction (num/den with auto-GCD)
PSRAMAllocator<T> cas/PSRAMAllocator.h STL allocator → ps_malloc/ps_free for PSRAM
SymExpr DAG cas/SymExpr.h/.cpp Immutable symbolic tree with hash (_hash) and weight (_weight)
ConsTable cas/ConsTable.h PSRAM hash-consing table: deduplication of identical nodes
SymExprArena cas/SymExprArena.h PSRAM bump allocator (16 blocks × 64 KB) + integrated ConsTable
ASTFlattener cas/ASTFlattener.h/.cpp MathAST (VPAM) → SymExpr DAG with hash-consing
SymDiff cas/SymDiff.h/.cpp Symbolic differentiation: 17 rules (chain, product, quotient, trig, exp, log)
SymIntegrate
cas/SymIntegrate.h/.cpp Slagle integration: table, linearity, u-substitution, parts (LIATE)
SymSimplify cas/SymSimplify.h/.cpp Multi-pass simplifier (8 iterations, fixed-point, trig/log/exp)
SymPoly cas/SymPoly.h/.cpp Univariable symbolic polynomial with CASRational coefficients
SymPolyMulti cas/SymPolyMulti.h/.cpp Multivariable polynomial + Sylvester resultant
SingleSolver cas/SingleSolver.h/.cpp Single-variable equation: linear / quadratic / Newton-Raphson
SystemSolver cas/SystemSolver.h/.cpp 2×2 system: Gaussian elimination + non-linear (resultant)
OmniSolver cas/OmniSolver.h/.cpp Analytic variable isolation: inverses, roots, trig
HybridNewton cas/HybridNewton.h/.cpp Newton-Raphson with symbolic Jacobian and 16-seed multi-start
CASStepLogger cas/CASStepLogger.h/.cpp StepVec in PSRAM — detailed steps (INFO/FORMULA/RESULT/ERROR)
SymToAST cas/SymToAST.h/.cpp Bridge: SolveResult → MathAST Natural Display
SymExprToAST cas/SymExprToAST.h/.cpp Bridge: SymExpr → MathAST. Includes convertIntegral() (+C)
CAS Tests — 53 Unit Tests
Phase Tests Coverage
A — Foundations 1–18 Rational: add, subtract, multiply, divide, simplification. SymPoly: arithmetic, derivation, normalisation.
B — ASTFlattener 19–32 AST→SymPoly conversion for simple polynomials, constants, trig functions, powers.
C — SingleSolver 33–44 Linear (single solution), quadratic (2 real roots, repeated root, negative discriminant), steps.
D — SystemSolver 45–53 2×2 determined system, indeterminate (infinite solutions), incompatible system.
# platformio.ini — enable tests: build_flags = ... -DCAS_RUN_TESTS build_src_filter = +<*> +<../tests/CASTest.cpp>
Hardware
Component Specification
MCU ESP32-S3 N16R8 CAM — Dual-core Xtensa LX7 @ 240 MHz
Flash 16 MB QIO (default_16MB.csv)
PSRAM 8 MB OPI (qio_opi — critical to prevent boot panic)
Display ILI9341 IPS TFT 3.2" — 320×240 px — SPI @ 10 MHz (verified)
SPI Bus FSPI (SPI2): MOSI=13, SCLK=12, CS=10, DC=4, RST=5
Backlight GPIO 45 — hardwired to 3.3V (pinMode(45, INPUT))
Keyboard 5×10 matrix (Phase 7) — Rows OUTPUT: GPIO 1,2,41,42,40 · Cols INPUT_PULLUP: GPIO 6,7,8…
Storage LittleFS on dedicated partition — persistent A–Z variables
USB Native USB-CDC on S3 — 115 200 baud
Full Pinout ILI9341 Display
Signal GPIO Notes
MOSI 13 FSPI Data In
SCLK 12 FSPI Clock
CS 10 Chip Select (active LOW)
DC 4 Data/Command
RST 5 Reset
BL 45 Hardwired to 3.3V — always INPUT
5×10 Keyboard Matrix (driver Keyboard, Phase 7)
Row GPIO Role Column GPIO Role
ROW 0 1 OUTPUT COL 0 6 INPUT_PULLUP
ROW 1 2 OUTPUT COL 1 7 INPUT_PULLUP
ROW 2 41 OUTPUT COL 2 8 INPUT_PULLUP
ROW 3 42 OUTPUT COL 3–9 3,15,16,17,18,21,47