CORDIC Optimization on an STM32 (ARM Cortex-M4)

embedded-systems
arm
optimization
dsp
Fixed-point CORDIC arctangent optimization for an embedded ARM Cortex-M4, from a naive 982-cycle baseline down to ~184 cycles via register allocation, loop unrolling, and lookup-table precomputation.
Author

Zak Toews, Robert Bell

Published

August 1, 2025

CORDIC Optimization Project using an STM32 Board

SENG 440 Embedded Systems — University of Victoria, Electrical and Computer Engineering

Summer 2025 · Instructor: Mihai Sima · Students: Robert Bell and Zak Toews · Group 18

Abstract

This project presents a fixed-point implementation of the CORDIC algorithm for trigonometric computation on a selected microcontroller using an ARM Cortex-M4. The CORDIC algorithm is an efficient method of computation which allows a resource constrained piece of hardware to perform otherwise costly calculations by replacing multiplication and division operations with shift and add operations, which is preferred on systems without FPUs. We were able to explore and utilize different embedded systems optimization techniques to reduce the number of cycles of computation to achieve a result within acceptable specifications. After each implementation technique was applied, we made sure to use the ARM debug architecture to monitor the number of cycles, and we explored the assembly code to understand the resulting changes in the instructions. Implementing a precalculated look up table (LUT), loop unrolling, and register keywords reduced the number of cycles needed for calculation, while techniques such as CMSIS and predicate replacement over branches reduced overall efficiency. The resulting optimized code saw a reduction of cycle count from ~632, from the math library function, and ~982, from the non-optimized CORDIC function to ~184 cycles. This is a substantial improvement that compares directly with “O3” and “Ofast” optimizations upon the non-optimized CORDIC function.

Table of Contents

  1. Introduction
  2. Background
  3. Algorithm Design
  4. C Code + Optimization
  5. Compiling C and Comparing Assembly
  6. Results
  7. Improvements
  8. Conclusions
  9. References
  10. Appendices

Introduction

The goal of this design project is to explore methodologies to optimize the CORDIC mathematical technique on a specific microcontroller. We have decided to use the STM32F407G-DISC1 board, which uses a STM32F407VGT6UAA286 chip.

The development of this project was done using the Ubuntu 24.04.2 LTS linux environment on a designated laptop, using the STM32CubeIDE version 1.18.1 integrated development environment. The code was compiled using the GNU Arm Embedded Toolchain (arm-none-eabi-gcc 13.3.rel1). A mesh VPN network was implemented for the developers to access the development computer remotely.

The primary objective of this work was to improve execution speed while preserving the numerical precision of the CORDIC algorithm. As such, the primary success metric was the reduction in clock cycles required for computation.

For reference, calling the standard atan() function from the C math library required approximately 630 clock cycles on the target platform. In comparison, the baseline CORDIC arctangent implementation, as described in the course documentation, required roughly 982 cycles.

A sequence of targeted optimizations was applied to the CORDIC implementation, each tested independently. These efforts culminated in a “naive” optimized version achieving a reduced runtime of approximately 387 cycles.

Further performance gains were observed through inspection of compiler behavior at the -O3 optimization level, resulting in a function execution time of approximately 184 cycles — comparable to results achieved with both -O3 and -Ofast compiler optimizations.

In addition to these optimizations, several other avenues for potential improvement were identified and evaluated for future work.

Zak Toews Robert Bell
Implemented “register” keyword optimization · Implemented CMSIS optimization · Implemented loop unrolling optimization · Implemented naive optimization · Implemented final optimization · Report contributions · Presentation contributions Researched hardware capabilities and available ARM intrinsics · Implemented precalculated LUT · Implemented control flow optimization with predicates · Initialized testbench with DWT and SWO · Report contributions · Presentation contributions

Figure 1. Project Contribution Table.

Background

(The background section covers the basics about CORDIC: why it is necessary, how it is implemented, its history and applications.)

The CORDIC technique has been used in many different applications. The implementation of many mathematical expressions including trigonometric, exponential, and logarithmic functions are often approximated using Taylor series polynomial expansions. These operations provide good approximations to many functions, but can be computationally expensive on hardware, especially on hardware without a floating point unit (FPU), a specialized part of the CPU or separate chip.

