如何通过map文件优化单片机代码

1,519 阅读4分钟

本文正在参与 “性能优化实战记录”话题征文活动

  在平时写代码的时候,特别是嵌入式相关的代码时,能想到的优化方法一般就是通过设置编译器的优化等级。或者是在定义变量的时候考虑变量的使用范围,然后根据数据范围选择比较适合的数据类型。但是这种优化方式操作起来都比较模糊,没有一个直观的感受。为了让代码的优化有个直观的感受,今天就借助map文件来优化代码。

  首先看一段简单的示例。

在这里插入图片描述

  这是一段很简单的测试代码,就是在主程序里面让LED灯闪烁,同时给x变量每次加0.1。这个代码还需要优化吗?别着急,先打开工程中生成的map文件看看。

在这里插入图片描述

  map文件在工程目录debug文件夹里面的List文件夹中,使用记事本打开这个文件。

在这里插入图片描述

  在文件中间有一部分是各个目标文件所占用的空间大小,依次往下找,找几个占用空间比较大的位置。

在这里插入图片描述

  可以发现float.o这个目标文件占用的空间相对于其他文件来说很大了。那么这个float.o文件是哪里来的呢?看到float大概可以猜测到这是浮点相关的文件。在程序中只有一个地方用到了浮点运算。

在这里插入图片描述

  难道这一行代码能占用那么多的空间吗?那么把浮点运算改为整形运算试试。

在这里插入图片描述

  将x扩大10倍,然后每次加1,编译代码后重新打开map文件查看。

在这里插入图片描述

  这时就会发现在long.o前面的float.o文件消失了。由此可见,刚才那个float.o文件就是代码中的那一行浮点运算生成的。通过map文件的观察可以得出一个结论,浮点运算在单片机中占用的空间很大,所以尽量把浮点运算换算成整形运算。否则如果浮点运算的运算量比较大时,单片机的空间很快就会被占满了。

  接下来在通过串口将变量的值打印出来。

在这里插入图片描述

  通过串口助手观察打印的值

在这里插入图片描述

  串口打印的值也正常,接下来继续查看map文件。

在这里插入图片描述

  在map文件中可以看到在long.o文件的前面和后面多了好几个文件。而且占用空间非常大。

  那为什么又出现了一个非常大的 float.o 文件?这是因为printf函数支持浮点数打印,在函数里面有浮点数,所以就会产生一个float.o文件。xprintffull_nomb.o也是在调用printf函数时生成的。

  由此可见,printf函数占用的空间相当大的,在程序中尽可能的避免使用printf函数。那么要打印数据的时候怎么办呢?可以通过单片机串口默认输出功能打印字符,但是又有一个新问题,串口输出都是字符格式,而现在要打印整数,如何将整数转换为字符格式呢?可以通过自定义的函数,先将数字转换为字符串,然后在通过串口输出字符串。

  首先编写一段整数转字符串的函数。

void int2str(int n, char *str)
{
    char buf[10] = "";
    int i = 0;
    int len = 0;
    int temp = n < 0 ? -n: n;  // temp为n的绝对值

    if (str == NULL)
    {
        return;
    }
    while(temp)
    {
        buf[i++] = (temp % 10) + '0';  //把temp的每一位上的数存入buf
        temp = temp / 10;
    }

    len = n < 0 ? ++i: i;  //如果n是负数,则多需要一位来存储负号
    str[i] = 0;            //末尾是结束符0
    while(1)
    {
        i--;
        if (buf[len-i-1] ==0)
        {
            break;
        }
        str[i] = buf[len-i-1];  //把buf数组里的字符拷到字符串
    }
    if (i == 0 )
    {
        str[i] = '-';          //如果是负数,添加一个负号
    }
}

  接下来编写串口相关函数。

//发送单个字符
void SendChar( unsigned char dat )
{
    while( ( UART1_SR & 0x80 ) == 0x00 );       //发送数据寄存器空
    UART1_DR = dat;
}
//发送字符串
void SendString( unsigned char* s )
{
    while( 0 != *s )
    {
        SendChar( *s );
        s++;
    }
}

  下面修改主函数代码。

void main( void )
{
    int  x = 10;
    char str[100] = {0};
        
    __asm( "sim" );                             //禁止中断
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    Uart1_IO_Init();
    Uart1_Init( 9600 );
    __asm( "rim" );                             //开启中断

    while( 1 )
    {
        LED = ~LED;

        x += 1;    
        int2str(x,str);
        SendString(str);
        SendString("\r\n");
        delay_ms( 1000 );
    }
}

  首先通过int2str函数将变量x转换为字符串,然后在通过SendString函数将字符串打印出来,最后在调用一次SendString函数打印回车换行字符。打印效果如下:

在这里插入图片描述

  接下来再看一下map文件中占用的空间大小。

在这里插入图片描述

  通过map文件可以看到,相对于直接使用printf函数来说,自定义函数实现打印功能要节省不少空间。这样通过观察map文件中的目标文件大小,在优化程序的过程中就可以直观的看到优化后的代码与优化前的代码具体差距在哪里。这样在调试代码的过程中就会胸有成竹,不会像无头苍蝇一样到处乱撞。