Skip to content

The Ties That Divide

Recently, I have been working on a project with the Make Controller. The Make Controller has been a handy platform for me to explore the ARM architecture, and the libraries distributed by MakingThings make it easy. While screwing around with the build of FreeRTOS included with the software, I stumbled across a piece of the C standard library that has bit me in the ass before. I figured I'd blog about it here so others don't fall into the same trap I did, or at least can get themselves out when they do.

Often while developing embedded systems, I am a bit picky about understanding all the lines of code in the system. As processors have increased in memory and performance, this is becoming less of an issue, and should certainly be true for the ARM architecture. However, I was surprised to discover that a minimal FreeRTOS build (with the MakingThings configuration) on the ARM was clocking in at around 30kBytes. Digging into the map file, I was shocked to discover that the RTOS build was including the floating point libraries. Unable to explain why an RTOS needs floating point libraries, I dug until I came across the following innocuous line in tasks.c, and it all became clear:

 
sprintf( pcStatusString, ( portCHAR * ) "%s\t\t%c\t%u\t%u\t%u\r\n", /*SNIP*/ );
 

Nothing is obviously suspicious about the above line. We are just formatting a set of strings and unsigned integers. However, if we consider how sprintf and it's ilk need to work, it becomes clear why the floating point libraries were included in the FreeRTOS build. The standard library function sprintf determines data types by parsing the format string. When sprintf was compiled, the code for "%f" was compiled as well as the "%u" formatter. As a result, the floating point libraries will be linked in.

In this case, the fix was easy. MakingThings distributed the FreeRTOS build with trace enabled, which resulted in the call to sprintf. By just disabling FreeRTOS's trace feature, we remove the call to sprintf and reduce the code size to about 7kBytes.

At this point, you might be thinking, "But I actually wanted to get the strings for those numbers." If so, you easily could write your own unsigned integer formatting function:

 
void MySprintfU(char *inStr, unsigned inVal)
{
    unsigned i, j;
 
    // Setup the values
    i = j = 0;
 
    // Find base 10 ASCII representation (reverse order in string)
    while(inVal)
    {
        unsigned temp;
        temp = inVal / 10;
        inStr[i++] = inVal - (temp * 10) + '0';
        inVal = temp;
    }
 
    // Handle 0
    if (i == 0)
        inStr[i++] = '0';
 
    // Null terminate
    inStr[i] = 0;
 
    // Reverse the inString
    while(j < i)
    {
        char c = inStr[--i];
        inStr[i] = inStr[j];
        inStr[j++] = c;
    }
}
 

But, even this has it's drawbacks. Mainly, the following line:

 
temp = inVal / 10;
 

Many embedded processors don't have a divide intrinsic (I'm looking at you DSPs), so the divide function is implemented as a serious of divide primitives. The divide could end up taking 16-32 extra instructions per iteration of the while() loop. Clearly this could be unacceptable if we need the ASCII string often enough (insert the standard warning about profiling your code).

Fear not my gentle reader. I have encountered this problem before and I present the solution to you in the form of fixed-point math:

 
void MySprintfU(char *inStr, unsigned inVal)
{
    unsigned i, j;
 
    // Setup the values
    i = j = 0;
 
    // Find base 10 ASCII representation (reverse order in string)
    while(inVal)
    {
        unsigned temp;
        temp = ((unsigned long long)inVal *
                (unsigned long long)0xCCCCCCCD) >> 35;
        inStr[i++] = inVal - (temp * 10) + '0';
        inVal = temp;
    }
 
    // Handle 0
    if (i == 0)
        inStr[i++] = '0';
 
    // Null terminate
    inStr[i] = 0;
 
    // Reverse the inString
    while(j < i)
    {
        char c = inStr[--i];
        inStr[i] = inStr[j];
        inStr[j++] = c;
    }
}
 

Although the above code has "hack" written all over it, the performance is based on sound mathematical principles. Basically, we take our number as an 32 bit integer and multiply it by a 35-bit fractional representation of 1/10 (the high 3 bits are 0). The result is a 64 bit number, where the low 35 bits are less than 0. Shifting them out, gives us our number divided by 10. On some compilers and processors, I've seen the divide optimize into two instructions.

{ 2 } Comments

  1. Richard Barry | March 6, 2007 at 11:37 pm | Permalink

    I am presuming the code is compiled with GCC? One of the (if not the only) disadvantage with GCC is its library handling. If you are not careful bloat like this can occur.

    GCC is a very flexible system, but with flexibility comes complexity. For example, there are options for removing unused symbols, but these are not included by default. I have a GCC build that was provided to me that does this and gets the ARM7 code size down to a value comparable to commercial compilers.

    As far as libraries go, there are restricted versions around. For example, when setting up a CrossWorks project (an IDE for GCC) you get the option as to whether or not include floating point, and other options, in the string formatting function. I also find that the libraries that ship with CrossWorks use much less stack than those that come other distributions which is worth keeping in mind when working with a real time kernel.

    Regards,
    Richard.

  2. Jeremy Faller | March 7, 2007 at 6:19 am | Permalink

    Rich:

    Actually, as far as the linker is concerned, if we call sprintf the floating point libraries ARE used and cannot be stripped. It’s all because the linker cannot know what formatters are passed to sprintf, so it must include support for all formatters (including "%f"). The only way to get the linker to strip float point libraries is to not call sprintf. FreeRTOS did make this easy but just allowing me to turn off trace.

    All in all, I have been impressed with FreeRTOS. I am a bit worried that it’s saving too much on the stack during ISRs, but until I see a problem, I need to stop being so anal.

Post a Comment

Your email is never published nor shared. Required fields are marked *