Writing Optimized C Code For Microcontroller Applications

21
WRITING OPTIMIZED C CODE FOR MICROCONTROLLER APPLICATIONS By Wilson Chan Toshiba America Electronics Components, Inc. Email: [email protected]

Transcript of Writing Optimized C Code For Microcontroller Applications

Page 1: Writing Optimized C Code For Microcontroller Applications

WRITING OPTIMIZED C CODE FOR MICROCONTROLLER APPLICATIONS

By Wilson ChanToshiba America Electronics Components, Inc.

Email: [email protected]

Page 2: Writing Optimized C Code For Microcontroller Applications

INTRODUCTION

If you have a microcontroller project that requires a small program, or theapplication has very limited memory resource, you may prefer to use Assembly languagefor programming. Nowadays, as the performance of microcontrollers has beenimproving, application systems have become larger and more complicated. As a result,programs can no longer be coded in Assembly language easily. To improve developmentefficiency, many microcontroller based products are programmed in C. Generally, whenprograms are written in C and compiled by a C compiler, the code efficiency decreasescompared to an Assembly language program. In order to improve code efficiency, mostC compilers make use of optimization techniques. Often, the output code is optimizedfor size, or execution speed, or both. Besides relying on the C compiler to generateefficient code, the programmer can lend a helping hand to the C compiler by adoptingcertain programming styles. This paper provides an overview of common optimizingtechniques used by C compilers and recommend C programming guidelines that willresult in optimized code for microcontroller applications.

PROGRAMMING MODEL

Some microcontrollers do not have hardware support for a C stack. If you plan todevelop your embedded applications in C, you should select a microcontroller with astack-based architecture. If the microcontroller has dedicated address-specifying/indexregisters, they will also help the C compiler to generate more efficient code.

In this paper, we’ll use a C compiler for a microcontroller which has aprogramming model as shown in Figure 1 to illustrate the effect of various optimizationmethods on the quality of the generated machine code. The W, A, B, C, D, E, H, Lregisters are 8-bit general purpose registers. They can be used in pairs as four 16-bitgeneral purpose registers: WA, BC, DE and HL. The IX and IY registers are special-purpose 16-bit registers used as address-specifying registers under register indirectaddressing mode and as index registers under index addressing mode. The SP register isa 16-bit stack pointer. The PC register is a 16-bit program counter. The PSW is a 16-bitprogram status word register. JF is the jump status flag, ZF is the zero flag, CF is thecarry flag, HF is the half carry flag, SF is the sign flag, and VF is the overflow flag.

W AB CD EH L

IXIYSPPC

PSW

General-Purpose :

8bit X 8

Special-Purpose :

16bit (IX, IY, SP, PC)

PSW = JF, ZF, CF,HF, SF, VF

Figure 1 Programming Model

Page 3: Writing Optimized C Code For Microcontroller Applications

COMMON OPTIMIZATIONS IN C COMPILERS

This section describes common optimization techniques often found in optimizingC compilers.

Convolution and Propagation of Constants

This optimization method propagates constants in place of variables wheneverpossible and computes any constant expression at compile time rather than at run time.Consider the C program example in Figure 2. For the C statement in line 3, the Ccompiler computes the addition of two constants at compile time rather than at executiontime. Similarly, for C statements from lines 4 to 6, the constant in line 4 is propagated tolines 5 and 6. This optimization technique reduces program size and increases executionspeed.

1 _test:2 ld WA,0x13 ld BC,0x24 add WA,BC5 ld (_i),WA6 ld WA,0x17 ld IY,_a8 ld (IY),WA9 inc WA10 inc WA11 ld IX,_b12 ld (IX),WA13 ld WA,(IY)14 add WA,(IX)15 ld (_c),WA16 ret

1 int i, a, b, c;2 test(){3 i = 1 + 2;4 a = 1;5 b = a + 2;6 c = a + b;7 }

