
Optimizing microcontroller programs usually refers to optimizing program code or execution speed. Optimizing code and optimizing speed are actually a unified contradiction. Generally, optimizing the size of the code will lead to an increase in execution time; if the execution speed of the program is optimized, it usually results in an increase in code size. It is difficult to achieve both simultaneously, and one can only grasp a balance point during the design phase.
Optimization of Program Structure
1 Program Writing Structure
Although the writing format does not affect the quality of the generated code, certain writing rules should still be followed when actually writing programs. A clearly written program is beneficial for future maintenance. When writing programs, especially for statements like While, for, do…while, if…else, switch…case, etc., or combinations of these statements, a “structured” writing format should be adopted.
2 Identifiers
User-defined identifiers in the program should not only follow naming conventions but also avoid using algebraic symbols (like a, b, x1, y1) as variable names. Instead, choose meaningful English words (or abbreviations) or Pinyin as identifiers to enhance program readability, such as: count, number1, red, work, etc.
3 Program Structure
C language is a high-level programming language that provides a complete standardized control structure. Therefore, when designing microcontroller application system programs using C language, it is essential to adopt a structured programming approach as much as possible. This makes the entire application system program structure clear and facilitates debugging and maintenance.
For a larger application program, the entire program is usually divided into several modules based on functionality, with different modules completing different functions. Each module can be written separately, and even by different programmers. Generally, the functionality of a single module is relatively simple, making design and debugging easier. In C language, a function can be considered a module.
Modular programming not only involves dividing the entire program into several functional modules but also emphasizes maintaining the relative independence of variables between modules, i.e., keeping modules independent and minimizing the use of global variables. For some commonly used functional modules, they can be encapsulated into an application library for direct invocation when needed. However, if modules are divided too finely, it may lead to reduced execution efficiency (the time taken to save and restore registers when entering and exiting a function consumes some time).
4 Defining Constants
During program design, for frequently used constants, if they are directly written into the program, once the value of the constant changes, it is necessary to find and modify all occurrences of that constant in the program, which inevitably reduces maintainability. Therefore, it is advisable to define constants using preprocessor commands to avoid input errors.
5 Reducing Conditional Statements
Use conditional compilation (ifdef) wherever possible instead of if statements, which helps reduce the length of the generated code during compilation.
6 Expressions
For parts of an expression where the order of operations is not clear or easily confused, parentheses should be used to explicitly specify their precedence. An expression should not be overly complex; if it is too complicated, it will be difficult to understand over time, which is not conducive to future maintenance.
7 Functions
For functions in the program, before use, the type of the function should be specified, ensuring it is consistent with the originally defined function type. Functions without parameters and return types should be marked with “void”. If code length needs to be shortened, some common program segments can be defined as functions. If execution time needs to be reduced, after debugging, some functions can be replaced with macro definitions. Note that macros should only be defined after debugging, as most compilers report errors only after macro expansion, which increases debugging difficulty.
8 Minimize Global Variables, Use Local Variables More
Global variables are stored in data memory; defining a global variable reduces the available data memory space for the MCU. If too many global variables are defined, it may lead to insufficient memory allocation by the compiler. Local variables, on the other hand, are mostly located in the internal registers of the MCU. In most MCUs, using register operations is faster than data memory, and the instructions are more flexible, which helps generate higher quality code. Moreover, the registers and data memory occupied by local variables can be reused in different modules.
9 Set Appropriate Compiler Options
Many compilers have several different optimization options. Before use, one should understand the meaning of each optimization option and then choose the most suitable one. Related articles: C Language Compilers in Embedded Development. Generally, once the highest level of optimization is selected, the compiler may excessively pursue code optimization, potentially affecting program correctness and causing runtime errors. Therefore, one should be familiar with the compiler being used and know which parameters will be affected by optimization and which will not.
Code Optimization
1 Choose Appropriate Algorithms and Data Structures
Familiarity with algorithm languages is essential. Replace slower sequential search methods with faster binary search or unordered search methods, and replace insertion sort or bubble sort with quick sort, merge sort, or heap sort to significantly improve program execution efficiency.
Choosing an appropriate data structure is also important. For example, using a lot of insert and delete instructions in a randomly stored dataset is much faster than using a linked list. Arrays and pointers are closely related; generally, pointers are more flexible and concise, while arrays are more intuitive and easier to understand. For most compilers, using pointers generates shorter code and higher execution efficiency than using arrays.
However, in Keil, the opposite is true; using arrays generates shorter code than using pointers.
2 Use the Smallest Data Types Possible
If a variable can be defined using a character type (char), do not use an integer type (int); if it can be defined using an integer type, do not use a long integer (long int); and if a floating-point type (float) is not necessary, do not use it. Of course, after defining a variable, do not exceed its scope. If a value is assigned beyond the variable’s range, the C compiler will not report an error, but the program’s runtime result will be incorrect, and such errors are difficult to detect.
3 Use Increment and Decrement Instructions
Typically, using increment and decrement instructions and compound assignment expressions (like a-=1 and a+=1) can generate high-quality program code. Compilers usually generate instructions like inc and dec, while using a=a+1 or a=a-1 often results in 2-3 bytes of instructions generated by many C compilers.
4 Reduce Computational Intensity
Replace complex expressions with simpler ones that perform the same function. For example:
-
Modulus Operation
a=a%8;
Can be changed to:
a=a&7;
Explanation: Bitwise operations can be completed in one instruction cycle, while most C compilers call a subroutine to perform the “%” operation, resulting in longer code and slower execution speed. Generally, for modulus of 2^n, bitwise operations can be used instead.
-
Square Operation
a=pow(a,2.0);
Can be changed to:
a=a*a;
Explanation: In microcontrollers with built-in hardware multipliers (like the 51 series), multiplication is much faster than square operations because floating-point square calculations are implemented through subroutine calls. In AVR microcontrollers with built-in hardware multipliers, such as ATMega163, multiplication can be completed in just 2 clock cycles. Even in AVR microcontrollers without built-in hardware multipliers, the subroutine for multiplication is shorter and faster than that for square operations. If calculating the cube, for example:
a=pow(a,3.0);
Change to:
a=a*a*a;
The improvement in efficiency is even more significant.
-
Using Shifts for Multiplication and Division
a=a*4;b=b/4;
Can be changed to:
a=a<<2;b=b>>2;
Explanation: Generally, if multiplication or division by 2^n is needed, shifts can be used instead. In ICCAVR, if multiplying by 2^n, left shift code can be generated, while multiplying by other integers or dividing by any number calls multiplication and division subroutines. Using shifts results in more efficient code than calling multiplication and division subroutines. In fact, any multiplication or division by an integer can be achieved using shifts, such as:
a=a*9
Can be changed to:
a=(a<<3)+a;
5 Loops
-
Loop Statements
For tasks that do not require the loop variable to participate in calculations, they can be placed outside the loop. These tasks include expressions, function calls, pointer operations, array accesses, etc. All unnecessary operations should be grouped together and placed in an init initialization program.
-
Delay Functions
Commonly used delay functions typically use increment forms:
void delay (void){unsigned int i;for (i=0;i<1000;i++);}
Change to decrement delay function:

