STM32 自动化初始化模块 AINI

STM32 自动化初始化模块 AINI

1.前言

说明:本文灵感来自于 RT-Thread 的自动化初始思路,表示感谢.
如何让我们的初始化函数自动执行,让代码看起来更简洁:
如下面所示:

int main(void)
{
  while (1)
  {

  }
}

串口助手打印显示:
init BOARD …
init DEVICE …
init COMPONENT …
init ENV …
init APP …

实际上在main函数里面我并没有做任何操作,就已经做到5个初始化函数已经执行。

2.让我们看看如何做到这些

如果我们把要初始化函数的地址按照顺序排列在一起,我们只要拿到第一个函数的地址,那么我们就可以依次拿到其它函数的地址,然后去执行相应的函数。

  • 1.首先需要定义一个函数指针,我们的初始化函数都必须按照这个格式写
  • 2.定义一个 section 区域用来存放我们的函数指针
  • 3.按照顺序来存放函数指针

3.具体实现

3.1 定义一个函数指针

typedef int (*aini_init_fn_t)(void);
此函数形参为void,返回值类型int
后面我们自己的初始化函数也必须是这种格式
注意:不要在初始化函数中使用堵塞类型的操作,因为他们是顺序执行的,会影响排列在后面的函数执行

3.2 定义指针,指向函数地址

#define AINI_USED        __attribute__((used))
#define AINI_SECTION(x)  __attribute__((section(x)))

#define AINI_INIT_EXPORT(fn,level) \
    AINI_USED const aini_init_fn_t __aini_call_##fn AINI_SECTION(".aini_call." level) = fn

_aini_call##fn 是一个aini_init_fn_t类型的指针变量,并把函数地址fn赋值给它。
##操作是连接左右两边的字符串,这样就可以定义前缀是"_aini_call"+函数名的变量,把这个变量放入.aini_call.区域,并使用level来进行标记排序

3.3 定义不同level的section

//板级初始化 顺序1
#define AINI_BOARD_INIT(fn)      AINI_INIT_EXPORT(fn,"1")
    
//设备初始化 顺序3
#define AINI_DEVICE_INIT(fn)     AINI_INIT_EXPORT(fn,"2")
    
//组件初始化 顺序4
#define AINI_COMPONENT_INIT(fn)  AINI_INIT_EXPORT(fn,"3")
    
//环境初始化 顺序5
#define AINI_ENV_INIT(fn)        AINI_INIT_EXPORT(fn,"4")
    
//APP初始化 顺序6
#define AINI_APP_INIT(fn)        AINI_INIT_EXPORT(fn,"5")

3.4 使用固定函数来指定section “aini_call.” 的起始地址和结束地址

