Binary fixed point is a numerical representation method where a set number of bits are permanently allocated to the integer and fractional parts of a value, separated by an implied binary point. If you are writing firmware for an 8-bit AVR or optimizing an interrupt service routine (ISR) on an ESP32, relying on standard float variables can silently destroy your timing margins. Fixed-point math changes the game in real circuits by allowing the microcontroller's integer Arithmetic Logic Unit (ALU) to handle fractional calculations in just one or two clock cycles. This fundamentally changes your circuit's power envelope and BOM cost: by eliminating the need for a hardware Floating Point Unit (FPU) or power-hungry software float libraries, you can downgrade to a cheaper, lower-power 8-bit or Cortex-M0+ microcontroller without sacrificing control loop performance. People commonly confuse it with floating point, but while floating point dynamically shifts the radix point using an exponent (sacrificing deterministic execution time for massive range), fixed point keeps the radix point nailed down, guaranteeing exact cycle counts for every operation.

What Binary Fixed Point Actually Is (And Why It Matters)

In standard integer math, the binary point is implicitly fixed at the far right of the register. A 16-bit integer like 00000000 00000011 simply means 3. In binary fixed point, we mentally (and programmatically) move that binary point to the left by a predefined number of bits. The hardware doesn't know the point moved; it just sees a standard integer. It is the firmware's responsibility to track where the implied point lives.

This matters because fractional math is unavoidable in modern electronics. Whether you are calculating the duty cycle for a PID motor controller, applying a digital low-pass filter to a noisy thermistor reading, or running a sensor fusion algorithm on an IMU, you need decimals. On microcontrollers without a dedicated FPU (like the ubiquitous ATtiny85, STM32G030, or PIC16F series), performing a single floating-point multiplication requires calling a software library that executes dozens of underlying integer instructions. This burns clock cycles, bloats your flash memory, and introduces non-deterministic execution times that can cause jitter in high-speed control loops.

The Math: A Worked Numeric Example in Q8.8 Format

To standardize how we talk about these implied binary points, engineers use Q-format notation. Let's look at a concrete numeric example using the Q8.8 format, which maps perfectly to a standard 16-bit signed integer (int16_t in C/C++).

The Golden Rule of Q-Format: In a Qm.n format, m is the number of integer bits (including the sign bit), and n is the number of fractional bits. The resolution (smallest possible step) is always 1 / 2^n.

In Q8.8, we have 8 integer bits and 8 fractional bits. The resolution is 1 / 2^8 = 1 / 256 ≈ 0.00390625. Let's encode the real-world value 3.75 into this format using a numbered sequence:

  1. Identify the multiplier: Since we have 8 fractional bits, our scaling factor is 2^8, which is 256.
  2. Multiply the real value: 3.75 × 256 = 960.
  3. Convert to binary: The decimal value 960 translates to the 16-bit binary 00000011 11000000 (or 0x03C0 in hex).
  4. Verify the split: The left 8 bits (00000011) equal 3. The right 8 bits (11000000) equal 192. Since 192 / 256 = 0.75, our total is exactly 3.75.

When you need to convert this back to a human-readable float for serial debugging, you simply cast the integer to a float and divide by 256.0. But inside the tight control loop, you never divide; you use bitwise shifts. Dividing by 256 is mathematically identical to right-shifting by 8 bits (value >> 8), which takes a single clock cycle on almost any microcontroller.

Where You Meet This in Practice

You will encounter binary fixed point architectures in several critical areas of electrical and embedded engineering:

  • Motor Control (FOC): Field Oriented Control algorithms on DSPs (like the TI C2000 series) rely heavily on Q-format math to execute Park and Clarke transforms within microseconds.
  • Digital Audio Processing: IIR and FIR filters in audio codecs use fixed-point to prevent the accumulation of rounding errors that cause audible artifacts in floating-point implementations.
  • FPGA Logic: When writing Verilog or VHDL, instantiating a floating-point IP core consumes massive amounts of logic elements and DSP slices. Fixed-point math maps directly to native LUTs and carry chains, saving silicon area and power.
  • Battery-Operated Sensor Nodes: LoRaWAN or Zigbee end-devices running on coin cells use fixed-point to process sensor data without waking up power-hungry FPU hardware blocks.

Real-World Scenario: When Floating Point Bricks Your PID Loop