1 _test:2 ld WA,0x33 ld (_i),WA4 ld WA,0x15 ld (_a),WA6 ld WA,0x37 ld (_b),WA8 ld WA,0x49 ld (_c),WA10 ret

C Language Program

Without Optimization With Optimization

Figure 2 Convolution and Propagation of Constants

Page 4: Writing Optimized C Code For Microcontroller Applications

Dead-Code Elimination

This optimization method deletes unused variables at compile time. Consider theC program example in Figure 3. With dead-code elimination optimization, the Ccompiler eliminates the C statement in line 3.

1 _test:2 ld WA,0x13 xor WA,WA4 ret

1 int test(){2 int a;3 a = 1;4 return 0;5 }

1 _test:2 xor WA,WA3 ret

C Language Program

Without Optimization With Optimization

Figure 3 Dead-Code Elimination

Page 5: Writing Optimized C Code For Microcontroller Applications

Strength Reduction

This optimization method replaces expensive operations with less expensive ones.Consider the C program example in Figure 4. The most efficient code is a left-shiftinstead of an integer multiplication. Without optimization, the generated code makes acall to a multiplication function supplied by the C run-time library to compute themultiplication which takes much longer than a left-shift operation.

1 _test:2 ld BC,0x23 ld WA,(_i)4 cal C87C_muli5 ld (_i),WA6 ret

1 int i;2 test() {3 i *= 2;4 }

1 _test:2 ld IY,_i3 ld WA,(IY)4 shlca WA5 ld (IY),WA6 ret

C Language Program

Without Optimization With Optimization

Figure 4 Strength Reduction

Page 6: Writing Optimized C Code For Microcontroller Applications

Common Sub-Expression Elimination

This optimization method reduces the number of operations by using the firstoperation result in subsequent statements that contain the same operation. Consider the Cprogram example in Figure 5. The calculation of the sub-expression, i + 1, is reducedfrom two times to once with optimization.

1 _test:2 ld WA,(SP+0x7)3 inc WA4 shlca WA5 ld IX,WA6 add IX,(SP+0x3)7 ld WA,(SP+0x7)8 inc WA9 shlca WA10 ld DE,WA11 add DE,(SP+0x5)12 ld WA,(DE)13 add WA,(IX)14 ret

1 int test(int *a, int *b, int i)2 {3 return(a[i+1] + b[i+1]);4 }

1 _test:2 ld WA,(SP+0x7)3 inc WA4 shlca WA5 ld IX,WA6 add IX,(SP+0x3)7 ld DE,WA8 add DE,(SP+0x5)9 ld WA,(DE)10 add WA,(IX)11 ret

C Language Program

Without Optimization With Optimization

Figure 5 Common Sub-Expression Elimination

Page 7: Writing Optimized C Code For Microcontroller Applications

Code Motion

This optimization method is often used to optimize loops. Generally speaking,most of the program execution time is spent in loops. Therefore, it is important for Ccompilers to provide optimization for loops. Consider the C program example in Figure6. First, the invariant operation, b + c, is moved outside of the loop. Second, arrayaddress calculations that use an induced variable (updated at each iteration) are reducedto incrementing an accumulator. The optimized code will not only be smaller in size (26bytes versus 39 bytes), but will also execute much faster.

1 _test:2 ld BC,0x03 cmp BC,0xa4 j sge,L15 L2:6 ld WA,BC7 shlca WA8 ld DE,WA9 add DE,_a10 ld WA,(_b)11 add WA,(_c)12 ld (DE),WA13 inc BC14 cmp BC,0xa15 j slt,L216 L1:17 ret

1 int a[10], b, c;2 test(){3 int i;4 for (i = 0; i < 10; i++)5 a[i] = b + c;6 }

1 _test:2 ld BC,(_b)3 add BC,(_c)4 ld WA,_a5 ld DE,WA6 add WA,0x147 L2:8 ld (DE),BC9 inc DE10 inc DE11 cmp DE,WA12 j lt,L213 ret

C Language Program

Without Optimization With Optimization

Figure 6 Code Motion

