Skip to the content.

System Architecture Overview

This document describes the high-level architecture and module breakdown of the embedded-motor-pid-controller project.

The goal of this project is to demonstrate a production-ready, portable PID-based motor speed controller in embedded C, featuring comprehensive testing, professional build infrastructure, and advanced control algorithms. It includes a Python simulation environment for offline tuning and validation.


1. Project Structure

The project is organized into seven main components:

  1. firmware/ – Core embedded C implementation (PID + motor abstraction)
  2. tests/ – Unit test suite using Unity framework
  3. sim/ – Python-based simulation and analysis tools
  4. docs/ – Comprehensive project documentation
  5. .github/ – CI/CD workflows (GitHub Actions)
  6. Build System – CMake configuration and Doxygen setup
  7. Project Root – Configuration files, changelog, license
embedded-motor-pid-controller/
├── firmware/
│   ├── src/              # Implementation files
│   │   ├── main.c        # Application entry point
│   │   ├── motor.c       # Motor abstraction layer
│   │   └── pid.c         # PID control algorithm
│   └── include/          # Public API headers
│       ├── motor.h
│       └── pid.h
├── tests/
│   └── test_pid.c        # Unit tests (Unity framework)
├── sim/
│   └── pid_simulation.py # Python simulation & plotting
├── docs/
│   ├── images/           # Documentation assets
│   ├── api/              # API documentation (Doxygen)
│   │   ├── README.md     # Generation instructions
│   │   └── html/         # Generated HTML (Doxygen output, deployed to GitHub Pages)
│   ├── build.md          # Build instructions
│   ├── architecture.md   # This file (includes CI/CD details)
│   ├── index.md          # GitHub Pages landing page
│   └── _config.yml       # Jekyll configuration
├── .github/
│   └── workflows/
│       └── ci.yml        # CI/CD pipeline
├── CMakeLists.txt        # Build system configuration
├── Doxyfile              # API documentation config
├── requirements.txt      # Python dependencies
├── CHANGELOG.md          # Version history
├── LICENSE               # MIT License
└── README.md             # Project overview

2. Firmware Architecture (C Modules)

The firmware is designed with modularity, portability, and production quality in mind, separating hardware abstraction (motor control) from the core algorithm (PID control). This approach enables:

2.1 Module Overview

File(s) Module Name Description Dependencies
main.c Application Entry / Control Loop System initialization, PID configuration, and main control loop (superloop or RTOS task wrapper). Demo application showing PID usage. motor, pid
motor.c/.h Motor Control Abstraction Layer Low-level motor interface: configures GPIO/PWM, reads encoder feedback, exposes a hardware-agnostic API. Simple plant model for simulation. Hardware-specific HAL (or simulation)
pid.c/.h PID Control Algorithm (Production) Production-grade PID implementation with anti-windup, derivative filtering, derivative-on-measurement, and comprehensive state management. None (pure C99)

2.2 Module Responsibilities

main.c - Application Entry Point

motor.c/.h - Hardware Abstraction Layer

pid.c/.h - Production PID Controller (v1.0.0)

Core Data Structure (pid_t):

typedef struct {
    // PID gains
    float kp, ki, kd;
    float dt;                    // Sample time

    // Output limits
    float out_min, out_max;

    // Anti-windup limits
    float integrator_min, integrator_max;

    // Derivative filtering
    float derivative_lpf;        // Low-pass filter coefficient (0-1)

    // Internal state
    float integrator;            // Integral accumulator
    float prev_error;            // For derivative calculation
    float prev_measurement;      // For derivative-on-measurement
    float derivative_filtered;   // Filtered derivative value
} pid_t;

Public API:

Key Features (Production-Ready):

  1. Proper Anti-Windup:

    • Integrator clamped to prevent accumulation during saturation
    • Faster recovery from saturation conditions
    • Default limits calculated from output limits and Ki gain
  2. Derivative-on-Measurement:

    • Eliminates "derivative kick" on setpoint changes
    • Calculates derivative based on measurement changes only
    • Smoother control response, reduced actuator wear
    • Formula: d = -Kd * (measurement[n] - measurement[n-1]) / dt
  3. Optional Derivative Filtering:

    • Low-pass filter reduces noise amplification
    • Exponential moving average: filtered = α * filtered + (1-α) * raw
    • Configurable filter coefficient (0 = no filter, higher = more filtering)
    • Improves robustness to sensor noise
  4. Design Characteristics:

    • Reentrant: State passed via struct, multiple instances supported
    • Platform-agnostic: Pure C99, no external dependencies
    • Efficient: Suitable for real-time embedded systems
    • Fixed-point friendly: Can be adapted for integer math
    • Backward compatible: v1.0.0 maintains API compatibility with v0.1.0

