OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4412|回复: 1

技术干货!裸机系统自动初始化详解

[复制链接]

1153

主题

1165

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4970
金钱
4970
注册时间
2019-5-8
在线时间
1259 小时
发表于 2021-7-6 16:57:55 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2021-11-1 10:43 编辑

以下文章摘自微信公众号——开源电子网《STM32开发中常用的C语言知识点》
更多技术文章,请扫下方二维码关注

开源电子网,扫码2222222.png



裸机系统自动初始化详解



1.1 自动初始简介

   自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。

    例如我们在裸机上初始化各个外设时,一般把初始化函数放到main函数中,那么造成代码篇幅过长,为了解决这个问题,RT_thread和OneOS也是用了自动初始化机制,这样有利于代码简洁和整体上比较模块化,在传统裸机上,初始化是使用如下几个方式来初始化函数:

    第一种:全部放置在main函数里面定义,如以下源码所示:
  1. int main(void)
  2. {
  3.     HAL_Init();                             /* 初始化HAL库 */
  4.     sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */
  5.     delay_init(72);                         /* 延时初始化 */
  6.     usart_init(115200);                     /* 串口初始化为115200 */
  7.     usmart_dev.init(72);                    /* 初始化USMART */
  8.     led_init();                             /* 初始化LED */
  9.     lcd_init();                             /* 初始化LCD */
  10.     key_init();                             /* 初始化按键 */
  11.     beep_init();                            /* 蜂鸣器初始化 */
  12.     norflash_init();                        /* 初始化NORFLASH */
  13. my_mem_init(SRAMIN);                     /* 初始化内部SRAM内存池 */
  14. while(1)
  15. {
  16. ;
  17. }
  18. }
