10 simple tricks to optimize your C code in small embedded systems

Benabadji

August 14, 2019

Benabadji

The following simple tricks in C are dedicated for program memory space optimization. These are particularly useful for embedded systems programmers dealing with low-cost 8-bit microcontrollers with limited flash memory. While assembly language is the best choice in this case, (almost all young) hardware engineers prefer nowadays to use C langage even when the program is a relatively short control program. These 10 tips have been tested with the XC8 compiler (v1.42, free edition) to show the amount of program memory bytes involved before and after applying each code snippet on an enhanced mid-range 8-bit XLP PICmicro (a PIC16F1824). These code snippets do nothing special other than offer a proof of the concept. More clever (and sophisticated) tricks can be found here in the references listed at the end of this article.

Trick #1: Use as possible, short mathematical expressions.

Example 1: Use instead: 
void main(void)
{
int a, b ;
 
  a = (b - 1) * 3 ;
}
void main(void)
{
int a, b ;
 
  a = (b - 1) ;
  a = a + a + a ;
}
Program memory bytes used = 50 Program memory bytes used = 25

Trick #2: Use multiple if instructions (without else) to replace switch instruction. Each case of the switch instruction (without default case) is replaced with an if instruction (without else). However, this trick is not relevant if the switch instruction is terminated with a default case.

Example 2: Use instead: 
void main(void)
{
int a, b ;
 
  switch (a)
  { case 0: b = a + 5 ; break ;
    case 1: b = a * 5 ; break ;
    case 2: b = a - 5 ; break ;
  }
}
void main(void)
{
int a, b ;
 
  if (a == 0)  b = a + 5 ;
  if (a == 1)  b = a * 5 ;
  if (a == 2)  b = a - 5 ;
}
Program memory bytes used = 79 Program memory bytes used = 74

Trick #3: Suppose we want to scan a specific set of keyboard touches (alphabetical only, case insensitive):

Example 3: Use instead: 
(
This makes code run faster, as well)
void main(void)
{
char chx ;
 
  if (chx == 'a'  ||  chx == 'A')  b = a + 5 ;
  if (chx == 'm' ||  chx == 'M') b = a - 10 ;
  if (chx == 't'   ||  chx == 'T')  b = a * 25 ;
}
void main(void)
{
char chx ;
 
  // to lower case,
  // caution, alphabetical only

  chx = chx | 0x20 ;
  if (chx == 'a')  b = a + 5 ;
  if (chx == 'm') b = a - 10 ;
  if (chx == 't')   b = a * 25 ;
}
Program memory bytes used = 84 Program memory bytes used = 77

Trick #4: Use if instruction without else, as possible. In this code, we assume that the condition (a == 0) happens more frequently (when using the design in the real world). Hence, we put at first : b = 0 ;  Then, we consider the alternate state (less frequent)  with a simple if instruction (without else).