This technique relies on two different modes of application, rotation mode and vector mode. The vectoring mode starts with a vector, and makes iterations to rotate that vector toward the x-axis, which allows for the arctan and scaled magnitude to be found. In rotation mode, a vector and an angle are the inputs, and the algorithm rotates that vector by that angle to produce new coordinates, which can be used to compute sine and cosine.

The CORDIC algorithm computes these expensive expressions using only shifts, adds, and a small look up table (LUT). This allows a less expensive calculation to approximate these expressions, which lets machines with limited resources be able to perform such calculations.

CORDIC was developed by Convair, an American aircraft manufacturing company, as a means of digitizing the analog resolvers, the technology used at the time to assist in navigational computation on their aircraft.

Here are some real world applications:

Wireless Communications CORDIC is extensively used in digital signal processing for wireless systems:

  • Digital down-conversion: Converting high-frequency signals to baseband requires sine/cosine generation for mixing operations
  • Phase-locked loops (PLLs): CORDIC generates the reference signals needed for frequency synthesis and phase tracking
  • QAM demodulation: Quadrature amplitude modulation requires precise phase calculations that CORDIC handles efficiently
  • Beamforming: Antenna arrays use CORDIC for calculating the phase shifts needed to steer transmission beams

Graphics and Gaming Vector rotations are core to 3D graphics:

  • 3D transformations: Rotating objects in 3D space, camera movements, and perspective projections
  • Game physics: Calculating trajectories, collisions, and object orientations
  • Image processing: Rotating images, geometric corrections, and filtering operations

Navigation and Control Systems

  • GPS receivers: CORDIC calculates satellite positions and user location from trigonometric relationships
  • Inertial navigation: Processing gyroscope and accelerometer data to determine orientation and position
  • Robotics: Calculating joint angles, end-effector positions, and trajectory planning

Digital Signal Processing Hardware CORDIC is particularly valuable in FPGA and DSP implementations because:

  • It only requires additions, subtractions, and bit shifts (no multipliers)
  • It’s highly parallelizable and pipeline-friendly
  • It provides consistent execution time regardless of input values

How it works

x_temp_1 = x_i;
y_temp_1 = y_i;
z_temp = 0;

for (i = 0; i < 15; i++) { /* 15 iterations are needed */
    if (y_temp_1 >= 0) {
        x_temp_2 = x_temp_1 + (y_temp_1 >> i);
        y_temp_2 = y_temp_1 - (x_temp_1 >> i);
        z_temp += z_table[i];
    } else {
        x_temp_2 = x_temp_1 - (y_temp_1 >> i);
        y_temp_2 = y_temp_1 + (x_temp_1 >> i);
        z_temp -= z_table[i];
    }
    x_temp_1 = x_temp_2;
    y_temp_1 = y_temp_2;
}

Figure 0.0: cordic_arctan.c as described in slide deck (more or less)

The CORDIC algorithm uses a fixed set of elementary angles, referred to in this project as a Z-table. The values of such table are calculated as:

Figure 2. Precomputed arctangent values for CORDIC iterations, adapted from [1].

Algorithm Design

(In the algorithm design section, the details of the dataflow are presented, including details such as register use, bitwidth of variables, and the decisions involving rounding.)

Some information that applies to all of our optimization functions:

The input and output of the functions are the 32-bit integer representations of 64-bit double values. Initial x and y double values declared in the main() function are converted into their 32-bit integer representations, then the output z, in our case the result of the arctan(x/y), which is a 32-bit integer, is then converted back to 64-bit double inside the main() function.

The main() function is then used to initialize x and y values, z_table values, and retrieve arctan output (z) values. And for the purposes of testing and confirmation, main also counts the amount of cycles for each optimized function call as well as printing out the result to ensure that the implementation is correct.

It should also be mentioned at this point, that within our initial CORDIC documentation, both arctan and a sin/cos combined function were discussed, but as these functions operate very similarly on an instruction level, only optimizations were applied to the CORDIC arctan function.

Initial Code Design (cordic_arctan.c)

The initial code design used int for all variables. int variables assume the word size of the controller, and in our case that resolves to a signed 32 bit integer. Function input includes int X param, int Y param, int[] Z_table, and int* Z which is a pointer to the final output of the function. Temporary variables are used to accumulate the x, y, and z variables.

After initializing local variables, a loop is called which contains a single decision branch applying an addition or subtraction to the z (output) from the z_table values.

The register keyword was not used, it was assumed before compiling that the local variables would be stored in memory.

