As a best-selling author, I invite you to explore my books on Amazon. Don't forget to follow me on Medium and show your support. Thank you! Your support means the world!

The rise of electric vehicles, industrial automation, and robotics has put motor control technology at the forefront of embedded systems development. Field-Oriented Control (FOC) stands as the preferred method for controlling AC motors and brushless DC motors due to its efficiency and performance advantages. Rust, with its focus on memory safety and performance, offers a compelling platform for implementing these sophisticated control algorithms.

I've spent years developing motor control systems, and the transition to Rust has transformed how I approach these projects. The language combines low-level control with high-level abstractions that make complex implementations more manageable without sacrificing performance.

Understanding Field-Oriented Control

Field-Oriented Control transforms the complex three-phase AC quantities into a simpler two-coordinate system (direct and quadrature axes) that rotates with the motor's magnetic field. This transformation simplifies the control problem and enables precise management of motor torque and flux.

The fundamental concept involves measuring the motor's current, transforming these measurements through mathematical operations, and generating appropriate PWM signals to drive the motor efficiently. The entire process must execute quickly, typically within microseconds, to maintain stable motor operation.

struct FocState {
    // Current values in alpha-beta frame
    i_alpha: f32,
    i_beta: f32,

    // Current values in d-q frame
    i_d: f32,
    i_q: f32,

    // Voltage commands in d-q frame
    v_d: f32,
    v_q: f32,

    // Rotor electrical angle
    theta: f32,

    // PI controller states
    d_integrator: f32,
    q_integrator: f32,
}

impl FocState {
    fn new() -> Self {
        Self {
            i_alpha: 0.0, i_beta: 0.0,
            i_d: 0.0, i_q: 0.0,
            v_d: 0.0, v_q: 0.0,
            theta: 0.0,
            d_integrator: 0.0, q_integrator: 0.0,
        }
    }
}

Rust's Safety in Critical Applications

Motor control represents a safety-critical application. Unexpected behavior can damage equipment or cause physical harm. Rust's strict compiler checks eliminate entire classes of bugs that plague C/C++ implementations:

Memory safety issues become compile-time errors rather than runtime failures. Race conditions are prevented through Rust's ownership model, particularly important when sharing data between control and communication tasks.

The absence of null pointers eliminates a common source of crashes. Buffer overflows, a frequent issue in DSP algorithms, are caught during compilation.

These safety features don't come at the cost of performance. Rust achieves this through zero-cost abstractions, where high-level constructs compile down to the same machine code as manually optimized low-level code.

Clarke and Park Transformations

The mathematical foundation of FOC involves two key transformations:

The Clarke transformation converts three-phase currents (a, b, c) to a stationary two-phase system (α, β).

The Park transformation then rotates this stationary frame to align with the rotor flux, creating the rotating reference frame (d, q).

Here's how these transformations look in Rust:

fn clarke_transform(current_a: f32, current_b: f32, current_c: f32) -> (f32, f32) {
    // Clarke transform: 3-phase to alpha-beta
    let alpha = current_a;
    let beta = (current_a + 2.0 * current_b) / 1.732;
    (alpha, beta)
}

fn park_transform(alpha: f32, beta: f32, sin_theta: f32, cos_theta: f32) -> (f32, f32) {
    // Park transform: alpha-beta to d-q
    let d = alpha * cos_theta + beta * sin_theta;
    let q = -alpha * sin_theta + beta * cos_theta;
    (d, q)
}

fn inverse_park_transform(d: f32, q: f32, sin_theta: f32, cos_theta: f32) -> (f32, f32) {
    // Inverse Park transform: d-q to alpha-beta
    let alpha = d * cos_theta - q * sin_theta;
    let beta = d * sin_theta + q * cos_theta;
    (alpha, beta)
}

PI Controllers for Current Regulation

The control system typically uses PI (Proportional-Integral) controllers to regulate current in the d-q frame. Rust's type system helps manage these controllers safely:

struct PiController {
    kp: f32,           // Proportional gain
    ki: f32,           // Integral gain
    integral: f32,     // Integral term state
    output_limit: f32, // Maximum absolute output value
}