Page 8: Writing Optimized C Code For Microcontroller Applications

Caching Of Memory Access

This optimization method reduces the number of memory access, thus speedingup program execution. Consider the C program example in Figure 7. With optimization,the number of memory access is reduced from three to one. Also, the resultant codeoccupies less memory (12 bytes versus 20 bytes).

Switch Table Optimization

Switch statements are very common in C programs. This optimization calls forthe C compiler to analyze the nature of the case values in the switch statement, thendecide on the optimum way to implement the switch statement. Consider the C programexamples in Figure 8. The C compiler implements the switch statement in the C program1 as a series of compare and branch instructions. For the C program 2, the C Compileruses a different coding style to improve code efficiency.

1 _test:2 ld WA,(_a)3 cmp WA,0x14 j t,L15 ld WA,(_a)6 ret7 L1:8 ld WA,(_a)9 dec WA10 ret

1 int a;2 int test() {3 if (a != 1)4 return a;5 else6 return a - 1;7 }

1 _test:2 ld WA,(_a)3 cmp WA,0x14 j t,L15 ret6 L1:7 dec WA8 ret

C Language Program

Without Optimization With Optimization

Figure 7 Caching of Memory Access

Page 9: Writing Optimized C Code For Microcontroller Applications

1 _test1:2 ld A,(SP+0x3)3 ld W,0x04 cmp WA,0x35 j t,L106 cmp WA,0x27 j t,L98 cmp WA,0x19 j f,L610 ld (_j),0x111 ret12 L9:13 ld (_j),0x214 ret15 L10:16 ld (_j),0x317 L6:18 ret

1 unsigned char j;2 test1(unsigned char i) {3 switch(i) {4 case 1:5 j = 1;6 break;7 case 2:8 j = 2;9 break;10 case 3:11 j = 3;12 break;13 default:14 break;15 }16 }

1 S50000:2 db 13 db 24 db 35 db 46 _test2:7 ld A,(SP+0x3)8 ld W,0x09 dec WA10 cmp WA,0x311 j gt,L1412 ld IX,S5000013 add IX,WA14 ld A,(IX)15 ld (_j),A16 L14:17 ret

C Language Program 1

With Optimization With Optimization

1 unsigned char j;2 test2(unsigned char i) {3 switch(i) {4 case 1:5 j = 1;6 break;7 case 2:8 j = 2;9 break;10 case 3:11 j = 3;12 break;13 case 4:14 j = 4;15 break;16 default:17 break;18 }19 }

C Language Program 2

Figure 8 Switch Table Optimization

Page 10: Writing Optimized C Code For Microcontroller Applications

Microcontroller Specific Optimization

Every microcontroller has a specific instruction set. The types of instructionsvary according to the particular microcontroller. The C compiler can generate moreefficient code by using instructions specific to a microcontroller. Consider the C programexample in Figure 9. The C compiler takes advantage of the bit manipulation instructionsprovided by the microcontroller to generate efficient code.

1 _test:2 ld IY,_a3 clr (IY).04 set (IY).25 ret

1 unsigned char a;2 test( ) {3 a &= ~0x1;4 a |= 0x4;5 }

C Language Program

With Optimization

Figure 9 Microcontroller Specific Optimization

Page 11: Writing Optimized C Code For Microcontroller Applications

PROGRAMMING GUIDELINES FOR EFFICIENT C CODE

It is important to choose a good optimizing C compiler for your microcontrollerproject. However, besides relying on the C compiler, you can also lend the C compiler ahelping hand in generating efficient code by observing certain programming guidelines.This section describes these programming guidelines.

Use Microcontroller Specific C Language Extensions

In order to facilitate the portability of C programs, a general rule of thumb is touse ANSI C constructs as much as possible, especially function prototyping. For casesthat require tighter code, use C compiler’s language extensions – to access hardware,locate variables in memory, specify interrupt service routines, etc. The disadvantage isthat using language extensions renders the C program non-portable.