3. Control Loop & Data Flow

The system operates in a closed-loop configuration:

graph TD
    A[Physical Motor and Encoder] --> B[Hardware Peripherals: Timers, GPIO, ADC]
    B --> C[Motor Module - motor.c]
    C --> D[Main Control Loop - main.c]
    D --> E[PID Module - pid.c]
    E --> C

3.1 Flow Description

  1. Input (Feedback):
    The motor encoder (or other sensor) provides feedback via hardware peripherals (timers, ADC, capture units, etc.).
  2. Motor Abstraction Layer:
    motor.c converts raw data (counts, pulses) into meaningful units (e.g., speed).
  3. Application Control Loop:
    The main control loop in main.c:
    • Reads the setpoint (desired speed/position).
    • Reads the measurement from the motor module.
    • Calls the pid module to compute the control effort.
  4. PID Processing:
    pid.c calculates the required control effort using:
    • Proportional term (P)
    • Integral term (I)
    • Derivative term (D) and applies any clamping/anti-windup to keep the output in a safe range.
  5. Output (Actuation):
    The motor module receives the control effort from motor_set_output() and converts it into a physical action (e.g., PWM duty cycle), which drives the motor.

4. Timing and Execution Model

The control loop can be executed in one of two ways:

  1. Bare-Metal Superloop:

    • main.c runs an infinite loop that:
      • Waits for a fixed time slice (e.g., via a timer flag)
      • Executes the control loop at a fixed frequency (e.g., 1 kHz)
    • Simple and works well for basic demos.
  2. RTOS Task:

    • The PID control logic is run in a periodic RTOS task.
    • Allows separation between:
      • Control tasks
      • Communication/UI tasks
      • Logging/diagnostics

The current project is kept MCU-agnostic, so timing details are left to the porting layer or example implementations.


5. Build System and Infrastructure

5.1 CMake Build System

The project uses CMake 3.15+ as its professional build system, providing:

Build Targets:

Key Features:

# Standard build
cmake -B build && cmake --build build

# Release build (optimized)
cmake -B build -DCMAKE_BUILD_TYPE=Release

# Build options
-DBUILD_TESTS=ON/OFF      # Enable/disable unit tests (default: ON)
-DBUILD_DEMO=ON/OFF       # Enable/disable demo app (default: ON)

Cross-Platform Support:

Benefits:

5.2 API Documentation (Doxygen)

Configuration: Doxyfile at project root

Features:

Usage:

doxygen Doxyfile
# Output: docs/api/html/index.html

Content:


6. Testing Infrastructure

6.1 Unit Testing Framework

Framework: Unity - Industry-standard C testing framework for embedded systems

Test Suite: tests/test_pid.c

Test Coverage (12 comprehensive tests):

Category Tests Description
Initialization 1 Verifies parameter setup and state initialization
Individual Terms 3 Tests P, I, D terms independently
Combined Operation 1 Validates full PID calculation
Output Limiting 2 Tests min/max clamping behavior
Anti-Windup 1 Verifies integrator clamping
State Management 1 Tests reset functionality
Edge Cases 3 Zero gains, negative errors, derivative behavior

Test Execution:

# Direct execution
./build/test_pid

# Via CTest
cd build && ctest --output-on-failure

# With verbose output
ctest --verbose

Test Output Example:

tests/test_pid.c:207:test_pid_init_sets_parameters:PASS
tests/test_pid.c:208:test_pid_proportional_only:PASS
tests/test_pid.c:209:test_pid_integral_only:PASS
...
-----------------------
12 Tests 0 Failures 0 Ignored
OK

6.2 Test Categories Detailed

1. Initialization Tests:

2. Term Verification Tests:

3. Anti-Windup Tests:

4. Derivative-on-Measurement Tests:

5. Edge Case Tests:

6.3 Testing Best Practices

The test suite demonstrates:


7. Simulation Environment

7.1 Python Simulation Tool

