当前位置: 首页 > news >正文

有限状态机FSM(Finite State Machine)自动初始化

1、自动初始化简介

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

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

第一种:全部放置在main函数里面定义,如:

int main(void)
{HAL_Init();                             /* 初始化HAL库 */sys_stm32_clock_init(RCC_PLL_MUL9);     /* 设置时钟, 72Mhz */delay_init(72);                         /* 延时初始化 */usart_init(115200);                     /* 串口初始化为115200 */usmart_dev.init(72);                    /* 初始化USMART */led_init();                             /* 初始化LED */lcd_init();                             /* 初始化LCD */……while(1){;}
}

第二种:将所有初始化函数放到某一个函数中,如_init(),然后在main中调用这个函数;

上述两种初始化方式都会导致初始化代码篇幅过长,参考Rt-thread和OneOS的自动初始化,可以用于初始化代码规范编写。

2、RT-Thread初始化设计

2.1、自动初始化优点

RT-Thread的自动初始化机制通过INIT_EXPORT宏实现,将初始化函数放入特定段,系统启动时自动按顺序执行。该机制分为多个优先级,确保依赖关系正确。它有以下技术优势:

  • 模块化设计

各组件通过宏声明初始化依赖,无需手动调用初始化函数。

  • 优先级控制

通过段名排序实现严格的初始化顺序控制。

  • 可扩展性

新增组件只需添加INIT_EXPORT声明,无需修改主初始化逻辑。

这种机制有效解决了嵌入式系统中组件初始化顺序的管理问题,是RT-Thread实现高度模块化设计的关键技术之一。

2.2、初始化段分类

如下表所示。

2.3、初始化执行流程

void rt_components_board_init(void)void rt_components_init(void)函数是专门用来负责自动初始化的。

void rt_components_board_init(void)
{
#if RT_DEBUG_INITint result;const struct rt_init_desc *desc;for (desc = &__rt_init_desc_rti_board_start; desc < &__rt_init_desc_rti_board_end; desc ++){rt_kprintf("initialize %s", desc->fn_name);result = desc->fn();rt_kprintf(":%d done\n", result);}
#elsevolatile const init_fn_t *fn_ptr;for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++){(*fn_ptr)();}
#endif /* RT_DEBUG_INIT */
}/*** @brief  RT-Thread Components Initialization.*/
void rt_components_init(void)
{
#if RT_DEBUG_INITint result;const struct rt_init_desc *desc;rt_kprintf("do components initialization.\n");for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++){rt_kprintf("initialize %s", desc->fn_name);result = desc->fn();rt_kprintf(":%d done\n", result);}
#elsevolatile const init_fn_t *fn_ptr;for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++){(*fn_ptr)();}
#endif /* RT_DEBUG_INIT */
}

 2.4、启动顺序

  1. 系统启动后调用rtthread_startup()
  2. 调用RT_WEAK void rt_hw_board_init(void)
  3. 执行rt_components_board_init()遍历.rti_fn段
  4. 按段名排序顺序调用各初始化函数
  5. 启动main线程
  6. 执行void rt_components_init(void)遍历.rti_fn段
  7. 按段名排序顺序调用各初始化函数

rt_components_board_init()函数最先执行,这个函数是用来初始化芯片相关的硬件的,这个函数会遍历用 INIT_BOARD_EXPORT(fn)声明的函数列表。
rt_components_init()函数是在系统启动后,在main线程里面被调用执行,这个函数是用来初始化其他用 INIT_XXX_EXPORT(fn)声明的函数列表的。

2.5、典型初始化函数示例

int uart1_init()
{	struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;  /* 初始化配置参数 */rt_err_t res;uart1Serial = rt_device_find(UART1_NAME);if (!uart1Serial){LOG_E("find %s failed!", UART1_NAME);return RT_ERROR;}config.baud_rate = BAUD_RATE_4500000;      //修改波特率为 100000config.data_bits = DATA_BITS_8;           //数据位 9config.stop_bits = STOP_BITS_1;           //停止位 1config.bufsz     = 1024;                   //修改缓冲区 buff size 为 128config.parity    = PARITY_NONE;            //偶校验位res = rt_device_control(uart1Serial, RT_DEVICE_CTRL_CONFIG, &config);	if(res != RT_EOK){LOG_E("commit control err!");return RT_ERROR;}return RT_EOK;
}INIT_PREV_EXPORT(uart1_init);

程序中调用INIT_PREV_EXPORT(uart1_init);查看map文件如下:

程序启动会按顺序加载0x08072648这个地址从而执行uart1_init这个函数。

3、自动初始化原理

自动初始化原理也是挺简单理解的,简单来说,就是遍历初始化函数的地址,然后执行到该地址时就会调用该地址的初始化函数,首先我们定义一个地址区域,该地址区域就是存放我们的初始化函数地址的区域,如以下源码所示:

static int32_t lv_device_init_start(void)
{return 0;
}LV_INIT_EXPORT(lv_device_init_start, "1.", "");static int lv_device_init_end(void)
{return 0;
}LV_INIT_EXPORT(lv_device_init_end, "1.end", "");

上述源码表示:定义两个地址,分别为外设初始化开始地址,外设初始化结束地址,首先关注重要的函数是以下两个函数:

LV_INIT_EXPORT(lv_device_init_start, "1.", "");
LV_INIT_EXPORT(lv_device_init_end, "1.end", "");

上述的函数可以在某个头文件定义,如以下源码所示:

