When a graphic device, such as LCD and OLED, is used in your project, it will need to embed font files or image files into the program binary. There are some ways to embed the binary file into the program code. Each one have advantages and disadvantages, so that you should use a proper one for the project.
This is a typical method that everybody think. Any binary data can be embedded in the C source files as constant table.
const uint8_t Image1[] = {
0x20,0x6C,0x61,0x6E,0x67,0x3D,0x22,0x65,0x6E,0x22,0x3E,0x0A,0x3C,0x68,0x65,0x61,
0x72,0x69,0x63,0x74,0x2E,0x64,0x74,0x64,0x22,0x3E,0x0A,0x3C,0x68,0x74,0x6D,0x6C,
0x75,0x69,0x76,0x3D,0x22,0x43,0x6F,0x6E,0x74,0x65,0x6E,0x74,0x2D,0x54,0x79,0x70,
/* May be several thousands lines or more!! */
0x70,0x2D,0x65,0x71,0x75,0x69,0x76,0x3D,0x22,0x43,0x6F,0x6E,0x74,0x65,0x6E,0x74,
0x74,0x22,0x20,0x74,0x69,0x74,0x6C,0x65,0x3D,0x22,0x53,0x69,0x74,0x65,0x20,0x54,
0x2D,0x53,0x74,0x79,0x6C,0x65,0x2D,0x54,0x79,0x70,0x65,0x22,0x20,0x63,0x6F,0x6E
};
It is not a smart way but there are advantages that easy to use with fixed and small data up to several kilobytes and compatible with not only C/C++ but any other languages. The disadvantages are bloated source code, limitation in table size depends on environment (maximum value of int) and reconversion is needed after changes to the original binary file.
This is the orthodox method that provided for such purpose. The objcopy command, the actual name varies depends on the toolchain, is a powerful file format converter that usually used to convert .elf file into .hex file in the build process. It can also be used to convert binary files into object files which can be linked with program binary. For instance, a binary file foo.bin can be converted into object file as follows:
objcopy -I binary -O elf32-little foo.bin foo.o
An option -I binary needs to be specified because the plain binary file does not have any format information. By default, the file data is allocated in .data section, so that the destination section needs to be explicitly changed to the constant section, such as .const and .rodata, as follows:
objcopy -I binary -O elf32-little --rename-section .data=.rodata,alloc,load,readonly foo.bin foo.o
The name of constant section depends on the linker script used in the project. This process is usually written in Makefile and automatically done if any changes are made to the binary files, so that this method is suitable for the project with many binary files.
The data start address, the data end address + 1 and the size of data can be referred with the symbols generated from the file name by objcopy as _binary_foo_bin_start, _binary_foo_bin_end, _binary_foo_bin_size, respectively. The data can be referred from the C code as shown below:
/* Style 1: Declaration of symbols (any type can be used) */ extern const char _binary_foo_bin_start[], _binary_foo_bin_size[]; const char *foo_ptr = _binary_foo_bin_start; /* Data location */ size_t foo_size = (size_t)_binary_foo_bin_size; /* Data size */
/* Style 2: Declaration of symbols (any type can be used) */ extern const char _binary_foo_bin_start, _binary_foo_bin_size; const char *foo_ptr = &_binary_foo_bin_start; /* Data location */ size_t foo_size = (size_t)&_binary_foo_bin_size; /* Data size */
Crazy programmer tends to prefer .incbin directive in assember source files. It is the most powerfull method that can extract a part of binary file and merge it in any order on the memory.
.section ".rodata" /* Allocates on ROM section (.progmem.data for AVR) */ .balign 4 /* Word alignment */ .global Foo123 Foo123: /* Label Foo123 has address of embedded data */ .incbin "foo1.bin", <ofs>, <size> /* Get <size> bytes at byte offset <ofs> */ .incbin "foo2.bin", <ofs> /* Left all if <size> is omitted */ .incbin "foo3.bin" /* Whole file if <ofs> is omitted */ .global _sizeof_Foo123 .set _sizeof_Foo123, . - Foo123 /* Defines the size of data */ .section ".text"
Embedded data can be referred as shown below.
/* Declaration of symbols (any type can be used) */ extern const char Foo123[], _sizeof_Foo123[]; const char *foo_ptr = Foo123; /* Data location */ size_t foo_size = (size_t)_sizeof_Foo123; /* Data size */
I prefer this method and use it over 10 years. Because .incbin is an assembler directive, it cannot be used as C code. However it can be used in C source files indirectly by asm statement that embeds assember code in C source file. In this case, some macros shown below will be useful.
/* Import a binary file */ #define IMPORT_BIN(sect, file, sym) asm (\ ".section " #sect "\n" /* Change section */\ ".balign 4\n" /* Word alignment */\ ".global " #sym "\n" /* Export the object address */\ #sym ":\n" /* Define the object label */\ ".incbin \"" file "\"\n" /* Import the file */\ ".global _sizeof_" #sym "\n" /* Export the object size */\ ".set _sizeof_" #sym ", . - " #sym "\n" /* Define the object size */\ ".balign 4\n" /* Word alignment */\ ".section \".text\"\n") /* Restore section */ /* Import a part of binary file */ #define IMPORT_BIN_PART(sect, file, ofs, siz, sym) asm (\ ".section " #sect "\n"\ ".balign 4\n"\ ".global " #sym "\n"\ #sym ":\n"\ ".incbin \"" file "\"," #ofs "," #siz "\n"\ ".global _sizeof_" #sym "\n"\ ".set _sizeof_" #sym ", . - " #sym "\n"\ ".balign 4\n"\ ".section \".text\"\n")
These macros can be used in C source files as shown below. The gcc accepts asm statement out side of function block, so that it can be defined anywhere in the C source files. Note that the file name is not in relative path from the C source file as like #include but from the project root, because the code in the asm statement is passed to the assembler in compliler output (*.s). Include directory path will need to be specified to assembler options.
/* Allocates foo.bin in constant section and reffered in FooBin */ IMPORT_BIN(".rodata", "foo.bin", FooBin); /* Declaration of symbols (any type can be used) */ extern const char FooBin[], _sizeof_FooBin[]; for (int i = 0; i < (int)_sizeof_FooBin; i++) { putchar(FooBin[i]); }
Any changes in source binary files embedded by .incbin does not refrect on build process, so that the corresponding C source files need to be explicitly recompiled.
If the binary data is a monolithic data block not tightly coupled with the program code, such as a filesystem image, it is often managed as an individual ROM image. In this case, the binary file is directly merged with the program hex file, or selected with the program code together on ROM programming. The binary data is merged on the ROM. To convert the binary file into hex file, the objcopy can be used. The location of the binary data needs to be managed somewhere in ROM area and not overlap the program code.
objcopy -I binary -O ihex --change-section-lma .data=0x20000 diskimage.bin diskimage.hex
The merged data can be reffered as:
/* Pointer to the data (any type can be used) */ const uint8_t *diskimage = (const uint8_t*)0x20000; /* Data location */
This is not for the embedded projects but for the VC++ projects. As every VC++ programmer knows, most type of data, such as dialog, icon and cursor, is managed as resource in VC++ projects. This scheme can manage not only the pre-defined class but also user defined class as shown below.
#include "resource.h" HRSRC hbin = FindResource(0, MAKEINTRESOURCE(IDR_BIN1), TEXT("BIN")); // Handle of the data IDR_BIN1 const BYTE *bindata = (const BYTE*)LockResource(LoadResource(0, hbin)); // Pointer to the data size_t binsize = (size_t)SizeofResource(0, hbin); // Size of the data