Register Code Design (cordic_register_arctan.c)

This function design is the exact same as the initial code design except the local variables use the register keyword. This does not guarantee that the compiler will reserve the registers, but is merely a suggestion. Storing the local variables, that is, everything needed in the function besides the z_table, inside registers will save costly load and store operations.

Unroll Code Design (cordic_arctan_unroll2.c)

This function also starts with the initial code implementation, but within the loop, that previously iterated 15 times, we now have a single loop unroll that results in 7 iterations in a loop and a final iteration outside the loop (due to 15 being an odd number). This not only saves on the loop overhead that is called each iteration, but it also can facilitate pipelining.

CMSIS Code Design (cordic_arctan_CMSIS.c)

This code design is a little bit different when it comes to the initial variables. Instead of having x and y as 32-bit integer representations, they are instead converted to 16-bit integer representations. The local variables then become a 32-bit int of which x and y are packed into, a local z variable, as well as an x and y temp variable for when the 32-bit x/y variable gets unpacked each iteration of the loop. 16-bit addition (QADD16) and subtraction (QSUB16) functions can then be used with the unpacked values.

Predicate Code Design (cordic_arctan_predicate.c)

This section of the code was designed to minimize any comparison operations that might cause pipeline disruptions due to unexpected branching in the assembly. Bit masking is used to negate operations that are deemed not necessary at runtime, to ensure that the same control path is taken during every execution of the code.

Naive Optimized Code Design (cordic_arctan_naive_optimized.c)

This naive design implementation, after looking at the effectiveness of the above optimizations, is the result combining the Register Code Design and the Unroll Code Design.

Final Optimized Code Design (cordic_arctan_optimized_full_unroll.c)

After looking at the assembly code from the “O3” compiler optimization of the Initial Code Design, several more changes were added to the Naive Optimized Code Design. Firstly, a complete loop unroll was used in place of the single unroll. This means that each of the 15 iterations exist in the C code. Secondly, as there was no need to store the z table values so as to iterate through them dynamically, the same values could be referenced directly as literals. This means that if the register keyword is adhered to within the function, hardly anything should exist in memory at all.

Figure 3. UML of the CORDIC control flow

C Code + Optimization

(The C Code and optimization section will explore the various techniques that were applied in this project, expanding on how cycles were saved.)

Using register Key Word

By using the register keyword before a variable of 32 bits or less, the compiler can be motivated to use registers as the variables instead of putting them on the stack and fetching and storing them in memory. Store and Load operations are relatively costly operations at 2+ cycles a call. These Load and Store operations could ideally be avoided all together, saving many clock cycles.

Loop Unrolling

Reasons for loop unrolling include reduced loop control overhead and increased efficiency with pipelining.

The Cortex-M4 CPU utilizes a 3-stage pipelining strategy of Fetch, Decode, and Execute. This means that in optimal code, 3 instructions can be executed in parallel.

Figure 4. From the STM32 manual, we get a brief visual of the Cortex-M4 pipeline with the 3 pipeline operations in an optimised state.

By unrolling in certain circumstances, we can remove overhead of the loop AND reduce waiting or NOP usage resulting in less overall clock cycles.

CMSIS-DSP Intrinsics

The hardware for this project allowed for the use of the ARM CMSIS, a standardized layer between the user specific C code and the ARM Cortex-M hardware, that emphasizes portability, readability, performance, and faster development. CMSIS also provides developers access to a whole suite of debugging tools such as the DWT (Data Watchpoint and Trace unit) and SWO (Serial Wire Output), which provided for higher visibility on the project, concerning both debugging and performance evaluations. The DWT->CYCCNT provided access to the number of cycles which was used to quantify the performance of the code during the application of optimization techniques.

CMSIS-DSP is a library of optimized math functions used for digital signal processing. These functions can allow for SIMD (Single Instruction, Multiple Data) design. This type of arithmetic essentially packs several numbers into a single register, so that operations take place on multiple pieces of data in a single instruction, which increases parallelism by making better use of the pipeline.

Intrinsic Name Description
__PKHBT(x, y, 16) Pack Halfword Bottom Combines two 16-bit values into a single 32-bit register
__SXTB16 Sign Extend Byte to Halfword Takes each byte within the lower and upper halfword of the input and sign-extends it to form two 16-bit values in the output register.
__QADD Saturating Addition Performs addition, ensuring that fixed-point calculations would not wrap around on overflow.
__QSUB Saturating Subtraction Performs subtraction, clamping the result to prevent overflow or underflow instead of allowing wrap-around.