The delay effect of the two functions is similar, but almost all C compilers generate 1-3 bytes less code for the latter function because almost all MCUs have instructions for zero transfer, and using the latter method can generate such instructions. The same applies when using while loops; using decrement instructions to control the loop generates 1-3 bytes less code than using increment instructions.
However, when there are instructions that read and write arrays through the loop variable “i” within the loop, using pre-decrement loops may cause array out-of-bounds issues, which should be noted.
-
While Loops and Do…While Loops
When using while loops, there are two forms:
unsigned int i;i=0;while (i<1000){i++; //user program}
Or:
unsigned int i;i=1000;do {i--; //user program} while (i>0);
In both of these loops, the code generated by the do…while loop is shorter than that generated by the while loop.
6 Lookup Tables
Generally, avoid performing very complex calculations in programs, such as floating-point multiplication, division, and square roots, as well as complex mathematical model interpolation calculations. For these time-consuming and resource-consuming calculations, it is advisable to use lookup tables and place the data tables in program memory. If directly generating the required table is difficult, it is also advisable to calculate it at startup and then generate the required table in data memory, allowing for direct lookup during program execution, thus reducing the workload of repeated calculations during execution.
7 Others
For example, using inline assembly and storing strings and constants in program memory are beneficial for optimization.
Article
https://www.cnblogs.com/tianqiang/p/9005538.html