Example 4: Use instead: 
void main(void)
{
int a, b, c ;
 
  if (a == 0) b = 0 ; else b = (a - c) * 100 ;
}
void main(void)
{
int a, b, c ;
 
 b = 0 ; if (a) b = (a - c) * 100 ;
}
Program memory bytes used = 64 Program memory bytes used = 63
(negligible, but often, a code source
contains many if/else instructions

Trick #5: Use one dimensionnal table, instead of two dimensions. It is relatively easy to transform a 2-dimension table (with row index i and column index j) into a 1-dimension table (with index k), assuming the index will be computed as follow: k = i * N + j ; where N is the total number of columns. (all indexes are zero based)

Example 5: Use instead: 
void main(void)
{
  const char *TabMois[] =
                           {"Jan","Feb","Mar","Apr","May","Jun",
                           "Jul","Aug","Sep","Oct","Nov","Dec"} ;
  char ch1 = TabMois[0][0], ch2 = TabMois[0][1],
          ch3 = TabMois[0][2] ;
}
void main(void)
{
  const char TabMois[] =
                   "JanFebMarAprMayJunJulAugSepOctNovDec" ;
  char ch1 = TabMois[0], ch2 = TabMois[1],
          ch3 = TabMois[2] ;
}
Program memory bytes used = 149 Program memory bytes used = 68

Trick #6: When computing expressions, use integer values instead of float values, as possible. Let suppose a battery voltage which value varies between 10.5 to 14.2 volts, with step of 0.1 volt. We want to compute the mean value for each minute (for a 1s data acquisition system). Accumulate the 10-bit numerical count measure into an integer variable, without converting it (each second) to a meaningful voltage measure (float value). This conversion will be done only once, in the last step.   

Example 6: Use instead: 
int Measure (unsigned int canal)
{  //more code involved here to configure the canal
   //…
    return ((ADRESH<<8) + ADRESL) ;
}
void main(void)
{
float Vbat, Vmean=0 ; int a, cnt = 60 ;
  while (cnt) 
  { if (TMR1IF) { Vbat = Measure(a) * 4.88 ;
    Vmean += Vbat ; cnt-- ; TMR1IF = 0 ; }
  }
  Vmean /= 60 ;
}
int Measure (unsigned int canal)
{  //more code involved here to configure the canal
   //…
    return ((ADRESH<<8) + ADRESL) ;
}
void main(void)
{
float Vmean ; int a, cnt, Vbat, Vbat60=0 ;
  while (cnt) 
  { if (TMR1IF) { Vbat = Measure(a) ; Vbat60 += Vbat ;
    cnt-- ; TMR1IF = 0 ; }
  }
  Vmean = (Vbat60 * 4.88) / 60 ;
}
Program memory bytes used = 841 Program memory bytes used = 570

Trick #7: for instruction may be written in several ways. Look at this construction:

Example 7: Use instead: 
void main(void)
{
  for (a = 10 ; a > 0 ; a--) b = c + a ;
}
void main(void)
{
  for (a = 10 ; a-- ; ) b = c + a ;
}
Program memory bytes used = 43 Program memory bytes used = 27

Trick #8: Rework a mathematical expression containing multiplication with a constant value k, and modify it so as to use division by 1/k

Example 8: Use instead: 
void main(void)
{
float a, b, c ;
  a = (b + c) * 0.1 ;
}
void main(void)
{
float a, b, c ;
  a = (b + c) / 10.0 ;
}
Program memory bytes used = 570 Program memory bytes used = 559

Trick #9: Prefer using unsigned values for int or long types, as possible. Be careful to keep numbers inside the interval {0, …, INT_MAX} or {0, …, LONG_MAX}

Example 9: Use instead: 
void main(void)
{
int Vbat ; // 1, 2, …, 1023
  Vbat = 1023 / Vbat ;  // case 1
  Vbat = (2048L * 15) / Vbat ;  // case 2
}
void main(void)
{
int Vbat ; // 1, 2, …, 1023
  Vbat = 1023U / Vbat ;  // case 1
  Vbat = (2048UL * 15) / Vbat ;  // case 2
}
Case 1 : Program memory bytes used = 102

Case 2 : Program memory bytes used = 160

Case 1 : Program memory bytes used = 73

Case 2 : Program memory bytes used = 114

Trick #10: Re-arrange the test inside an if instruction:

Example 10: Use instead:
void main(void)
{
int a, b ;
  if (a - b) b = (a - 5) * 100 ;    // case 1
  if (b > a) b = (a - 5) * 100 ;     // case 2
  if (b – a > 0) b = (a - 5) * 100 ; // case 3
}
void main(void)
{
int a, b ;
  if (a != b) b = (a - 5) * 100 ;    // case 1
  if (a < b) b = (a - 5) * 100 ;      // case 2
  if (a – b < 0) b = (a - 5) * 100 ; // case 3
}
Case 1 : Program memory bytes used = 67

Case 2 : Program memory bytes used = 62

Case 3 : Program memory bytes used = 77

Case 1 : Program memory bytes used = 58

Case 2 : Program memory bytes used = 62

Case 3 : Program memory bytes used = 65

References

[1] https://ww1.microchip.com/downloads/en/AppNotes/doc8453.pdf

[2] https://www.edn.com/electronics-blogs/edn-magazine--april-2013/4408338/10-C-Language-Tips-for-Hardware-Engineers

[3] http://www.eventhelix.com/RealtimeMantra/Basics/OptimizingCAndCPPCode.htm


Salim Mohammed Benabadji is a student at the University of Science and Technology of Oran with four years of expertise in Qt, C/C++ & QML application development in various domains.

 

Loading comments...

Parts Search Datasheets.com

KNOWLEDGE CENTER