复制代码


  第二种方式:
  1. <font size="4">void _init(void)
  2. {
  3. HAL_Init();                                  /* 初始化HAL库 */
  4.     sys_stm32_clock_init(RCC_PLL_MUL9);   
  5.                                                  /* 设置时钟, 72Mhz */
  6.     delay_init(72);                        /* 延时初始化 */
  7.     usart_init(115200);                 /* 串口初始化为115200 */
  8.     usmart_dev.init(72);               /* 初始化USMART */
  9.     led_init();                              /* 初始化LED */
  10.     lcd_init();                              /* 初始化LCD */
  11.     key_init();                             /* 初始化按键 */
  12.     beep_init();                           /* 蜂鸣器初始化 */
  13.     norflash_init();                      /* 初始化NORFLASH */
  14. </font><div><font size="4">  </font></div>
复制代码


上述两种初始化方式也是导致初始化代码篇幅过长,小编参考了Rt-thread和OneOS的自动初始化,我觉得它们这样初始化函数比较合理,可以使用于我们平常代码规范编写。

 
1.2 自动初始化原理

    自动初始化原理也是挺简单理解的,简单来说,就是遍历初始化函数的地址,然后执行到该地址时就会调用该地址的初始化函数,首先我们定义一个地址区域,该地址区域就是存放我们的初始化函数地址的区域,如以下源码所示:
  1. static int32_t lv_device_init_start(void)
  2. {
  3.     return 0;
  4. }
  5. LV_INIT_EXPORT(lv_device_init_start, "1.", "");
  6. static int lv_device_init_end(void)
  7. {
  8.     return 0;
  9. }
  10. LV_INIT_EXPORT(lv_device_init_end, "1.end", "");
复制代码



    上述源码表示:定义两个地址,分别为外设初始化开始地址,外设初始化结束地址,首先关注重要的函数是以下两个函数
  1. <font size="4">LV_INIT_EXPORT(lv_device_init_start, "1.", "");
  2. </font><div><font size="4">LV_INIT_EXPORT(lv_device_init_end, "1.end", "");</font></div>
复制代码




    上述的函数可以在某个头文件定义,如以下源码所示:
  1. typedef signed int                      lv_int32_t;            
  2. typedef lv_int32_t                      lv_err_t;              
  3. typedef lv_err_t (*lv_init_fn_t)(void);
  4. #define LV_INIT_PRIO_HIGH          "1"
  5. #define LV_INIT_PRIO_MIDDLE        "2"
  6. #define LV_INIT_PRIO_LOW           "3"
  7. #define LV_SECTION(x)               __attribute__((section(x)))
  8. #define LV_USED                     __attribute__((used))
  9. #define LV_INIT_EXPORT(lv_fn, lv_level, lv_sublevel)                                                           
  10.                                                 \
  11. LV_USED const lv_init_fn_t  _lv_call_##lv_fn \
  12. LV_SECTION(".init_call." lv_level lv_sublevel) = lv_fn
复制代码



  下面我们来分析自动初始化机制的原理,首先我们注意一个代码段,如以下所示:
  1. #define LV_SECTION(x) __attribute__((section(x)))
复制代码


    __attribute__((section(x)))的代码段定义的是x存储函数地址或者变量地址,然后LV_SECTION(".init_call." lv_level lv_sublevel) = fn代码段表示“.init_call.1.” = fn,注意:x = “.init_call.1.”,所以“.init_call.1.”字符串指向fn(fn就是函数地址)。
“##”在c语言中必表示字符串连接符,所以我们得出以下代码:
  1. LV_USED const lv_init_fn_t_lv_call_lv_device_int_start".int_call.1."
复制代码


    同理,LV_INIT_EXPORT(lv_device_init_end, "1.end", "");也是一样的操作,所以得到以下代码:
  1. LV_USED const lv_init_fn_t  _lv_call_lv_device_init_end “.init_call.1.end”
复制代码




1. 3自动初始化示例


  在裸机系统中,我们怎么制作自动初始化机制呢,首先我们在外设初始化函数中略加修改,例如led_init()函数,如以下源码所示:
  1. void led_init(void)
  2. {
  3. /* 定义引脚的函数…此处省略 */
  4. }
  5.   如果使用自动初始化,那么led_init()函数需要修改,如以下源码所示:
  6. static int led_init(void)
  7. {
  8.   /* 定义引脚的函数…此处省略 */
  9.     return 0;
  10. }
  11. LV_DEVICE_INIT(led_init, LV_INIT_PRIO_LOW);
  12.   注意:必须return0,下面调用LV_DEVICE_INIT()外设初始化,其他外设也是一样的道理.
  13.   1.遍历初始化函数,如以下源码所示:
  14. static lv_err_t _k_device_auto_init(void)
  15. {
  16.     const lv_init_fn_t *lv_fn_ptr_init_start;   
  17.              /* 开始地址 */
  18.     const lv_init_fn_t *lv_fn_ptr_init_end;   
  19.              /* 结束地址 */
  20.     const lv_init_fn_t *lv_fn_ptr;                 
  21.              /* 当前地址 */
  22.     lv_err_t ret;                                             
  23.         /* 获取初始化开始地址 */
  24. lv_fn_ptr_init_start = &_lv_call_lv_device_init_start + 1;                                                
  25.         /* 获取初始化结束地址 */
  26.     lv_fn_ptr_init_end   = &_lv_call_lv_device_init_end - 1;   
  27. for (lv_fn_ptr = lv_fn_ptr_init_start;
  28. lv_fn_ptr <= lv_fn_ptr_init_end;
  29. lv_fn_ptr++)                           
  30. /* 遍历地址 */
  31.     {
  32.         ret = (*lv_fn_ptr)();                        
  33.               /* 执行地址的函数 */
  34.         if (ret != 0)
  35.         {
  36.             return ret;
  37.         }
  38.     }
  39.     return 0;
  40. }
复制代码




    从上述源码可知:初始化开始地址不正是LV_INIT_EXPORT(lv_device_init_start,"1.", "");函数吗,而初始化结束地址不正是LV_INIT_EXPORT(lv_device_init_end,"1.end", "");,然后我们把led_init()函数地址插入这地址区域中,如下图所示:

自动 1.jpg
  最后在main函数这样定义即可,如以下源码所示:
  1. int main(void)

  2. {

  3.     lv_err_t ret;

  4.     /* 外设初始化 */

  5.     ret = _k_device_auto_init();



  6.     if (0 != ret)

  7.     {

  8.         printf( "\r\ndevice startup failed\r\n");

  9.     }

  10.    

  11.     ................

  12.    

  13.     while(1);

  14. }
复制代码


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

4

主题

16

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
235
金钱
235
注册时间
2019-10-23
在线时间
71 小时
发表于 2021-7-7 08:40:59 | 显示全部楼层
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-23 10:53

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表