How to write efficient and stable microcontroller code
Because the performance of a single-chip computer is different from that of a computer, it cannot be compared in terms of space resources, memory resources, and operating frequency. PC programming basically does not need to consider the space occupation and memory occupation, and the ultimate goal is to realize the function. For single-chip microcomputers, it is completely different. Generally, the Flash and Ram resources of single-chip microcomputers are measured in KB. It is conceivable that the resources of single-chip microcomputers are pitiful. For this reason, we must try to squeeze all its resources. To maximize its performance, program design must
follow the following points for optimization:
-
Use as small a data type as possible.
If you can use unsiged, you don't need signed; if you
can use char, you don't need int; if you
can use floating, you don't need it.
Can use bit manipulation without arithmetic. -
Using self-adding and self-subtracting instructions
Usually using self-adding, self-subtracting instructions and compound assignment expressions (such as a-=1 and a+=1, etc.) can generate high-quality
program code, and compilers can usually generate inc and dec with instructions like a=a+1 or a=a-1
, there are many C compilers that generate two to three byte instructions. -
Reducing the intensity of the
operation can replace the original complex expression with an expression that is less computationally intensive but has the same function.
(1) The remainder operation
N= N %8 can be changed to N = N &7
Note: Bit operations can be completed in only one instruction cycle, and most of the "%" operations of C compilers are
completed by calling subroutines , the code is long and the execution speed is slow. Usually, the only requirement is to find the remainder of the square of 2n, which can be replaced by the method of bit operation.
(2) The square operation
N=Pow(3,2) can be changed to N=3*3
Note: In the microcontroller with built-in hardware multiplier (such as the 51 series), the multiplication operation is much faster than the square operation, because the floating The square of the number of points
is realized by calling the subroutine. The subroutine of the multiplication operation has shorter code and faster execution speed than the subroutine of the square operation.
(3) Use displacement instead of multiplication and division
. N=M*8 can be changed to N=M<<3
N=M/8 can be changed to N=M>>3
Note: Usually, if you need to multiply or divide by 2n, you can Use the shift method instead. If you multiply by 2n, you can generate left
-shift code, while multiplying by other integers or dividing by any number calls the multiplication and division subroutine.
The code obtained by the shift method is more efficient than the code generated by calling the multiplication and division subroutine. In fact, as long as it is multiplied or divided by an integer, the result can be obtained by shifting.
For example, N=M*9 can be changed to N=(M<<3)+M;
(4) The difference between self-addition and self-subtraction.
For example, the delay function we usually use is realized by self-addition.
void DelayNms(UINT16 t)
{
UINT16 i,j;
for(i=0;i
define MAX(A,B) {(A)>(B)?(A):(B)}
Note: The difference between functions and macro functions is that macro functions take up a lot of space, while functions take up time. What everyone needs to know is that the
function call uses the system stack to save data. If the compiler has a stack check option, usually some assembly
statements are embedded in the function header to check the current stack; at the same time, the cpu also To save and restore the current scene when the function is called, push and pop the stack, so the
function call needs some cpu time. The macro function does not have this problem. The macro function is only embedded into the current program as a pre-written code, and
does not generate a function call, so it only takes up space. This phenomenon is especially prominent when the same macro function is frequently called.
-
Use Algorithms Appropriately
If there is an arithmetic problem, find the sum of 1 to 100.
As programmers, we would click the keyboard without hesitation to write the following calculation method:
UINT16 Sum(void)
{
UINT8 i,s;
for(i=1;i<=100;i++)
{
s+=i;
}
return s;
}
Obviously everyone will think of this method, but the efficiency is not satisfactory. We need to use our brains to solve the problem by using mathematical algorithms to
improve the computational efficiency by a level.
UINT16 Sum(void)
{
UINT16 s;
s=(100 *(100+1))>>1;
return s;
}
The result is obvious, the same result with different calculation methods, the operating efficiency will be greatly different, so we need Maximize the efficiency of program execution through
mathematical methods. -
Replacing Arrays with Pointers
In many cases, pointer arithmetic can be used instead of array indexing, often resulting in faster and shorter code.
Compared to array indexing , pointers generally make code faster and take up less space. The difference is more pronounced when using multidimensional arrays. The following code works the same,
but the efficiency is not the same.
UINT8 szArrayA[64];
UINT8 szArrayB[64];
UINT8 i;
UINT8 *p=szArray;
for(i=0;i<64;i++)szArrayB[i]=szArrayA[i];
for(i=0;i <64;i++)szArrayB[i]=*p++;
The advantage of the pointer method is that after the address of szArrayA is loaded into the pointer p, only need to increment p in each loop. In the array indexing
method, the complex operation of subscripting the array based on the i value must be performed in each loop. -
The essence of forced conversion
C language The first essence is the use of pointers, and the second essence is the use of forced conversions. Proper use of pointers and forced conversions can not only improve
program efficiency, but also make programs more concise. It occupies an important position, and the following
five used as an explanation. Example
1: Convert signed byte integer to unsigned byte integer
UINT8 a=0;
INT8 b=-3;
a=(UINT8)b;
end mode), converts the array a[2] to an unsigned 16-bit integer value.
Method 1: Use the displacement method.
UINT8 a[2]={0x12,0x34};
UINT16 b=0;
b=(a[0]<<8)|a[1];
Result: b=0x1234
Method 2: Coercion.
UINT8 a[2]={0x12,0x34};
UINT16 b=0;
b= (UINT16 )a; //Forced conversion
result: b=0x1234
Example 3: Save the structure data content.
Method 1: Save one by one.
typedef struct _ST
{
UINT8 a;
UINT8 b;
UINT8 c;
UINT8 d;
UINT8 e;
}ST;
ST s;
UINT8 a[5]={0};
sa=1;
sb=2;
sc=3;
sd=4;
se=5;
a[0]=sa;
a[1]=sb;
a[2 ]=sc;
a[3]=sd;
a[4]=se;
Result: The contents stored in array a are 1, 2, 3, 4, and 5.
Method 2: Coercion type conversion.
typedef struct _ST
{
UINT8 a;
UINT8 b;
UINT8 c;
UINT8 d;
UINT8 e;
}ST;
ST s;
UINT8 a[5]={0};
UINT8 p=(UINT8 )&s;//cast
UINT8 i= 0;
sa=1;
sb=2;
sc=3;
sd=4;
se=5;
for(i=0;i
define Perror(FUN) printf("Err:%s %s %d: %s\n", FILE, func,LINE,FUN) The implementation of the linux-like perror function, here is the location of the file where the error occurred, the function where it is located, and it will cause The function FUN called on error.
Usage of # and ## in macros
define STR(s) #s
define CONS(a, b) int(a##e##b)
printf(STR(vck));//output vck
printf(“%d\n”, CONS(2,3));//2e3 output 2000