Figure 5. A table outlining the DSP intrinsics explored in development.

Branch Elimination via Predicated Operations

Embedded systems heavily rely on efficient pipelines to fetch, decode, and execute instructions at the same time. Conditional statements in C (IF/ELSE) are translated into assembly, and will either form an IT block or a conditional branch; both using a comparison operation to decide control flow. Unpredictable branch outcomes have the possibility to stall the pipelining as pipeline flushing or NOP operations may occur, which results in cycles being wasted.

Replacing conditional statements with predicate based branchless operations allow for the system to run the same operations without making a comparison, and the need for checking flags. Bit masking will allow these conditions to be converted into a mask, which can select, modify or negate values and run these operations every time, resulting in a more stable control flow.

When evaluating the functions using condition and predicated operations in this project, it was found that the conditional statements resulted in less cycles being used. The version using predicate based operations required a mask to be created and applied to several values, and having to manage intermediate results. These tasks were necessary to be applied during each loop iteration. This extra load on the ALU resulted in a larger cost than the small branch penalty in the unoptimized CORDIC algorithm.

Replacing Z-Table Calculation with Pre-Initialized Values

The initial code for the project used an implementation of the Z-table calculation algorithm to run before the CORDIC algorithm was used to populate the Z-values. Saving these values and simply assigning them to an array used 0.0683% of the cycles required to calculate them manually each time, from 105301 cycles required to 72.

Compiling C and Comparing Assembly

While exploring options to utilize optimization techniques, it was proven to be useful to enter commands in the terminal which provided outputs for the associated assembly files. The command used was as follows:

arm-none-eabi-gcc -S -mcpu=cortex-m4 -mthumb cordic_arctan_predicate.c

While the last argument of this command changed depending on which function was being tested, it allowed for the exact understanding of the use of the stack, branches, and any additional operations. On top of the DWT, this proved to be an excellent tool in providing metrics regarding the usefulness or futility of the changes being applied to the source code. This examination allowed for the identification of changes in instruction count, memory accesses, and control flow that could confirm whether a given optimization such as loop unrolling, predicate-based branching, or the use of CMSIS intrinsics was going to save or cost cycles before being evaluated in other bench tests.

When paired with performance measurements obtained from the Data Watchpoint and Trace (DWT) unit, this method provided both a quantitative and qualitative assessment of each modification. The DWT offered precise timing data, while the assembly analysis clarified the underlying reasons for observed performance gains or losses. This combination ensured that optimization decisions were supported by both empirical data and a clear understanding of the generated code on the Cortex-M4 architecture.

Final Optimized Code block

Figure 6. On the left, we have a condensed version of the Final Optimization Design. On the right, there are the condensed assembly instructions.

As can be seen in the assembly code, except for the end when assigning the value to *z, there are no longer any load and store operations. Load and store operations take at least 2 cycles depending on if they are efficiently pipelined or not. Cmp, add, mov, sub, asr, and blt operations are all cheap 1-cycle operations according to the Cortex-M4 instruction set.

Results

Figure 7. Output of the testbench showing output value and average cycles per function execution.

Let’s explain the above figure that displays our results.

We see that the cost to simply reset the counter and immediately read the value is 3 cycles, so we can assume at least a 3 cycle discrepancy for each measurement. Below, we will inspect each function’s cycle count and output individually.

Library Arctan function

The math library atan() function, averaged over 100 calls, takes about 632 cycles to complete the computation. The cycle count will be one of our comparators for the success of our optimized functions. The output of this function will be one of our benchmarks for the precision of our refactored functions.

Basic Un-optimized CORDIC Arctan function

The basic, un-optimized CORDIC arctan function, as laid out in our source documentation, on average, takes about 981 cycles to complete.

Compared to the library atan() function, this is approximately 1.55 times costlier, which is a surprise. However, there could be some optimizations within the library function, or potentially, the simplicity of the values chosen for X and Y, could have led to the library function being quicker. This initial function output deviates from the expected value by about 1%. Considering the precision necessary for the application, and if we assume the calculations are not compounded, this is good.

CMSIS CORDIC Arctan Function