impl PiController {
    fn new(kp: f32, ki: f32, output_limit: f32) -> Self {
        Self {
            kp,
            ki,
            integral: 0.0,
            output_limit,
        }
    }

    fn update(&mut self, error: f32, dt: f32) -> f32 {
        // Calculate integral term with anti-windup
        self.integral += error * dt;

        // Calculate output
        let output = self.kp * error + self.ki * self.integral;

        // Apply output limiting
        let limited_output = output.clamp(-self.output_limit, self.output_limit);

        // Anti-windup: adjust integral if output is saturated
        if limited_output != output {
            self.integral = (limited_output - self.kp * error) / self.ki;
        }

        limited_output
    }

    fn reset(&mut self) {
        self.integral = 0.0;
    }
}

Space Vector Modulation

Space Vector Modulation (SVM) translates the voltage commands from the d-q frame into PWM signals for the three-phase inverter. This algorithm optimizes the use of the DC bus voltage and minimizes harmonics.

fn space_vector_modulation(alpha: f32, beta: f32, v_bus: f32) -> (f32, f32, f32) {
    // Convert alpha-beta to space vector
    let u_alpha = alpha;
    let u_beta = beta;

    // Sector determination
    let sector = {
        let a = u_beta > 0.0;
        let b = (-0.8660 * u_alpha - 0.5 * u_beta) > 0.0;
        let c = (0.8660 * u_alpha - 0.5 * u_beta) > 0.0;

        if !a && !b && !c {
            1
        } else if !a && !b && c {
            2
        } else if !a && b && c {
            3
        } else if !a && b && !c {
            4
        } else if a && b && !c {
            5
        } else {
            6
        }
    };

    // Calculate duty cycles based on sector
    // (Simplified for brevity)
    let (ta, tb, tc) = match sector {
        1 => {
            // Calculations for sector 1
            let t4 = (1.0 + 1.732 * u_beta) / 3.0;
            let t6 = (1.0 + 1.732 * u_beta - 3.0 * u_alpha) / 3.0;
            (1.0 - t4 - t6, t4, t6)
        },
        // Other sectors...
        _ => (0.5, 0.5, 0.5) // Default/fallback
    };

    // Convert to PWM duty cycles (0.0 to 1.0)
    let duty_a = ta.clamp(0.0, 1.0);
    let duty_b = tb.clamp(0.0, 1.0);
    let duty_c = tc.clamp(0.0, 1.0);

    (duty_a, duty_b, duty_c)
}

Motor Position and Velocity Estimation

Accurate rotor position measurement is critical for FOC. Many systems use quadrature encoders or Hall effect sensors:

struct QuadratureEncoder {
    counts_per_revolution: u32,
    count: i32,
    prev_count: i32,
    velocity: f32,
    last_update_time: u32, // microseconds
}

impl QuadratureEncoder {
    fn new(counts_per_revolution: u32) -> Self {
        Self {
            counts_per_revolution,
            count: 0,
            prev_count: 0,
            velocity: 0.0,
            last_update_time: 0,
        }
    }

    fn update(&mut self, new_count: i32, current_time_us: u32) {
        self.prev_count = self.count;
        self.count = new_count;

        // Calculate velocity in rad/s
        let delta_count = self.count - self.prev_count;
        let delta_time_s = (current_time_us - self.last_update_time) as f32 / 1_000_000.0;

        if delta_time_s > 0.0 {
            let delta_angle = 2.0 * std::f32::consts::PI * delta_count as f32 
                            / self.counts_per_revolution as f32;
            self.velocity = delta_angle / delta_time_s;
        }

        self.last_update_time = current_time_us;
    }

    fn get_angle(&self) -> f32 {
        // Return angle in radians [0, 2π)
        let angle = (self.count % self.counts_per_revolution as i32) as f32 
                  / self.counts_per_revolution as f32 * 2.0 * std::f32::consts::PI;
        if angle < 0.0 {
            angle + 2.0 * std::f32::consts::PI
        } else {
            angle
        }
    }
}

Fixed-Point Arithmetic for Constrained Systems