static int aini_start(void)
{
//    serial_hw_console_output("os_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_start,"0");

static int aini_board_start(void)
{
//    serial_hw_console_output("os_board_start\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_start,"0.end");

static int aini_board_end(void)
{
//    serial_hw_console_output("os_board_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_board_end,"1.end");

static int aini_end(void)
{
//    serial_hw_console_output("os_end\r\n");
    return 0;
}
AINI_INIT_EXPORT(aini_end,"5.end");

3.5 自动执行初始化函数

/**
 * @brief 自动调用 OS_BOARD_INIT(fn) 自定的函数
 * .init_call.1
 */
static void os_components_board_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_board_start; pfn < &__aini_call_aini_board_end;pfn++)
    {
        (*pfn)();
    }

}

/**
 * @brief 自动初始化 section .init_call.2 ~ .init_call.5
 * 
 */
static void os_components_init(void)
{
    volatile const aini_init_fn_t *pfn;
    for(pfn = &__aini_call_aini_board_end; pfn < &__aini_call_aini_end; pfn++)
    {
        (*pfn)();
    }
}

aini_components_board_init() aini_components_init() 两个函数需要手动调用

3.6 map分布

$Super$$main                             0x08001395   Thumb Code     2  main.o(i.main)
serial_hw_console_output                 0x08001399   Thumb Code    26  main.o(i.serial_hw_console_output)
__aini_call_aini_start                   0x080013b8   Data           4  aini.o(.aini_call.0)
__aini_call_aini_board_start             0x080013bc   Data           4  aini.o(.aini_call.0.end)
__aini_call_board_init                   0x080013c0   Data           4  main.o(.aini_call.1)
__aini_call_aini_board_end               0x080013c4   Data           4  aini.o(.aini_call.1.end)
__aini_call_device_init                  0x080013c8   Data           4  main.o(.aini_call.2)
__aini_call_component_init               0x080013cc   Data           4  main.o(.aini_call.3)
__aini_call_env_init                     0x080013d0   Data           4  main.o(.aini_call.4)
__aini_call_app_init                     0x080013d4   Data           4  main.o(.aini_call.5)
__aini_call_aini_end                     0x080013d8   Data           4  aini.o(.aini_call.5.end)

可以看出__aini_call_aini_start __aini_call_aini_end 起到了占位作用

3.7 如何使用

int env_init(void)
{
    serial_hw_console_output("init ENV ...\r\n");
}
AINI_ENV_INIT(env_init);

首先定义 int fun(void)类型的初始化函数
接着使用下面5个宏之一指定初始化函数的执行顺序:
1.AINI_BOARD_INIT(fn)
2.AINI_DEVICE_INIT(fn)
3.AINI_COMPONENT_INIT(fn)
4.AINI_ENV_INIT(fn)
5.AINI_APP_INIT(fn)

3.8 一个例子

int main(void)
{
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  aini_components_board_init();
  aini_components_init();
  while (1)
  {

  }
}

/* 下面是我自定义的初始化函数 */
int app_init(void)
{
    serial_hw_console_output("init APP ...\r\n");
}
AINI_APP_INIT(app_init);

int env_init(void)
{
    serial_hw_console_output("init ENV ...\r\n");
}
AINI_ENV_INIT(env_init);

int component_init(void)
{
    serial_hw_console_output("init COMPONENT ...\r\n");
}
AINI_COMPONENT_INIT(component_init);

int device_init(void)
{
    serial_hw_console_output("init DEVICE ...\r\n");
}
AINI_DEVICE_INIT(device_init);

int board_init(void)
{
    serial_hw_console_output("init BOARD ...\r\n");
}
AINI_BOARD_INIT(board_init);

现在看起来好像还是没那么简介毕竟main函数里面还是调用了很多函数,没有完全做到底层和业务层完全分离

4.使用mdk的补丁功能继续减少main函数中的代码调用

先来了解下编译器语法:

$Sub$$main() //重定义main函数,在mian函数之前执行
$Super$$main()//跳转到main函数

有了这两个语法我们继续优化:

int $Sub$$main(void)
{
    /* 1.关中断 */
    //do nothing
    
    /* 2.初始化系统时钟,片内资源 */
    ani_seq_init();
    
    /* 3.初始化板级设备 */
    aini_components_board_init();
    
    /* 4.初始化一些软件级别的初始化,如库,APP等 */
    aini_components_init();
    
    /* 5. 开中断 */
    //do nothing
    
    #if defined(__CC_ARM) || defined(__CLANG_ARM)
    extern int main(void);
    extern int $Super$$main(void);
    /* 6.跳转到main函数 */
    $Super$$main(); /* for ARMCC. */
    #else
    #error 'not support complier!'
    #endif
    
    return 0;
}

__weak int ani_seq_init(void)
{
   /* 初始化系统时钟,片内资源 */
}

再来看看mian函数

int main(void)
{
  while (1)
  {

  }
}

是不是已经很简洁了
我把源代码工程放到了gitee 仓库,欢迎学习交流!
aini地址

你可能感兴趣的:(其它,stm32,自动化初始函数,mdk打补丁,mdk自动执行函数)