typedef signed int                      lv_int32_t;typedef lv_int32_t                      lv_err_t;typedef lv_err_t (*lv_init_fn_t)(void);#define LV_INIT_PRIO_HIGH          "1"
#define LV_INIT_PRIO_MIDDLE        "2"
#define LV_INIT_PRIO_LOW           "3"#define LV_SECTION(x)               __attribute__((section(x)))
#define LV_USED                     __attribute__((used))#define LV_INIT_EXPORT(lv_fn, lv_level, lv_sublevel) \   
LV_USED const lv_init_fn_t  _lv_call_##lv_fn \
LV_SECTION(".init_call." lv_level lv_sublevel) = lv_fn

下面我们来分析自动初始化机制的原理,首先我们注意一个代码段,如以下所示:

#define LV_SECTION(x) __attribute__((section(x)))

__attribute__((section(x)))的代码段定义的是x存储函数地址或者变量地址,然后

LV_SECTION(".init_call." lv_level lv_sublevel) = lv_fn

代码段表示“.init_call.1.” = lv_fn,注意:x = “.init_call.1.”,所以“.init_call.1.”字符串指向fn(fn就是函数地址)。

“##”在c语言中必表示字符串连接符,所以我们得出以下代码:

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", "");也是一样的操作,所以得到以下代码:

LV_USED const lv_init_fn_t  _lv_call_lv_device_init_end ".init_call.1.end"

自动初始化的根本原理就是把函数加入符号段,其实就是使用了MDK编译器的__attribute__((section(x)))关键字,对函数进行声明,通过section关键字进行声明的函数,在编译器进行链接的时候,就会自动收集这些函数并把他们放到一个集中的区域里面,查看以下.map文件可知。

4、自动初始化示例

在裸机系统中,我们怎么制作自动初始化机制呢,首先我们在外设初始化函数中略加修改,例如led_init()函数,如以下源码所示:

void led_init(void)
{
/* 定义引脚的函数…此处省略 */
}

如果使用自动初始化,那么led_init()函数需要修改,如以下源码所示:

static int led_init(void)
{/* 定义引脚的函数…此处省略 */return 0;
}
LV_DEVICE_INIT(led_init, LV_INIT_PRIO_LOW);

注意:必须return0,下面调用LV_DEVICE_INIT()外设初始化,其他外设也是一样的道理。

遍历初始化函数,如以下源码所示:

static lv_err_t _k_device_auto_init(void)
{const lv_init_fn_t *lv_fn_ptr_init_start; /* 开始地址 */const lv_init_fn_t *lv_fn_ptr_init_end; /* 结束地址 */const lv_init_fn_t *lv_fn_ptr; /* 当前地址 */lv_err_t ret;/* 获取初始化开始地址 */
lv_fn_ptr_init_start = &_lv_call_lv_device_init_start + 1;/* 获取初始化结束地址 */lv_fn_ptr_init_end   = &_lv_call_lv_device_init_end - 1;for (lv_fn_ptr = lv_fn_ptr_init_start;
lv_fn_ptr &lt;= lv_fn_ptr_init_end;
lv_fn_ptr++)
/* 遍历地址 */{ret = (*lv_fn_ptr)();/* 执行地址的函数 */if (ret != 0){return ret;}}return 0;
}

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

最后在main函数这样定义即可,如以下源码所示:

int main(void)
{lv_err_t ret;/* 外设初始化 */ret = _k_device_auto_init();if (0 != ret){printf("\r\n device startup failed\r\n");}................while(1);
}

同理,应用层的初始化也可以放在_k_device_auto_init中一同处理。

参考链接:技术干货!裸机系统自动初始化详解 - 哔哩哔哩

rt-thread嵌入式操作系统自动初始化详解-CSDN博客

https://zhuanlan.zhihu.com/p/636316166

http://www.dtcms.com/a/275141.html

相关文章:

  • C++:vector(1)
  • 2025Nginx最新版讲解/面试
  • “功能替代”到“智能共创”——深入解读新松多可仿生人形机器人
  • map暨例题
  • 卢比危机下的金融破局:科伦坡交易所技术升级作战图
  • C++类对象多态基础语法【超详细】
  • GTSuite许可服务器设置
  • 380. O(1) 时间插入、删除和获取随机元素
  • 深度学习:反向传播算法
  • Google Test 介绍和使用指南
  • 《QtPy:Python与Qt的完美桥梁》
  • STM32 IIC通信(寄存器与hal库实现)
  • 组件杠杠结构
  • 干眼症的预防与治疗
  • 域名锁是什么?有必要安装域名锁吗?
  • 拼数(字符串排序)
  • TransUnet医学图像分割模型
  • PrimeTime (PT Shell) report_timing 报告全字段完整解析
  • 深度对比扣子(Coze) vs n8n
  • halcon 求一个tuple的极值点
  • 上位机知识篇---高效下载安装方法
  • Auto-GPT 简易教程
  • Ant Design ProTable重置函数全解析
  • 【Ubuntu 22.04 ROS2 Humble】没有数字签名。 N: 无法安全地用该源进行更新
  • 47-RK3588 用瑞芯微官方提供recovery进行OTA升级
  • VR协作海外云:跨国企业沉浸式办公解决方案
  • ATAM与效用树:架构评估的核心方法论
  • 喷涂机器人cad【1张】+三维图+设计说明书+降重
  • 【SpringAI】6.向量检索(redis)
  • 【JAVA】面向对象三大特性之继承