Many microcontrollers lack floating-point units. Rust's fixed-point libraries provide a compelling alternative:

use fixed::{types::extra::U16, FixedI32};

// Define a fixed-point type with 16 fractional bits
type Fix = FixedI32<U16>;

fn fixed_point_pi_controller(
    error: Fix, 
    integral: &mut Fix, 
    kp: Fix, 
    ki: Fix, 
    dt: Fix, 
    output_limit: Fix
) -> Fix {
    // Update integral term
    *integral += error * dt;

    // Calculate output
    let output = kp * error + ki * *integral;

    // Apply output limiting
    if output > output_limit {
        output_limit
    } else if output < -output_limit {
        -output_limit
    } else {
        output
    }
}

Complete FOC Implementation

A complete FOC implementation combines all these elements into a cohesive system:

struct FocController {
    // Motor parameters
    pole_pairs: u8,

    // Current controllers
    id_controller: PiController,
    iq_controller: PiController,

    // Current state
    state: FocState,

    // Reference values
    id_ref: f32,
    iq_ref: f32,
}

impl FocController {
    fn new(pole_pairs: u8) -> Self {
        Self {
            pole_pairs,
            id_controller: PiController::new(0.5, 10.0, 0.9),
            iq_controller: PiController::new(0.5, 10.0, 0.9),
            state: FocState::new(),
            id_ref: 0.0,
            iq_ref: 0.0,
        }
    }

    fn update(
        &mut self, 
        current_a: f32, 
        current_b: f32, 
        current_c: f32, 
        angle: f32, 
        dt: f32,
        v_bus: f32
    ) -> (f32, f32, f32) {
        // 1. Update rotor angle
        self.state.theta = angle * self.pole_pairs as f32;
        let sin_theta = self.state.theta.sin();
        let cos_theta = self.state.theta.cos();

        // 2. Clarke transform
        let (alpha, beta) = clarke_transform(current_a, current_b, current_c);
        self.state.i_alpha = alpha;
        self.state.i_beta = beta;

        // 3. Park transform
        let (d, q) = park_transform(alpha, beta, sin_theta, cos_theta);
        self.state.i_d = d;
        self.state.i_q = q;

        // 4. Execute PI controllers
        let d_error = self.id_ref - d;
        let q_error = self.iq_ref - q;

        self.state.v_d = self.id_controller.update(d_error, dt);
        self.state.v_q = self.iq_controller.update(q_error, dt);

        // 5. Inverse Park transform
        let (v_alpha, v_beta) = inverse_park_transform(
            self.state.v_d, self.state.v_q, sin_theta, cos_theta
        );

        // 6. Space Vector Modulation
        space_vector_modulation(v_alpha, v_beta, v_bus)
    }

    fn set_torque_reference(&mut self, torque: f32) {
        // Convert torque to q-axis current (simplified)
        self.iq_ref = torque;
        // Typically, id_ref is kept at 0 for PMSM motors
        self.id_ref = 0.0;
    }
}

Real-Time Considerations

Motor control systems operate under strict timing constraints. Rust's deterministic performance characteristics are advantageous here:

No garbage collection means no unexpected pauses. Predictable stack allocation patterns make timing analysis more reliable. Fine-grained control over hardware resources allows optimal use of peripherals.

For hard real-time systems, I often avoid dynamic memory allocation entirely:

#![no_std]
#![no_main]

use core::panic::PanicInfo;
use cortex_m_rt::entry;
use stm32f4xx_hal::{prelude::*, stm32};

// Statically allocated controller
static mut CONTROLLER: Option<FocController> = None;

#[entry]
fn main() -> ! {
    // Initialize hardware
    let peripherals = stm32::Peripherals::take().unwrap();
    let rcc = peripherals.RCC.constrain();
    let clocks = rcc.cfgr.sysclk(84.mhz()).freeze();

    // Configure PWM outputs, ADCs, etc.
    // ...

    // Initialize controller (use static allocation)
    unsafe {
        CONTROLLER = Some(FocController::new(7)); // 7 pole pairs
    }

    // Configure interrupt-driven control loop
    // ...

    loop {
        // Background tasks, or sleep to save power
    }
}