File: sim/pid_simulation.py

Purpose: Desktop simulation for offline PID tuning and validation before hardware deployment

Workflow:

  1. Compiles firmware using GCC with strict flags (-Wall -Wextra -Werror)
  2. Runs the compiled executable with simple motor plant model
  3. Generates log.csv - Time-series data (time, setpoint, measurement, output)
  4. Produces step_response.png - Visual plot of system response
  5. Adapts display mode: GUI when running locally, non-GUI in CI environment

Dependencies (requirements.txt):

numpy>=1.24.0,<2.0.0       # Numerical calculations
matplotlib>=3.7.0,<4.0.0   # Response plotting

7.2 Installation and Usage

# Install dependencies
pip install -r requirements.txt

# Run simulation
cd sim
python pid_simulation.py

7.3 Simulation Outputs

CSV Data (log.csv):

Visual Plot (step_response.png):

7.4 Analysis Capabilities

The simulation enables offline analysis of:

Performance Metrics:

Tuning Validation:

Use Cases:


8. Continuous Integration & Deployment (CI/CD)

8.1 GitHub Actions Workflow

Configuration: .github/workflows/ci.yml

Trigger Events:

Parallel Job Architecture:

Commit/PR Trigger
      ├─► Test Job (Unit Tests)
      │   ├─ Ubuntu-latest
      │   └─ Windows-latest
      │
      └─► Simulate Job (Python Simulation)
          ├─ Ubuntu-latest (Python 3.11)
          └─ Windows-latest (Python 3.11)

8.2 Test Job Details

Platforms: Ubuntu, Windows

Steps:

  1. Checkout: Clone repository
  2. Setup: Install CMake via lukka/get-cmake
  3. Dependencies: Clone Unity test framework
  4. Configure: cmake -B build -DCMAKE_BUILD_TYPE=Release
  5. Build: cmake --build build --config Release
  6. Test: ctest --test-dir build --output-on-failure -C Release

Quality Gates:

8.3 Simulate Job Details

Platforms: Ubuntu, Windows Python: 3.11

Steps:

  1. Checkout: Clone repository
  2. Python Setup: Install Python 3.11
  3. Dependencies: pip install -r requirements.txt
  4. Verify: Check GCC availability
  5. Simulate: Run python pid_simulation.py
  6. Artifacts: Upload log.csv and step_response.png

Outputs:

8.4 CI Benefits

Automated Quality Assurance:

Development Workflow:

Visibility:

8.5 Accessing CI Artifacts

Download Simulation Results:

  1. Navigate to Actions tab
  2. Select a workflow run
  3. Download artifacts named: pid-simulation-<os>-py<version>
  4. Extract to view:
    • log.csv - Time-series simulation data
    • step_response.png - Response plot visualization

9. Portability and Extension Points

This architecture is intentionally modular and extensible:

9.1 Hardware Porting

To port to new hardware (e.g., STM32, ESP32, Arduino):

  1. Update motor.c/.h:

    • Implement platform-specific PWM configuration
    • Add sensor/encoder reading for your hardware
    • Adapt GPIO and timer initialization
  2. Update main.c:

    • Initialize platform-specific clocks and peripherals
    • Configure UART/debugging if needed
    • Add platform startup code
  3. Update Build System:

    • Create CMake toolchain file for your platform
    • Configure compiler flags for target architecture
    • Link platform-specific libraries (HAL, RTOS)
  4. PID Core (pid.c/.h):

    • No changes required - platform-agnostic
    • Can optionally adapt to fixed-point if needed

9.2 Feature Extensions

Multi-Motor Control:

pid_t motor1_pid, motor2_pid;
pid_init(&motor1_pid, ...);
pid_init(&motor2_pid, ...);

Advanced Control Modes:

System Integration:

9.3 Algorithm Enhancements

Already Implemented (v1.0.0):

Future Possibilities:


10. Version History and Evolution

v1.0.0 (Nov 2025) - Production Release

Major Additions:

Technical Improvements:

v0.1.0 (Feb 2025) - Initial Release


11. Future Roadmap

Planned Enhancements

Testing & Quality:

Control Features:

Integration Examples:

Tools:


12. Summary

This architecture demonstrates professional embedded systems development:

Code Quality:

Development Practices:

Suitable For:


For detailed implementation instructions, see:


End of document.