To understand why this matters on the bench, let's walk through a failure scenario involving a DC motor speed controller.

Setup: You are building a closed-loop speed controller for a conveyor belt using an ATtiny85 (an 8-bit AVR running at 8 MHz with no hardware FPU). The PID control loop is triggered by a Timer1 interrupt at 1 kHz, meaning the entire ISR must execute within a 1000 µs period. You wrote the PID math using standard float variables for the error, integral, and derivative terms.

Numbers: On the ATtiny85, a software-emulated floating-point multiplication takes roughly 120 clock cycles. An addition takes about 40 cycles. A full PID calculation step (three multiplies, two additions, plus integral accumulation) consumes about 500 clock cycles. At 8 MHz, 500 cycles equates to 62.5 µs. On paper, 62.5 µs fits easily inside your 1000 µs budget.

Outcome: The motor stutters violently at low speeds, the serial debug output is garbled, and the microcontroller randomly resets every few minutes, tripping the hardware watchdog timer.

What Went Wrong: The floating-point math wasn't the only thing happening in the ISR. You also had to read the ADC (which takes ~100 µs on the AVR), update the PWM registers, and handle the context switching overhead of the interrupt itself. The total ISR execution time silently ballooned to 1150 µs. Because the ISR took longer than the timer period, interrupts began stacking and dropping. The PID loop missed ticks, the integral term wound up uncontrollably, and the main loop was starved of CPU time, failing to pet the watchdog timer.

The Fix: Rewriting the PID math using Q8.8 fixed-point integers. The 16-bit integer multiply (MUL instruction) takes exactly 2 clock cycles. The integer add takes 1 cycle. The PID math drops from 500 cycles to roughly 15 cycles. The ISR execution time plummets to 140 µs total, leaving 860 µs of headroom. The motor runs perfectly smooth, and the watchdog never resets.

Quick-Reference: Q-Format Notation and Bit Scaling

Choosing the right Q-format depends on your required dynamic range and precision. Here is a reference table for the most common formats used in embedded C and FPGA design.

Format Name Total Bits Integer Bits (inc. sign) Fractional Bits Resolution Max Positive Value
Q15 16 1 15 ~0.0000305 0.9999695
Q8.8 16 8 8 0.00390625 127.996
Q16.16 32 16 16 ~0.0000152 32767.999
Q31 32 1 31 ~0.00000000046 0.9999999995

Note: For deep architectural details on how DSPs handle Q-format natively, refer to the All About Circuits guide on embedded math or standard Wikipedia documentation on Fixed-Point Arithmetic.

Frequently Asked Questions

Does binary fixed point overflow easier than floating point?
Yes, this is the primary trade-off. Because the radix point is fixed, your maximum value is strictly limited by the integer bits. Furthermore, when you multiply two Q8.8 numbers, the result mathematically requires a Q16.16 register to prevent overflow. You must explicitly cast to a 32-bit integer (int32_t) before multiplying, and then right-shift the result back down to 16 bits.

Can I use fixed point on an ESP32 or Cortex-M4 that already has an FPU?
Absolutely. Even with a hardware FPU, floating-point operations can suffer from pipeline stalls and memory bandwidth bottlenecks. In high-throughput DSP tasks (like processing a 1024-point FFT on an audio stream), using 32-bit fixed-point math (Q16.16) allows you to leverage SIMD (Single Instruction, Multiple Data) instructions or DMA transfers much more efficiently than standard IEEE 754 floats.

How do I handle negative numbers in fixed-point?
Fixed-point relies on standard two's complement representation, just like regular integers. If you are using Q8.8 in a signed 16-bit integer (int16_t), the most significant bit is the sign bit. A value of -3.75 is simply the two's complement of 960, which is -960 in decimal, or 0xFC40 in hex. The bitwise shift rules remain exactly the same, provided you use arithmetic right shifts (which preserve the sign bit) rather than logical right shifts.

What is the biggest mistake beginners make when implementing this?
Failing to scale the PID tuning constants. If you tune your PID loop using floating-point math where $K_p = 1.5$, you cannot just plug 1.5 into your fixed-point code. You must convert $K_p$ into the same Q-format as your error variable (e.g., $1.5 \times 256 = 384$) before the multiplication step, otherwise your control output will be scaled down by a factor of 256, resulting in a completely unresponsive system.