【星闪】Hi2821 | SDK开发入门,应用启动流程,创建自己的应用
1. SDK文件结构
- docs:官方文档;
- src:SDK源码;
- tools:其他文档;
- vendor:厂商资料,里面包含一些第三方厂商开发板的资料。
一般在开发的时候只需要复制 src 目录进自己的项目即可。src 目录的结构如下:
- .vscode:因为 HiSpark Studio 是魔改 VSCode 的,所以这个文件夹就是用来放对应的配置文件的;
- analyzerJson:也是放一些配置文件;
- application:应用代码;
- build:SDK构建所需的脚本、配置文件;
- drivers:驱动代码;
- include:API头文件;
- interim_binary:核心库;
- kernel:内核代码;
- middleware:中间件代码;
- open_source:第三方库代码;
- output:存放编译时生成的目标文件与中间文件;
- tools:编译工具链(包括linux和windows)、镜像打包脚本、NV制作工具和签名脚本等。
2. 自定义应用开发
application 目录存放的是应用代码,那么我们自己的应用也建议放在这里,所以先在里面创建一个目录,我这里命名为“my_demo”。
2.1 CMakeLists.txt
SDK是基于CMake构建的,所以应用目录内创建 CMakeLists.txt 作为构建脚本,内容如下:
set(COMPONENT_NAME "my_demo")set(SOURCES${CMAKE_CURRENT_SOURCE_DIR}/my_demo.c
)set(PRIVATE_HEADER
)set(PUBLIC_HEADER
)set(PRIVATE_DEFINES
)set(PUBLIC_DEFINES
)set(COMPONENT_PUBLIC_CCFLAGS
)set(COMPONENT_CCFLAGS
)set(WHOLE_LINK
true
)set(BUILD_AS_OBJ
false
)set(MAIN_COMPONENT
true
)build_component()
SDK 是基于组件进行构建的,组件的构建通过一系列的变量进行配置,如下:
变量名 | 说明 |
---|---|
COMPONENT_NAME | 当前组件名称 |
SOURCES | 当前组件的源文件 |
PUBLIC_HEADER | 当前组件需要对外提供的头文件路径 |
PRIVATE_HEADER | 当前组件内部的头文件路径 |
PRIVATE_DEFINES | 当前组件内部使用的宏定义 |
PUBLIC_DEFINES | 当前组件需要对外提供的宏定义 |
COMPONENT_PUBLIC_CCFLAGS | 当前组件需要对外提供的编译选项 |
COMPONENT_CCFLAGS | 当前组件内部生效的编译选项 |
这里只需要添加源文件的路径即可。
2.2 Kconfig
跟 ESP32 一样, SDK 通过 Kconfig 进行 SDK 配置、裁切内核等操作,为了演示我这里也创建一个 Kconfig 配置文件,具体的语法在这里就不赘述了。
config MY_DEMO_NAMEstring "Demo Name"default "My Demo"helpSet the demo name.
只有一个配置项,用于设置 demo 的名字。
2.3 my_demo.c
接着就是创建一个源文件作为应用编译进代码里面,内容如下:
#include "soc_osal.h"
#include "app_init.h"
#include "common_def.h"static int my_demo_task(void *arg)
{unused(arg);while (1) {osal_printk("This is %s\r\n", CONFIG_MY_DEMO_NAME);osal_msleep(1000);}return 0;
}static void my_demo_entry(void)
{osal_kthread_lock();osal_task* task_handle = osal_kthread_create(my_demo_task, NULL, "MyDemoTask", 4096);if (task_handle != NULL) {osal_kthread_set_priority(task_handle, 20);}osal_kthread_unlock();
}app_run(my_demo_entry);
上面的内容就是创建一个新的任务,这个任务会每隔1秒打印一段字符串,字符串包含了 Kconfig 中配置的 demo 名字。
2.4 修改父级 CMakeLists.txt
项目目录的文件都准备好了后,还需要修改父级的 CMake 脚本,即 application 目录下的 CMakeLists.txt,使能例程的构建。
add_subdirectory_if_exist(my_demo)
2.5 修改父级 Kconfig
同时还需要修改父级的 Kconfig 脚本,即 application 目录下的 Kconfig 文件,使能例程的配置。
config MY_DEMO_ENABLEboolprompt "Enable My Demo"default nhelpThis option enable my demo.if MY_DEMO_ENABLE
osource "application/my_demo/Kconfig"
endif
2.6 修改 config.py
最后修改 build\config\target_config\bs21e 目录下的 config.py 文件,这个脚本是用来控制 SDK 编译的。在“ram_component”这个列表里面添加“my_demo”组件,名字一定要跟 CMakeLists.txt 配置的组件名一致。
2.7 编译
上面的操作做完我们就可以按照一般的步骤去编译应用了,首先打开 Kconfig 的配置。
这时候可以看到 Demo Name 的设置,修改为自己喜欢的名字,点击保存然后退出,重编译 SDK,烧录固件,在串口助手如下:
3. 应用启动流程
从上面可以看到,例程与我们平时写的有很大不同,就是我们不需要写main函数也能启动应用,这是因为 SDK 已经把 main 函数给封装好了。所以下面就讲一讲 SDK 的是怎么引导应用启动的。
真正的 main 函数其实在 application/bs21e/standard 目录的 main.c 文件内:
void main(const void *startup_details_table)
{UNUSED(startup_details_table);main_init();
}
main 函数内就调用了 main_init 函数,内容如下:
void main_init(void)
{
#ifdef SUPPORT_CHIP_N1200
#ifndef DEVICE_ONLYrl_driver_deinit();osal_hook_register(os_irq_restore, os_irq_lock, os_irq_unlock);
#endif
#endiffunc_patch_init();watchdog_init();app_mpu_enable();
#if defined(CONFIG_SECURE_STORAGE_SUPPORT)uapi_drv_cipher_env_init();
#endif/* rtos kernel init. */if (rtos_kernel_init() == ERRCODE_SUCC) {/* hardware & software init. */chip_hw_init();chip_sw_init();chip_rf_init();pm_state_check();/* rtos thread init. */app_os_init();/* rtos kernel start. */rtos_kernel_start();}for (;;) { }
}
这里面就是初始化硬件外设和 LiteOS 内核,比较关键的是 app_os_init 函数,它的实现如下:
__attribute__((weak)) void app_os_init(void)
{osal_task *cur_handle = NULL;/* 线程创建接口 */osal_kthread_lock();for (uint8_t i = 0; i < M_NUM_TASKS; i++) {cur_handle = osal_kthread_create(g_app_tasks[i].task_func, g_app_tasks[i].task_arg,g_app_tasks[i].task_name, g_app_tasks[i].task_stack);if (cur_handle == NULL) {panic(PANIC_TASK_CREATE_FAILED, i);}osal_kthread_set_priority(cur_handle, g_app_tasks[i].task_pri);/* AT命令线程 */
#ifdef TEST_SUITE
#ifdef AT_COMMANDif (strcmp(g_app_tasks[i].task_name, "at") == 0) {uapi_set_at_task((uint32_t *)cur_handle->task);osal_kthread_suspend(cur_handle);}
#endif
#endif}osal_kthread_unlock();
#ifdef TEST_SUITEcmd_main_add_functions();
#endif
}
这个函数是带 __attribute__((weak)) 属性的,所以我们可以重定义这个函数,来实现我们自己的应用初始化流程。
SDK 默认的实现主要就是创建一系列的任务,其中就包含一个很关键的 app_main 任务,它的实现如下:
__attribute__((weak)) void app_main(void *unused)
{UNUSED(unused);hal_reboot_clear_history();system_boot_reason_print();system_boot_reason_process();
#if (USE_COMPRESS_LOG_INSTEAD_OF_SDT_LOG == NO)log_exception_dump_reg_check();
#endif
#if defined(CONFIG_SAMPLE_ENABLE)app_tasks_init();
#endif
#ifdef OS_DFX_SUPPORTprint_os_task_id_and_name();
#endifwhile (1) { //lint !e716 Main Loop(void)osal_msleep(TASK_COMMON_APP_DELAY_MS);oml_pf_log_print0(LOG_BCORE_PLT_DRIVER_REBOOT, LOG_NUM_DEBUG, LOG_LEVEL_INFO, "App main");
#if defined(PM_MCPU_MIPS_STATISTICS_ENABLE) && (PM_MCPU_MIPS_STATISTICS_ENABLE == YES)oml_pf_log_print2(LOG_BCORE_PLT, LOG_NUM_DEBUG, LOG_LEVEL_INFO,"[Mcpu mips statistics] total work time: %dus, total idle time: %dus.\r\n",pm_get_total_work_time(), pm_get_total_idle_time());PRINT("[Mcpu mips statistics] total work time: %dus, total idle time: %dus.\r\n",pm_get_total_work_time(), pm_get_total_idle_time());
#endifuapi_watchdog_kick();
#if defined(CONFIG_SLE_AMIC_TRANS_PATH_CHECK)sle_vdt_get_trans_path_value();
#endif
#if defined(MEMORY_INFO_PRINT_SUPPORT)print_stack_waterline_riscv();print_heap_statistics_riscv();
#endif
#ifdef OS_DFX_SUPPORTos_dfx_print_info();
#endif}
}
可以看到,这个任务函数也是可以重定义的。关键的是 app_tasks_init 函数,它就是负责调用我们的应用的。这个 app_main 任务还有一个作用是,它也作为系统中的空闲任务而存在;在主循环中 uapi_watchdog_kick 函数被定时调用,这是一个硬件看门狗的喂狗函数来的。
看到 app_tasks_init 函数,它的实现如下:
void app_tasks_init(void)
{init_call_t *initcall = &__zinitcall_app_run_start;init_call_t *initend = &__zinitcall_app_run_end;for (; initcall < initend; initcall++) {(*initcall)();}
}
可以看到它做的就是遍历一段内存空间,依次调用指针指向的函数,很明显这段内存空间里面存的都是函数指针。这些指针就是通过 app_run 宏定义来注册的,实现如下:
typedef void (*init_call_t)(void);#define USED_ATTR __attribute__((used))#define layer_initcall(func, layer, clayer, priority) \static const init_call_t USED_ATTR __zinitcall_##layer##_##func \__attribute__((section(".zinitcall." clayer #priority ".init"))) = (func)#define layer_initcall_def(func, layer, clayer) \layer_initcall(func, layer, clayer, 0)#define app_run(func) layer_initcall_def(func, run, "app_run")