>

在stm32上使用二进制字库的简单方法

2015-03-13 by Stavros

标题其实可以写成"在可执行程序里嵌入二进制资源的方法", 不过这个题目大了点, 还是原样吧.

以前如果我们用到不带字库的点阵LCD, 一般都是把字库按16进制写成一个大数组, 再和其他源程序一起编译. 有没有方便一点的办法呢?这里给出两个方案.

  1. 使用objcopy把字库转成目标文件(.o/.obj)

    假设我们需要嵌入的是5x7的ascii点阵字库, 文件名是asc5x7.bin.

    在arm-gcc环境下, 命令如下:

    arm-none-eabi-objcopy -B arm -I binary -O elf32-littlearm --rename-section .data=.rodata asc5x7.bin asc5x7.o
    

    需要注意的是, objcopy生成的.o文件默认是把数据放在.data段的. 因此这里需要加个--rename-section的选项, 把.data改成.text或者.rodata, 不然单片机可怜的一点点RAM根本不够用. 如果是在PC上运行, 这里改不改就无所谓了, 不过内存还是能省点就省点的好.

    在.o里会生成三个符号: _binary_asc5x7_bin_start, _binary_asc5x7_bin_end, binary_asc5x7_bin_size.

    在程序里这么调用:

    extern const int _binary_asc5x7_bin_start;
    uint8_t *start = (uint8_t *) (&_binary_asc5x7_bin_start);
    

    这样我们就得到了字库的起始地址. 在AVR上可能会稍复杂一些, 因为数据存储在FLASH里, 所以需要象PROGMEM数组一样, 用pgm_read_byte之类函数来读取.

    链接时会有一堆错误提示: "Conflicting CPU architectures ....", 这是因为.o里还缺少一个叫.ARM.attributes的段. 不过实际上也没啥影响, 程序是可以正确运行的.

  2. 直接利用GNU汇编的.incbin功能

    这是minux@bdwm提供的方法, 解决得明显更漂亮一些.

    首先建立一个asc5x7.s的源文件,内容如下:

    .section .rodata,"",%progbits
    .global _binary_asc5x7_bin_start, _binary_asc5x7_bin_end, _binary_asc5x7_bin_size
    _binary_asc5x7_bin_start:
    .incbin "asc5x7.bin"
    _binary_asc5x7_bin_end:
    _binary_asc5x7_bin_size = _binary_asc5x7_bin_end - _binary_asc5x7_bin_start
    

    其实_binary_asc5x7_bin_size和_binary_asc5x7_bin_end这两个符号不要也可以, 因为字库的存储格式我们事先已经知道了.

    然后建立一个空的.c文件: touch empty.c, arm-none-eabi-gcc -S empty.c, 这样我们得到了一个empty.s, 内容如下:

     .cpu arm7tdmi-s
     .fpu softvfp
     .eabi_attribute 20, 1
     .eabi_attribute 21, 1
     .eabi_attribute 23, 3
     .eabi_attribute 24, 1
     .eabi_attribute 25, 1
     .eabi_attribute 26, 1
     .eabi_attribute 30, 6
     .eabi_attribute 18, 4
     .file     "empty.c"
     .ident     "GCC: (Sourcery G++ Lite 2011.03-42) 4.5.2"
    

    把这些东西复制到之前那个asc5x7.s里, 部分内容可能要按需调整一下, 比如.cpu这里应该改成cortex-m0或者cortex-m3之类. 这样在链接时就不会出现前面那个"Conflicting CPU architectures ...."的报错信息了.

    如果要使用几个字库呢? 可以把它们全都放在一个段里, 但是如果只用到了某一个或几个字库, 也会把它们全都链接进去, 导致最终的可执行文件体积过大. 因此最好是拆成几个段. 假设我们用到了asc5x7.bin, asc12.bin, asc16.bin和asc24.bin, 写出来应该是这样:

    .section .rodata.asc5x7,"",%progbits
    .global _binary_asc5x7_bin_start
    _binary_asc5x7_bin_start:
    .incbin "asc5x7.bin"
    
    .section .rodata.asc12,"",%progbits
    .global _binary_asc12_bin_start
    _binary_asc12_bin_start:
    .incbin "asc12.bin"
    
    .section .rodata.asc16,"",%progbits
    .global _binary_asc16_bin_start
    _binary_asc16_bin_start:
    .incbin "asc16.bin"
    
    .section .rodata.asc24,"",%progbits
    .global _binary_asc24_bin_start
    _binary_asc24_bin_start:
    .incbin "asc24.bin"
    

    然后Makefile里需要增加几个参数, 假如之前没有的话.

    CFLAGS += -ffunction-sections --data-sections,
    LDFLAGS += -Wl,--gc-sections
    

    这样就只会把实际用到的段链接到最终可执行文件了.

    如果要在可执行文件里嵌入图片, 音频之类, 也可以用同样的方法.

    以上程序均在Windows 7 & Sourcery G++ & STM32F103/F030环境下测试通过. 在Keil/IAR等环境下请自行类推.

    补充:注意asc5x7.bin的字节数是奇数, 这样会使得后面几个字库的起始地址都是奇数, stm32进行某些操作时会进入HardFault. 解决办法是在每个.section之前的位置加个.align(3).