/* Function Prototype Examples */void void_ptr_func(); /* function returning nothing */char *char_ptr_func(); /* function returning a pointer to char*/int ifunc( char B, int *DEW ); /* function returning an int, 1st parameter is a char, 2nd parameter is a pointer to int */

/* Prototype declaration for interrupt processing functions.*/extern void int5(), inttc1(), intsio2(), int3(), inttc4(),intsio1(), inttc3(),int2(), inttbt(), int1(),inttc1(), int0(), intwdt(), intsw(), reset();/* Definition of vector table. */#pragma section const interrupt near 0xffe0/* const or code can be used for section type */void (*intvector[])() = { int5, inttc1, intsio2,int3, inttc4, intsio1, inttc3, int2, inttbt, int1, inttc1, int0, intwdt, intsw, reset };/* Define pointer variable (or array) in vector table. *//* Initialize to set the function address (function name). */

Figure 10 Function Prototype Examples

Page 12: Writing Optimized C Code For Microcontroller Applications

Use C Compiler’s Optimization Options

The C compiler may provide multiple levels of optimization. For examples,optimization levels 0, 1, 2 and 3. Usually, the higher the level, the more optimizationmethods will be used by the C compiler to generate machine code. However, dependingon the coding style, it is possible that the size of generated code of level 3 is larger thanthat of level 0.

static void DecSafeTicks(int nSafeTicks) {__DI; /* special function - disable interrupts */nTicks -= nSafeTicks;__asm(" ei"); /* inline assembly - enable interrupts */

}/* An example of I/O variable definition using directive #pragma */#pragma io port0 0x00unsigned char port0;

/* An example of I/O variable definition using _io */unsigned char __io(0x00) port0;

Figure 11 C Language Extension Examples

Level Function0 Minimum optimization (default) Stack release absorption. Branch instruction optimization. Deletion of unnecessary instructions1 Basic block optimization Propagation of copying restricted ranges. Gathering of common partial expressions in restricted ranges.2 Optimization of more than basic blocks Propagation of copying whole functions. Gathering of common partial expressions of whole functions3 Maximum optimization Loop optimization and other miscellaneous optimization

Figure 12 C Compiler's Optimization Options Example

Page 13: Writing Optimized C Code For Microcontroller Applications

The C compiler may have an option that minimizes program size. Use it if it is available.A possible side effect of this option is that in certain situations, the resultant code may besmaller in terms of number of bytes, but it may execute slower than the code generatedwithout specifying the option.

Optimize Usage of Memory Spaces

Many microcontrollers have more than one memory space. For example, amemory space may be accessible with an 8-bit offset, another memory space requires a16-bit offset, still some memory space requires an address space modifier. You candecrease program size by explicitly locating the frequently used variables into thememory space that requires the minimum number of bytes for addressing.

Format-XS

FunctionSpecifies the output of minimum object code size.

DescriptionWhen this option is specified, part of optimization is skipped. The

default, when this option is not specified, is the output of code with executionspeed priority.

Figure 13 C Compiler Code Size Optimization Example

Example:int a0 = 0, a1; /* defaultmemory area */int __tiny at0 = 0, __tiny at1;/* to tiny area */

void fcn(void) { a1 = a0; at1 = at0;}

Opcode _fcn:; 16-bit address offsetE1000048 R ld WA,(_a0)F1000068 R ld (_a1),WA; 8-bit address offsetE00048 R ld WA,(_at0)F00068 R ld (_at1),WAFA ret

Figure 14 Optimize Usage of Memory Space Example

Page 14: Writing Optimized C Code For Microcontroller Applications

Use of the const Keyword

Using a constant is more efficient than a const variable in terms of executionspeed and program size. The C compiler can easily access a constant with immediateaddressing whereas it needs to use index addressing to access a const variable which isusually placed in ROM. However, if a function argument is a pointer to a read-onlystring (a const data object), using the const keyword to declare the pointer argument mayhelp the C compiler, in certain cases, to generate more efficient code.