Using the CMSIS implementation to pack and unpack the 16-bit integers into a 32-bit container as well as using the QADD/SUB16 16-bit arithmetic operations did not appear to improve the cycle count. In fact, compared to the initial CORDIC function, there was about a 67% increase (formula at Appendix A), likely largely in part due to the expensive packing and unpacking that was implemented on each loop iteration. We also saw a decrease in precision, more than likely because of the reduced precision of using 16-bit integers.

Predicate CORDIC Arctan Function

The predicate function, like the CMSIS function, did not yield the results that we had hoped for with a 60% increase in clock cycle usage. However, it did produce the same value as the initial CORDIC function.

Register CORDIC Arctan Function

The easiest change that resulted in the largest improvement was seen in the register CORDIC arctan function. There was approximately 59% decrease in clock cycles while maintaining the same precision.

It can be assumed then, by looking at the result of this measurement (without needing to look at the assembly), that the local variables were indeed stored in registers.

Unrolled CORDIC Arctan Function

Here, we also saw a decrease in clock cycle usage while still outputting the same value. This decrease at ~6% was not as substantial as the register keyword implementation, but a decrease nonetheless.

Naive Optimized CORDIC Arctan Function

Using the results from above, we combined the optimizations that showed a decrease in clock cycle usage and created our first optimized solution which later on took the addition of “naive” into its title. The output is still the same value which is good, and we saw a ~61% decrease in clock cycle usage due to our choice of optimizations…

But…

After running compiler optimizations on the initial CORDIC Arctan function, we considered if we could do better.

O1, O2, O3, and Ofast Compiler Optimizations on Initial Un-optimized CORDIC function

They all output the same value, so we can assume that nothing integral to calculation consistency was optimized away. The clock cycle percentage decreases against the initial CORDIC function are as follows:

  • O1 = ~72%
  • O2 = ~74%
  • O3 = ~81%
  • Ofast = ~81%

Wanting to avoid any potential optimization that may reduce precision, we decided to look into some of the optimizations that O3 employed. The largest optimization that we saw was one that we already partially implemented, and that was a complete loop unroll. Once the loop was gone, and all 15 iterations were laid out in C code, it became apparent that we could remove the z_table all together, using literals instead. This would allow for almost everything to exist inside the CPU without needing the load and store operations to access memory.

Final Optimized CORDIC Arctan Function

And with these couple optimizations added to the naive optimization, we were able to get an average just slightly lower than those of O3 and Ofast at about 4 cycles still at about ~81% reduction in clock cycle usage.

Moreover, outside of the actual CORDIC function optimizations, at the beginning of the project, the Z-table calculation was reconfigured from a runtime calculation to a preinitialized look-up table (LUT). The cycle cost of the runtime calculation was 105301 cycles, while creating the look-up table required only 72 cycles. If one were to include those changes in the final percentage change, the initial value of the unoptimized CORDIC with runtime Z-table calculations would be 106282, and the optimized CORDIC using the preinitialized look-up table using 256 cycles, resulting in a ~99.75% reduction in clock cycle usage.

Improvements

(Further possible developments in implementation and optimization to the project are discussed in the improvements section.)

Increased Precision

Our primary focus throughout this project was on reducing the amount of cycles called per function; however, we consistently had an error (compared to the atan() library function) of about 1%. If this error were to be too great for a client, we would have to consider options such as a larger bit-width integer and more values held in the z_table leading to more rotation iterations.

Remove Branches

Although it may be difficult to tell the compiler to use the predicate operations, we may be able to enforce branchless operations by using a bit mask operation with XOR to dynamically replace addition and subtraction operations.

Figure 8. Example of removing branches

Use Restrict Keyword with Pointer Variable

Use of the restrict keyword tells the compiler that the pointer is unique and can therefore optimize the code as much as it likes because it is aware of all possible modifications to the pointer and its contents because it has the entire context.

Figure 9. Example of using restrict keyword with the pointer to z_d

Eliminate Temporary Variables

Not only do variables take up a register (in our case) that could be used for something else, if using them to store the results from an addition or subtraction, they will incur an extra move operation. Attempting to remove temporary variables in preference of self-assignment could result in more clock cycles saved.

Figure 10. Example of removing a temp variable for self-assignment

Use inline keyword

Using the inline keyword within the function declaration tells the compiler to place the function code directly inside the calling code block. This saves on the overhead associated with calling the function.

Figure 11. Example of a function declaration using inline

Peripheral Hardware Integration

