The CMSIS (Cortex Microcontroller Software Interface Standard) library provides useful functions for fixed-point arithmetic operations on ARM Cortex-M processors. Fixed-point arithmetic is important for embedded systems as it allows mathematical operations to be performed efficiently without requiring a floating point unit (FPU). The CMSIS library includes optimized functions for both signed and unsigned fixed-point math.
Overview of fixed-point arithmetic
In fixed-point arithmetic, numbers are represented with a fixed number of integer and fractional bits. For example, 16.16 fixed-point format uses 16 bits for the integer part and 16 bits for the fractional part. This allows numbers between -32768 and 32767.9999 to be represented precisely. The position of the radix point (binary point) is fixed, hence the name fixed-point.
Fixed-point math is typically faster and requires less code size than floating point math on microcontrollers without a dedicated FPU. However, care must be taken to avoid overflow and precision loss. Dynamic range and precision are determined by the number of integer and fractional bits used.
CMSIS fixed-point math functions
The CMSIS library provides a range of functions for fixed-point math operations including:
- Addition and subtraction
- Square root
- Trigonometric functions (sin, cos, tan, atan2)
- Logarithms and exponents
- Data conversion
These functions are optimized for ARM Cortex-M processors to give good performance without an FPU. The library supports Q15, Q31 and Q63 formats for signed numbers, and Q7, Q15, Q31 for unsigned numbers. The Q format refers to the number of fractional bits – Q15 has 15 fractional bits for example.
Using fixed-point math functions
Using the CMSIS fixed-point math functions is straightforward. The functions operate on standard C data types like 32-bit integers. Here are some usage examples: #include “arm_math.h” q31_t x, y, z; // Q31 signed 32-bit integers x = 0x7000000; // Initialize x y = 0x8000000; // Initialize y z = arm_add_q31(x, y); // z = x + y q15_t a, b, c; // Q15 signed 16-bit integers arm_mult_q15(&a, &b, &c, 1); // c = a * b q31_t angle, sine; arm_sin_q31(angle, &sine); // sine = sin(angle)
The addition, subtraction, multiplication and division functions support scalar and vector operations. Functions like
arm_sin_q31() operate on scalar data. Input and output scaling can be done using functions like
Choosing the right fixed-point format involves tradeoffs between dynamic range, precision, and efficiency. Some key factors are:
- Number of integer bits – Determines dynamic range
- Number of fractional bits – Determines precision
- Signed vs unsigned – Signed supports negative numbers
- Word size – 32-bit is efficient on Cortex-M
Some common formats are:
- Q15 – 16-bit signed, 1 integer bit, 15 fractional bits
- Q31 – 32-bit signed, 1 integer bit, 31 fractional bits
- Q7 – 8-bit unsigned, 7 fractional bits
Q15 and Q31 are convenient formats that align well with the Cortex-M’s native word size. Q15 provides a range of [-1, 1-2^15] with about 3 decimal digits of precision. Q31 provides higher precision but smaller dynamic range.
Choosing a fixed-point format
Here are some tips for choosing a fixed-point format:
- Analyze signal range to determine the required integer word length
- Use more fractional bits for higher precision
- Prefer unsigned formats if negative values are not needed
- Account for overflow and saturation
- Prefer word sizes that are power of two (e.g. 8, 16, 32 bit)
- Test different formats with representative data sets
Q15 or Q31 are good starting points. Q63 can be used if higher precision is needed. The CMSIS libraries provide saturation logic to avoid overflows.
Overflow and saturation
A common issue with fixed-point arithmetic is overflow, where the result of a calculation does not fit within the supported word length. This can happen whenever the true mathematical result exceeds the range supported by the fixed-point format. For example, multiplying two large Q15 numbers can easily overflow 16 bits.
The CMSIS libraries handle overflow using saturation logic. Essentially, any result that overflows the format is clipped to the maximum or minimum value. For Q31, results are saturated to 0x7FFFFFFF for positive overflow and 0x80000000 for negative overflow. This avoids wrap-around on overflow.
Saturation behavior is enabled using the “_sat” suffix on math functions like
arm_add_q15_sat(). The unsaturated functions will wrap-around on overflow. Saturation provides safety at the cost of some performance. q15_t x, y; x = 0x7FFF; // max positive Q15 value y = 0x4000; q15_t res = arm_add_q15(x, y); // Overflow q15_t res_sat = arm_add_q15_sat(x, y); // Saturation
In this example,
res will overflow but
res_sat will saturate correctly to 0x7FFF.
Fixed-point vs floating point
Fixed-point has some advantages and disadvantages compared to floating point:
- No need for dedicated floating point unit (FPU)
- Lower memory and MIPS requirements
- Deterministic execution time
- Limited dynamic range
- Lack of precision in some cases
- Risk of overflow
- No native compiler/hardware support
In resource constrained microcontrollers, fixed-point is often a pragmatic choice. It provides sufficient dynamic range and precision for many applications. The CMSIS libraries make fixed-point math on Cortex-M straightforward.
The CMSIS fixed-point math library enables efficient math operations on Cortex-M processors without an FPU. Choosing the right format requires balancing range, precision and efficiency. Q15 and Q31 are good formats to start with. Saturation handling minimizes risk of overflow errors. With careful use, fixed-point can support many embedded applications.
The CMSIS functions provide an optimized and consistent way to use fixed-point math on ARM chips. They leverage Cortex-M capabilities and are a useful tool for any developer working with these microcontrollers.