// ISR for control loop
#[interrupt]
fn TIM2() {
    static mut LAST_TICK: u32 = 0;

    // Calculate dt
    let now = DWT::get_cycle_count();
    let dt = ((now - *LAST_TICK) as f32) / 84_000_000.0; // assuming 84 MHz clock
    *LAST_TICK = now;

    // Read ADC values for currents
    // ...

    // Read encoder for position
    // ...

    // Update FOC algorithm
    let controller = unsafe { CONTROLLER.as_mut().unwrap() };
    let pwm_duties = controller.update(current_a, current_b, current_c, angle, dt, v_bus);

    // Update PWM outputs
    // ...

    // Clear interrupt flag
    // ...
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    // Safety shutdown of motor
    // ...
    loop {}
}

Embedded HAL for Hardware Abstraction

The Embedded HAL (Hardware Abstraction Layer) provides standardized traits for interacting with microcontroller peripherals:

use embedded_hal::{
    adc::OneShot,
    digital::v2::OutputPin,
    PwmPin,
    timer::CountDown,
};

struct MotorHardware<ADC, PWM1, PWM2, PWM3, ENABLE, TIMER>
where
    ADC: OneShot<u16, Channel=u8>,
    PWM1: PwmPin,
    PWM2: PwmPin,
    PWM3: PwmPin,
    ENABLE: OutputPin,
    TIMER: CountDown,
{
    adc: ADC,
    current_sense_channels: (u8, u8, u8),
    pwm_phase_a: PWM1,
    pwm_phase_b: PWM2,
    pwm_phase_c: PWM3,
    enable_pin: ENABLE,
    timer: TIMER,
}

impl<ADC, PWM1, PWM2, PWM3, ENABLE, TIMER> MotorHardware<ADC, PWM1, PWM2, PWM3, ENABLE, TIMER>
where
    ADC: OneShot<u16, Channel=u8>,
    PWM1: PwmPin,
    PWM2: PwmPin,
    PWM3: PwmPin,
    ENABLE: OutputPin,
    TIMER: CountDown,
{
    fn enable_motor(&mut self) -> Result<(), ENABLE::Error> {
        self.enable_pin.set_high()
    }

    fn disable_motor(&mut self) -> Result<(), ENABLE::Error> {
        self.enable_pin.set_low()
    }

    fn set_pwm_duty(&mut self, duties: (f32, f32, f32)) {
        let max_duty_a = self.pwm_phase_a.get_max_duty();
        let max_duty_b = self.pwm_phase_b.get_max_duty();
        let max_duty_c = self.pwm_phase_c.get_max_duty();

        self.pwm_phase_a.set_duty((duties.0 * max_duty_a as f32) as u16);
        self.pwm_phase_b.set_duty((duties.1 * max_duty_b as f32) as u16);
        self.pwm_phase_c.set_duty((duties.2 * max_duty_c as f32) as u16);
    }

    fn read_phase_currents(&mut self) -> Result<(f32, f32, f32), ADC::Error> {
        let raw_a = self.adc.read(self.current_sense_channels.0)?;
        let raw_b = self.adc.read(self.current_sense_channels.1)?;
        let raw_c = self.adc.read(self.current_sense_channels.2)?;

        // Convert ADC readings to amps (example conversion)
        let current_a = (raw_a as f32 - 2048.0) * 0.01;
        let current_b = (raw_b as f32 - 2048.0) * 0.01;
        let current_c = (raw_c as f32 - 2048.0) * 0.01;

        Ok((current_a, current_b, current_c))
    }
}

Performance Optimization

While Rust provides safety, motor control demands optimization. Several techniques help achieve both goals:

Placing critical code in RAM for faster execution. Using SIMD instructions via core::arch or portable_simd for parallel math operations. Leveraging inline assembly for time-critical operations.

// Example of optimized sine/cosine calculation using lookup tables
const SINE_TABLE_SIZE: usize = 256;
static SINE_TABLE: [f32; SINE_TABLE_SIZE] = generate_sine_table();

