Courses
Self-directed weekend courses aimed at one goal: becoming genuinely proficient, in both theory and practice, at digital signal processing for embedded software / firmware engineering. Course 1 is the mathematical foundation, worked by hand. Course 2 is the programming foundation: scientific Python, modern embedded C, and embedded Rust, taught to the level where the language stops being the problem and in the order the labs use them — Python first, then the tier’s compiled language. Course 3 is the bench: a fully lab-based course that spends that math and those languages building and measuring real-time DSP on real hardware; Course 1 is a mastered prerequisite for Course 3, its results applied there, never re-taught. Course 4 opens a second discipline on the same foundation: real-time rendering and GPU engineering — modern C++20, Vulkan, Metal, and CUDA — taught the same way, fully lab-based, with Course 1’s mathematics applied rather than re-taught and a profiler open on every lab.
Weekend rhythm: Saturday = reading + handwritten derivations (Course 1), a module’s exercises in Python, C, or Rust (Course 2), or breadboard build + first measurements (Course 3). Sunday = deeper measurement/firmware + analysis + a staff-level note reconciling predicted vs. measured.
Note on AI use: The Course 3 and Course 4 lab write-ups, the Course 2 lessons pages, and the Course 1 booklet’s teaching text are drafted with AI assistance so every page has a consistent structure. The substance is mine: every proof is worked by hand on paper first and converted to LaTeX/KaTeX using AI for typesetting only, and every circuit, measurement, and line of code is produced and debugged by me. The goal is to learn the material, which only happens by producing the proofs and building the labs myself.
Course 1 — Math Foundations for Signal Processing, Machine Learning, and Artificial Intelligence
A single self-contained booklet, Abridged Math Foundations for Signal Processing, Machine Learning, and Artificial Intelligence (192 pages): 51 lessons in 7 parts, dependency-ordered, the mathematical backbone under Course 3 and Course 4; the applied signal-processing layer lives as theory sections inside the Course 3 labs. Each lesson: theory with definitions, theorems, and proofs; practice by hand on small examples; exercises tagged [Proof] and [Hand], worked by hand; and a deeper-reading pointer into the full textbooks, whose worked problem sets accumulate on the Books page.
Part I · Lessons 1–7 — The Linear Algebra Core
Vectors and linear combinations; dot products, norms, and orthogonality; span, independence, basis, and dimension; matrices as linear maps; null space and rank; orthogonal projections and least squares; gradients.
Part II · Lessons 8–13 — Eigenstructure, the Spectral Theorem, the DFT, and the SVD
Complex vector spaces and complex exponentials; eigenvalues, eigenvectors, and diagonalization; self-adjoint operators and the spectral theorem; unitary matrices and the discrete Fourier transform; the singular value decomposition; determinants and trace.
Part III · Lessons 14–17 — The Probability Core
Probability models; counting — permutations, combinations, partitions; conditioning and Bayes’ rule; discrete and continuous random variables; expectation and variance; joint distributions and conditioning; the normal distribution.
Part IV · Lessons 18–23 — Random Vectors, Limit Theorems, and Stochastic Processes
Derived distributions, covariance, and correlation; conditional expectation and least-mean-squares estimation; random vectors and Gaussian vectors; limit theorems; the Bernoulli and Poisson processes; Markov chains.
Part V · Lessons 24–30 — The Signals-and-Systems Core
Signals and system properties; LTI systems and convolution; Fourier series; the continuous- and discrete-time Fourier transforms; frequency response and filtering; sampling and aliasing; Laplace and \(z\)-transforms.
Part VI · Lessons 31–43 — The Analysis Behind the Transforms
Sequences, series, and interchange of limits; metric spaces, continuity, and compactness; differentiation and the calculus theorems — the mean value theorem, L’Hôpital, Taylor — with the \(n\)-dimensional upgrade; the Riemann integral and the fundamental theorem of calculus; Lebesgue measure and integration and the swap theorems; Banach and Hilbert spaces, \(L^2\), and orthonormal bases; complex analysis — analytic functions, power series, and contour integrals, then zeros, poles, residues, and ROCs; the Fourier, Laplace, and \(z\)-transforms as operators, with the property tables proved; distributions and \(\delta\); dual spaces, Riesz representation, and functional derivatives; numerical linear algebra — conditioning, stability, factorizations, and iteration; transforms of distributions — moment generating and characteristic functions, the Chernoff bound, and the central limit theorem with proof.
Part VII · Lessons 44–51 — Convex Optimization and Information Theory for ML, Signals, and Sensors
Convex sets, functions, and problems; regularized fitting and inverse problems; duality, KKT conditions, and certificates; gradient, Newton, and proximal algorithms; entropy, KL divergence, and mutual information; the asymptotic equipartition property and lossless compression; hypothesis testing, channels, and sensor information; rate–distortion, compression, and representation learning.
Course 2 — Programming Foundations: Scientific Python, Modern Embedded C & Embedded Rust
The programming prerequisite for the labs: the three languages every page on this site is written in, taught with their libraries to the level where the language stops being the problem. The working rhythm is the one Course 3 assumes — an algorithm is written first in Python against NumPy and SciPy (or PyTorch), where the reference arrays come from; then it is implemented in C17/18 or Rust on the runtime tier it will live on — bare metal on the STM32, under FreeRTOS/RTIC/Embassy, or on embedded Linux on the Jetson — and checked against the Python version. From Module 4 on, every concept is taught once and shown in both C and Rust; everything is verified on the Mac (sanitizers, Miri, QEMU) with the boards as optional last rungs. Sources: the NumPy/SciPy/PyTorch documentation and the UvA notebooks Introduction to PyTorch; Seacord’s Effective C (2nd ed.) and the course’s Modern C for STM32 Firmware reference; The Rust Programming Language (3rd ed.), the Embedded Rust Book, and the RTIC and Embassy documentation; Mastering the FreeRTOS Real-Time Kernel; Grenning’s Test-Driven Development for Embedded C.
Part I · Modules 0–3 — Toolchains and Scientific Python
The toolchain for all three languages and the three runtime tiers, a first no_std binary booted in QEMU, and the availability matrix established by experiment. Then the numeric stack from the memory model up: ndarrays, strides, views and copies, broadcasting and vectorization, np.linalg/np.fft/np.random, dtype pitfalls, binary captures; the SciPy tour (signal, fft, linalg, optimize, stats), Matplotlib, pandas; the audio, image, and video libraries; statistics and scikit-learn; Numba and CuPy; the ctypes and PyO3 bridges and the assert_allclose arbiter discipline; and PyTorch — tensors, autograd, nn.Module, the training loop written by hand, quantization, ONNX export, and inference on the Jetson.
Part II · Modules 4–5 — The C Subset and the Rust Core
Modern C after K&R, pinned as C17/18: dialect and warnings as a contract, fixed-width types and promotions across LP64/ILP32, designated initializers and compound literals, static inline, restrict, compile-time contracts, _Generic, expressions and sequencing, preprocessor hygiene. Then Rust for a C programmer: ownership and borrowing, enums and exhaustive match, Result and the firmware panic policy, traits and monomorphization, lifetimes in driver APIs, iterators that compile to loops, const generics, integer overflow semantics, and the no_std idioms that follow.
Part III · Modules 6–8 — Memory and the Machine
Memory without a heap: storage durations, pools and arenas, heapless and StaticCell, layout and packing, stack budgets, DMA buffer ownership, mlockall on Linux. Undefined behavior and unsafe: the firmware UB list and how the optimizer exploits it, sanitizers and static analysis, safety vs. soundness, // SAFETY: contracts, Miri. Talking to hardware: volatile and MMIO, the linker script and map file, PACs and typestate, embedded-hal drivers that run on the STM32 and the Jetson alike, libgpiod and i2c-dev.
Part IV · Modules 9–11 — Concurrency on Each Tier
Interrupts and shared state: the C11 memory model, critical sections, atomics, ISR ↔︎ main patterns, and their type-level restatement as Send/Sync, critical_section::Mutex, and heapless::spsc. RTOS and async: FreeRTOS tasks and queues, RTIC’s Stack Resource Policy, Embassy’s async executor, compared on one three-stage pipeline. Embedded Linux systems programming: descriptors, timerfd, epoll, SCHED_FIFO, mlockall, pthreads and std::thread, serial and sockets, the Jetson’s real-time knobs.
Part V · Module 12 — Engineering Practice and the Capstone
Opaque types and linkage, assertions at every level, host-tested register fakes, the quality gate (warnings-as-errors, Clippy, sanitizers, static analysis), cargo profiles and the binary-size ladder, defmt vs. log, and C↔︎Rust interop in both directions. Capstone: the ADS1115 driver from Course 3 Lab 3.4 written three times — Python reference, C17, Rust — built for bare metal, an RTOS, and the Jetson, and closed with the note “My working subset, in three languages.”
Course 3 — Embedded DSP: From the Bench to Real-Time Firmware
A fully lab-based course connecting the mathematics of Course 1 to working firmware and a real bench. Eight core modules plus two bonus modules (~46 labs), each predicted by hand, built on a breadboard, measured on real instruments (WANPTEK PSU, Fluke 117, Siglent 100 MHz scope, FNIRSI LCR meter, Saleae Logic 8), and driven from an STM32 Cortex-M4F.
Modules 0–2 · Bench, Scope, and Digital Timing
Power supplies, multimeters, and LCR measurement done safely and correctly. The oscilloscope: probe compensation, DC/ripple, and a first RC filter. Digital I/O and timing on the STM32: GPIO timing, timer-interrupt jitter, and UART decoding, all captured on the Saleae logic analyzer.
Modules 3–4 · Mixed-Signal I/O and Analog Conditioning
I²C bus bring-up, the MCP4725 DAC (voltage and waveform generation), the ADS1115 16-bit ADC, and safe 3.3 V ↔︎ 5 V level shifting. Then analog signal conditioning with the MCP6002 op-amp: voltage followers, non-inverting gain, clipping, and an active anti-alias low-pass filter.
Modules 5–7 · Acquisition, Real-Time DSP, and Robust Firmware
STM32 data acquisition: single-sample, timer-triggered, and DMA circular-buffer sampling, plus a live aliasing demonstration. Real-time DSP on the Cortex-M4F: FIR and IIR filters (with fixed-point stability), the FFT spectrum analyzer, PSD/noise-floor estimation, and Goertzel tone detection. Robust firmware: watchdog and fault handling, a FreeRTOS pipeline, and an end-to-end capstone.
Bonus Module 8 · Edge ML Signal Processing
The modern differentiator: learned DSP on edge accelerators. Real-time GPU spectrograms on the Jetson Orin Nano, an on-device keyword/tone classifier, a learned denoiser compared to the classical Wiener filter, vibration anomaly detection, and a latency/power benchmark across STM32 vs. Raspberry Pi 5 vs. Jetson.
Bonus Module 9 · Host-in-the-Loop Audio, Image & Video Processing
Real media, real files, verifiable results. The laptop streams a real audio/image/video file to the embedded target, which processes it in real time and streams it back for verification against a reference implementation: a WAV round-trip harness, streamed real-time audio filtering, STFT overlap-add effects, 2-D image convolution (blur/Sobel), and a real-time video-frame pipeline on the Pi 5 / Jetson.
Course 4 — Real-Time Rendering & GPU Engineering: Vulkan, Metal & CUDA in Modern C++
A fully lab-based course (33 labs across 8 modules) that turns Course 1’s mathematics toward a second craft: real-time 3D rendering and GPU computing. Everything is built in modern C++20 with CMake and measured in a profiler: the graphics pipeline learned twice — every scene rendered through both Vulkan (C++) and Metal (Swift, then metal-cpp) — general-purpose CUDA in both C++ and Python on a Linux desktop’s RTX 4090, an engine that grows from a cleared swapchain to a render graph with multithreaded command recording, the techniques modern renderers are made of (cascaded shadows, deferred shading on tile-based GPUs, terrain LOD and streaming, GPU-driven rendering, Gaussian splatting), and a dedicated performance-engineering module built on Tracy, Xcode GPU capture, RenderDoc, and Nsight. Capstone: a streaming 3D world viewer on two APIs and two GPUs, with a staff-level performance report.
Modules 0–1 · Toolchain & GPU Compute Foundations
A C++20/CMake project skeleton with profiling, benchmarks, and sanitizers wired in from the first commit; Vulkan and Metal brought up side by side to a cleared, animated swapchain — validation layers and a first GPU capture included. Then the GPU as a throughput machine, learned where the tooling is deepest: CUDA kernels in C++ and Python (Numba/CuPy), the memory hierarchy and coalescing, the reduction ladder to warp shuffles, streams and async copies, and occupancy and the roofline in Nsight Compute.
Modules 2–3 · The Raster Pipeline & Shading
The graphics pipeline proper, every lab built twice — Vulkan/GLSL and Metal/MSL: pipelines and vertex input, transforms, depth and the camera (the coordinate-system differences worked, not footnoted), textures, samplers and mipmaps as applied sampling theory, and real glTF assets with honest alpha blending. Then the fragment shader earns its keep: Blinn–Phong in linear space, tangent-space normal mapping, physically based rendering with image-based lighting, and an HDR post stack — tone mapping and separable-Gaussian bloom.
Modules 4–5 · Engine Architecture & Advanced Techniques
From “a program that draws” to “an engine”: RAII resource lifetimes keyed to frames in flight, a minimal render graph that emits the barriers, a C++20 job system feeding multithreaded command recording, and a culled, instanced scene — one C++ core driving both APIs, with metal-cpp arriving as the second backend. Then the techniques a modern renderer is made of: cascaded shadow maps, deferred shading and its tile-memory variant on Apple GPUs, quadtree terrain with streaming and reverse-Z, GPU-driven culling with indirect draws, and a Gaussian-splat point renderer.
Modules 6–7 · Performance Engineering & the Capstone
The profilers become the subject: Tracy fluency and honest benchmarking, frame pacing, Metal GPU capture and counter-based limiter analysis, RenderDoc and timestamp queries on the Vulkan side, then a disciplined optimization ladder — overdraw, bandwidth, texture compression, perf-per-watt — run with before/after evidence. The capstone converges everything: a streaming 3D world viewer on two APIs and two GPUs, benchmarked on a deterministic flight path, closed out with a staff-level performance report in which every claim traces to a capture.