Use Of Auto Variables

For temporary variables, do not declare them as global variables. Rather, declarethem as auto variables.

When global variables are passed as arguments in a function, use the arguments,not the global variables, in expressions within the function.

For global variables that are accessed frequently in a function, make a copy of theglobal variables as auto variables and use the auto variables within the function.Consider the C program examples in Figure 16. The C program 1 produces 30 bytes ofmachine code whereas the C program 2 generates 18 bytes of machine code.

Example:#pragma section constconst int ix = 3000; char y[] = { 'A','B' };static char z = 4;

A const declaration can only be used for variables defined in constor code sections, or external variables. Variables in const sectionsautomatically take the const attribute and no const declarationtherefore needs to be coded, if a const declaration is made forvariables in a const section. The const declaration also affectsexternal variables

Figure 15 Use of const Keyword Example

Page 15: Writing Optimized C Code For Microcontroller Applications

_test:ld (_j),0x0

L2:ld IY,_ald DE,(IY)ld WA,(IY)inc WAld (IY),WAld (DE),0x0inc (_j)cmp (_j),0x64j lt,L2ret

1 unsigned char *a, j;2 test( ) {3 for (j=0; j<100; j++)4 *a++ = 0;5 }

_test:ld BC,(_a)xor A,A

L6:ld DE,BCinc BCld (DE),0x0inc Acmp A,0x64j lt,L6ret

C Language Program 1

With Optimization With Optimization

1 unsigned char *a, j;2 test( ) {3 unsigned char *c, i;4 c = a;5 for (i=0; i<100; i++)6 *c++ = 0;7 }

C Language Program 2

Figure 16 Auto Variable Example

Page 16: Writing Optimized C Code For Microcontroller Applications

Use Unsigned Data Types

Use unsigned data types with the data size that matches the natural width of themicrocontroller’s registers. Also, use the smallest data type that can get the job done. Forexample, if you write a C program for an 8-bit microcontroller, use the unsigned chardata type in loop control operations, as subscript of arrays and as bit-field members. Ifthe C compiler enforces ANSI C’s integer promotion rule by default, specify an option todisable it. Otherwise, this ANSI C’s rule will enlarge the program size.

Example:struct field {unsigned char a:1;unsigned char b:3;unsigned char c:3;unsigned char d:1;};struct field array[10];

void fcn( ) {unsigned char i; for (i=0; i < 10; i++) { array[i].b = 5; array[i].c = 7; }}

_fcn: ld IY,_array ld IX,IY ld BC,IY add IY,0xa L4: ld DE,BC ld A,(DE) and A,0x8f or A,0x50 ld (DE),A or (IX),0xe inc BC inc IX cmp IX,IY j lt,L4 ret

Figure 17 Unsigned Data Type Example

Page 17: Writing Optimized C Code For Microcontroller Applications

Pass Function Arguments with Registers

If the C compiler supports register variables as auto variables and arguments of afunction, use the facility. The register variables are placed in microcontroller’s registers,which will result in smaller and faster program.

Example:char a_src[41] = {"Hello"};char a_des[41];

void testcpy(void) {register char *p_src = a_src;register char *p_des = a_des;

while (*p_src)*p_des++ = *p_src++;

*p_des = '\0';}

_testcpy: push HL ld IY,_a_src ld IX,_a_des j L3 L2: ld DE,IX inc IX ld HL,IY inc IY ld A,(HL) ld (DE),A L3: cmp (IY),0x0 j f,L2 ld (IX),0x0 pop HL ret

Figure 18 Register Type Example

Page 18: Writing Optimized C Code For Microcontroller Applications

Function type __adec1 is used to interface with assembly language functions. Upto three arguments are passed via the registers. Arguments not passed via the register arepassed via the stack like __cdecl type. Arguments are sequentially evaluated from theleft unlike __cdecl type. The merit of using __adecl type is that, in many cases, passingarguments via the register simplifies the coding of argument processing in assemblylanguage.

Example:int g1, g2, g3, g4, sum;