fn const generate_sine_table() -> [f32; SINE_TABLE_SIZE] {
    let mut table = [0.0; SINE_TABLE_SIZE];

    for i in 0..SINE_TABLE_SIZE {
        let angle = 2.0 * std::f32::consts::PI * (i as f32) / (SINE_TABLE_SIZE as f32);
        table[i] = angle.sin();
    }

    table
}

#[inline(always)]
fn fast_sin(angle: f32) -> f32 {
    // Normalize angle to [0, 2π)
    let normalized = if angle < 0.0 {
        angle + 2.0 * std::f32::consts::PI * ((-angle / (2.0 * std::f32::consts::PI)).ceil())
    } else {
        angle % (2.0 * std::f32::consts::PI)
    };

    // Convert to table index
    let index = (normalized * (SINE_TABLE_SIZE as f32) / (2.0 * std::f32::consts::PI)) as usize;

    SINE_TABLE[index]
}

#[inline(always)]
fn fast_cos(angle: f32) -> f32 {
    fast_sin(angle + std::f32::consts::PI/2.0)
}

Error Handling and Fault Detection

Robust motor control requires comprehensive fault detection and safe handling of error conditions:

enum MotorFault {
    OverCurrent,
    OverVoltage,
    UnderVoltage,
    OverTemperature,
    EncoderError,
    ControlLoopTimeViolation,
}

struct SafetyMonitor {
    current_limit: f32,
    voltage_limits: (f32, f32),  // (min, max)
    temperature_limit: f32,
    loop_time_limit_us: u32,
}

impl SafetyMonitor {
    fn check_limits(
        &self,
        currents: (f32, f32, f32),
        voltage: f32,
        temperature: f32,
        loop_time_us: u32
    ) -> Result<(), MotorFault> {
        // Check phase currents
        let max_current = currents.0.abs().max(currents.1.abs()).max(currents.2.abs());
        if max_current > self.current_limit {
            return Err(MotorFault::OverCurrent);
        }

        // Check bus voltage
        if voltage < self.voltage_limits.0 {
            return Err(MotorFault::UnderVoltage);
        }
        if voltage > self.voltage_limits.1 {
            return Err(MotorFault::OverVoltage);
        }

        // Check temperature
        if temperature > self.temperature_limit {
            return Err(MotorFault::OverTemperature);
        }

        // Check control loop timing
        if loop_time_us > self.loop_time_limit_us {
            return Err(MotorFault::ControlLoopTimeViolation);
        }

        Ok(())
    }
}

Testing and Validation

Testing embedded motor control systems presents unique challenges. Rust's testing framework supports various approaches:

Unit tests for algorithm correctness. Hardware-in-the-loop testing using mocked peripherals. Property-based testing for edge cases.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_clarke_transform() {
        // Balanced three-phase system
        let (alpha, beta) = clarke_transform(1.0, -0.5, -0.5);
        assert!((alpha - 1.0).abs() < 1e-6);
        assert!((beta - 0.0).abs() < 1e-6);
    }

    #[test]
    fn test_park_transform() {
        // Test with known angles
        let (d, q) = park_transform(1.0, 0.0, 0.0, 1.0);  // 0 degrees
        assert!((d - 1.0).abs() < 1e-6);
        assert!((q - 0.0).abs() < 1e-6);

        let (d, q) = park_transform(0.0, 1.0, 1.0, 0.0);  // 90 degrees
        assert!((d - 0.0).abs() < 1e-6);
        assert!((q - (-1.0)).abs() < 1e-6);
    }

    #[test]
    fn test_svm() {
        // Test center point (zero output)
        let (a, b, c) = space_vector_modulation(0.0, 0.0, 24.0);
        assert!((a - 0.5).abs() < 1e-6);
        assert!((b - 0.5).abs() < 1e-6);
        assert!((c - 0.5).abs() < 1e-6);
    }
}

In my experience, Rust has transformed how I develop motor control systems. The language's emphasis on safety without sacrificing performance addresses the key challenges in this domain. The ability to express complex algorithms clearly while maintaining the determinism needed for real-time control makes Rust an ideal choice for the next generation of motor control applications.


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!

Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva