记录6:ESP-IDF自动初始化函数

0、前期准备

1、参考首篇文章搭建好esp32环境

2、准备好一块esp32开发开发板(本作者使用了esp32c3作为开发平台)

1、知识储备

1.1 概述

​ 一般在编写单片机程序的时候,需要手动在main函数初始化调用初始化函数进行设备初始化,为了解决这个问题,实现优雅编程,我们可以借鉴 linux 内核 module_init的原理:利用gcc编译器的特性,将某个函数的入口地址放在某个数据段中,所以我们在可以写一个初始化函数,去访问该数据段(函数地址)即可,实现函数调用。

1.2 基础知识
__attribute__ : 用设置函数、变量和类型属性
# 使用例子
__attribute__((constructor)) : 使用该属性修饰的函数,表示在调用main前调用该函数 
__attribute__((destructor)) : 使用该属性修饰的函数,表示在调用main后调用该函数 
const init_func_t _init_driver_##func __attribute__((section(".init_driver"),used)) = func: 使用该属性修饰的变量,表示将_init_driver_##fun函数变量名放到.init_driver数据段
1.3 esp32的编译链接脚本语法
[type:name]
key: value
key:
    value

type : 片段类型(值:段(section)、协议(scheme)和映射(mapping))  name : 片段名字(唯一)
key  :键值关键字(entries 和 archive)

注意:
    在段和协议中,仅支持entries键
    在映射中,支持archive和entries键

多个片段的类型和名称相同时会引发异常
片段名称和键值只能使用字母、数字和下划线


段:
定义了 GCC 编译器输出的一系列目标文件段,可以是默认段(如 .text、.data),也可以是用户通过 __attribute__ 关键字定义的段
'+' 表示段列表开始,且当前段为列表中的第一个段
例子:
[sections:name]
entries:
    .section+ # 第一个段
    .section
    ...

协议:
协议定义了每个段对应的目标
例子:
[scheme:name]
entries:
    sections -> target
    sections -> target
    ...

映射:
映射定义了可映射实体(即目标文件、函数名、变量名和库对应的协议
例子:
[mapping]
archive: archive                # 构建后输出的库文件名称(即 libxxx.a)
entries:
    object:symbol (scheme)      # 符号
    object (scheme)             # 目标
    * (scheme)                  # 库

有三种存放粒度:
    符号:指定了目标文件名称和符号名称。符号名称可以是函数名或变量名。
    目标:只指定目标文件名称。
    库:指定 *,即某个库下面所有目标文件的简化表达法。

除了实体和协议,条目中也支持指定如下标志:(注:<> = 参数名称,[] = 可选参数)

ALIGN(<alignment>[, pre, post])
    根据 alignment 中指定的数字对齐存放区域,根据是否指定 pre 和 post,或两者都指定,在输入段描述(生成于映射条目)的前面和/或后面生成:

SORT([<sort_by_first>, <sort_by_second>])
    在输入段描述中输出 SORT_BY_NAME, SORT_BY_ALIGNMENT, SORT_BY_INIT_PRIORITY 或 SORT。
    sort_by_first 和 sort_by_second 的值可以是:name、alignment、init_priority
    如果既没指定 sort_by_first 也没指定 sort_by_second,则输入段会按照名称排序,如果两者都指定了,那么嵌套排序会遵循 https://sourceware.org/binutils/docs/ld/Input-Section-Wildcards.html 中的规则。

KEEP()
    用KEEP命令包围输入段描述,从而防止链接器丢弃存放区域。更多细节请参考 https://sourceware.org/binutils/docs/ld/Input-Section-Keep.html

SURROUND(<name>)
    在存放区域的前面和后面生成符号,生成的符号遵循 _<name>_start 和 _<name>_end 的命名方式,例如,如果 name == sym1
    在添加标志时,协议中需要指定具体的 section -> target。对于多个 section -> target,使用逗号作为分隔符,
    例如:
        # 注意
        # A. entity-scheme 后使用分号
        # B. section2 -> target2 前使用逗号
        # C. 在 scheme1 条目中定义 section1 -> target1 和 section2 -> target2
        entity1 (scheme1);
            section1 -> target1 KEEP() ALIGN(4, pre, post),
            section2 -> target2 SURROUND(sym) ALIGN(4, post) SORT()
    合并后,如下的映射:
        [mapping:name]
        archive: lib1.a
        entries:
            obj1 (noflash);
                rodata -> dram0_data KEEP() SORT() ALIGN(8) SURROUND(my_sym)
    会在链接器脚本上生成如下输出:
        . = ALIGN(8)
        _my_sym_start = ABSOLUTE(.)
        KEEP(lib1.a:obj1.*( SORT(.rodata) SORT(.rodata.*) ))
        _my_sym_end = ABSOLUTE(.)

2、链接脚本格式

1、在组件文件夹内新建xxx.lf文件,写入如下内容
    # 段列表
    [sections:init_driver]
    entries:
        .init_driver
    # 指定段位置
    [scheme:init_dirver_desc]
    entries:
        init_driver -> flash_rodata
    # 段入口
    [mapping:init_driver]
    archive: *
    entries:
        * (init_dirver_desc);
            init_driver -> flash_rodata KEEP() SORT(name) SURROUND(driver_init_func_array)

2、打开CMakeLists.txt文件中,在idf_component_register 函数中引入该脚本,例子如下:
    idf_component_register(
                        ...
                        LDFRAGMENTS "driver_linker.lf"
                        ...
                        WHOLE_ARCHIVE)

3、使用流程

# 添加组件
idf.py -C components create-component test #test为组件名
# 进入到组件文件夹并且新建链接脚本(内容见附件1)
cd components/test
vim test.lf
# 修改CMakeFile.txt文件,内容如下:
...
idf_component_register(...
                    LDFRAGMENTS "test.lf"
                    ...)
# 创建初始化函数(见附件1)
vim test.c test.h
# 在main.c中注册一个测试函数,内容如下:
void test(void) {
	printf("test\n");
}
INIT_DRIVER(test);
# 编译,烧录之后,在monitor中就可以观察到test函数被调用
附件1

test.lf

[sections:init_test]
entries:
    .init_test

[scheme:init_test_desc]
entries:
    init_test -> flash_rodata

[mapping:init_test]
archive: *
entries:
    * (init_test_desc);
        init_test -> flash_rodata KEEP() SORT(name) SURROUND(test_init_func_array)

test.h

#ifndef TEST_H
#define TEST_H

typedef void (*init_func_t)(void);

#define INIT_DRIVER(func) \
    const init_func_t _init_test_##func __attribute__((section(".init_test"),used)) = func
#endif

test.c

static void __attribute__((constructor)) init_test_register(void) {

    extern const init_func_t _init_func_array_start;
    extern const init_func_t _init_func_array_end;
    for (const init_func_t* it = &_init_func_array_start; it != &_init_func_array_end; ++it) {
        (*it)();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MagicKingC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值