int __adecl RegParm( int p1,int p2, int p3, int p4) {g1 = p1;g2 = p2;g3 = p3;g4 = p4;return(p1+p2);}

void test( ) {sum = RegParm(2, -2, 88, -88);}

.RegParm: ld (_g1),WA ld (_g2),BC ld (_g3),DE ld DE,(SP+0x3) ld (_g4),DE add WA,BC pop DE pop BC j DE_test: ld WA,0xffa8 push WA ld DE,0x58 ld WA,0x2 ld BC,0xfffe cal .RegParm ld (_sum),WA ret

Figure 19 Function Type __adecl Example

Page 19: Writing Optimized C Code For Microcontroller Applications

Avoid Using Signed Variables, Long Types and Floating Points

Expressions involving these data types often result in calls to run-time libraryfunctions in the generated code. For the example in Figure 20, the C compiler generatesa function call to _fld_ff in the run-time library to support floating point type.

Example:float array4[10], vf;fcn1( ) {unsigned char i;for (i=0; i < 10; i++)

array4[i] = vf;}

_fcn1:push DEpush HLld (SP+0x4),0x0L2: ld A,(SP+0x4)ld W,0x0shlca WAshlca WAld BC,_array4ld HL,WAadd HL,BCld BC,_vfld WA,HLcal ._fld_ffinc (SP+0x4)cmp (SP+0x4),0xaj lt,L2pop HLpop DEret

Figure 20 Support Function in Run-Time Library Example

Page 20: Writing Optimized C Code For Microcontroller Applications

Avoid Operations Involving Different Data Types

The C compiler follows the ANSI C data type promotion rules to processexpression that involve different data types, resulting in extra object code and executiontime. In expressions that contain char and int, char gets promoted to int. In expressionsthat contain both signed and unsigned integers, signed integers are promoted to unsignedinteger. In expressions that contain floating point types, float gets promoted to double.

In expressions that contain only char, if the C compiler enforces ANSI C’s integerpromotion rule by default, specify an option to disable it. Otherwise, this ANSI C’s rulewill enlarge the program size.

Avoid Operations that Overloads the Stack

Avoid using recursive functions with many arguments and auto variables, andfunctions with variable length argument lists. If a structure or an array is used asargument in a function, pass a pointer to the data instead of passing the data on the stack.

Example:char c1;int i1, i2, i3;

fcn1( ) {i1 = c1 + i3;}

fcn2( ) {i1 = i2 + i3;}

_fcn1: ld A,(_c1) test A.7 subb W,W add WA,(_i3) ld (_i1),WA ret

_fcn2: ld WA,(_i2) add WA,(_i3) ld (_i1),WA ret

Figure 21 Operation Involving Different Data Types Example

Page 21: Writing Optimized C Code For Microcontroller Applications

CONCLUSION

We have described some of the commonly used optimizing methods of Ccompilers. Not all C compilers are created equal. Therefore, it is important to choose aC compiler that incorporates good optimizing methods in generating code for yourparticular microcontroller. We have also discussed a set of C programming guidelinesthat will help improve code efficiency of your microcontroller applications.

Example:struct s1 {char *text;int count;};extern struct s1 ays1[5];int sum;

int SumCount( struct s1 *p1,int p2) {int i, j;for (i=0, j=0; i < p2; i++,p1++)

j += p1->count;return(j);}

void test3( ) {sum = SumCount(ays1,(sizeof(ays1)/sizeof(structs1)));}

_SumCount: ld IX,(SP+0x3) xor IY,IY ld WA,IY ld DE,(SP+0x5) cmp DE,0x0 j sle,L1L2: ld BC,(IX+0x2) add WA,BC inc IY add IX,0x4 cmp IY,DE j slt,L2L1: ret_test3: ld WA,0x5 push WA ld WA,_ays1 push WA cal _SumCount ld SP,SP+0x4 ld (_sum),WA ret

Figure 22 Passing Pointer in Function Argument Example