While the current implementation of the project achieves low-latency trigonometric functions, the algorithm is performing calculations on hardcoded inputs, instead of external, real-time data. The speeds achieved are not likely to be replicated if there were a need to interface with multiple inputs outside of values written in the code itself. Moreover, many real world applications of such CORDIC would utilize more than one data stream, and therefore benefit from true parallelism.

Changes to such applications might require consideration of a different microprocessor. One with compatibility with specific CMSIS functions, such as onboard vector calculation abilities to better utilize design paradigms such as SIMD.

Such applications would require interfacing with SPI/DMA on the hardware, which will also create issues surrounding the timing of gathering inputs and performing calculations. External arithmetic hardware such as a DSP (Digital Signal Processor), FPGA or even an external microcontroller with more FPU capabilities might serve to delegate the repeated algorithms outside of the main system. This would allow for increases in speed, precision, and extra functionality.

Safe Coding Practices

Barr-C is a set of coding best practices specifically designed for embedded systems that emphasize reliability, predictability, and portability. This project implemented several of these techniques to ensure safety such as designing single responsibility functions, marking read-only input values as const (demonstrated in the LUT), implementing the restrict keyword on non-aliasing pointers for better code generation, replacing numeric literals with defined constants for readability and maintainability, using signed saturation functions to prevent overflow/underflow in fixed point, and employing unit tests, quantifying errors.

Conclusions

This project successfully demonstrated that significant performance gains can be achieved in a CORDIC arctangent implementation through targeted, low-level optimizations while preserving acceptable numerical precision. Starting from a baseline of approximately 982 cycles, we were able to reduce execution time to ~184 cycles through a combination of manual optimizations and compiler-assisted enhancements — an ~81% improvement over the initial unoptimized version. When factoring in the transition from a runtime-generated Z-table to a preinitialized lookup table, the total reduction in cycle count reached nearly 99.75%.

The most impactful optimizations were the elimination of memory accesses through full loop unrolling, replacement of the Z-table with compile-time constants, and ensuring variables remained in registers. These techniques, along with careful consideration of compiler optimization strategies, allowed the final implementation to achieve performance on par with aggressive compiler settings such as O3 and Ofast, without introducing loss of precision beyond the baseline 1% error margin relative to the standard library atan() function.

Despite the fact that several of the optimization techniques that were explored ended up costing the system more cycles and overall reducing efficiency, some of these techniques would dramatically improve a system with real world application. Where SIMD design techniques only increased overhead in our single calculation system, any system with multiple streams of input would definitely benefit from such design changes.

While the primary focus was on speed, several potential avenues for further improvement have been identified. Increasing precision through expanded bit-width and additional rotation iterations, enforcing branchless execution, eliminating unnecessary temporaries, and leveraging keywords such as “restrict” and “inline” could yield additional efficiency. Furthermore, integrating the algorithm into a real-time, peripheral-driven workflow and exploring parallel data handling would extend its applicability to practical embedded systems scenarios.

Overall, this work not only achieved its performance objectives but also provided valuable insights into the trade-offs and opportunities in optimizing mathematical algorithms for microcontrollers. The resulting implementation offers a strong foundation for both higher-precision variants and broader system integration in future development.

References

[1] J. T. Arbaugh, “Table Look-up CORDIC: Effective Rotations Through Angle Partitioning,” dissertation, 2004

[2] AN5325 - how to use the Cordic to perform mathematical …, https://www.st.com/resource/en/application_note/an5325-how-to-use-the-cordic-to-perform-mathematical-functions-on-stm32-mcus-stmicroelectronics.pdf (accessed Jun. 1, 2025).

[3] “STM32F0DISCOVERY,” STMicroelectronics, https://www.st.com/en/evaluation-tools/stm32f0discovery.html (accessed May 31, 2025).

[4] STM32 cortex®-M4 mcus and MPUS Programming manual, https://www.st.com/resource/en/programming_manual/pm0214-stm32-cortexm4-mcus-and-mpus-programming-manual-stmicroelectronics.pdf (accessed Jun. 1, 2025).

[5] https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf

[6] J. E. Volder, “The Birth of Cordic,” The Journal of VLSI Signal Processing, vol. 25, no. 2, pp. 101–105, 2000. doi:10.1023/a:1008110704586

Appendices

Appendix A The following formula was used to calculate the percentage change:

Appendix B