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

【星闪】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")
    http://www.xdnf.cn/news/15266.html

    相关文章:

  • 大模型聊天模板
  • 在人工智能自动化编程时代:AI驱动开发和传统软件开发的分析对比
  • AI 助力:如何批量提取 Word 表格字段并导出至 Excel
  • Infoblox NetMRI 远程命令执行漏洞复现(CVE-2025-32813)
  • C++值类别与移动语义
  • GraphRAG Docker化部署,接入本地Ollama完整技术指南:从零基础到生产部署的系统性知识体系
  • 动物世界一语乾坤韵芳华 人工智能应用大学毕业论文 -仙界AI——仙盟创梦IDE
  • 板凳-------Mysql cookbook学习 (十一--------9)
  • Typecho分类导航栏开发指南:从基础到高级实现
  • axios拦截器
  • Java 大视界 -- Java 大数据在智能安防视频监控系统中的视频摘要快速生成与检索优化(345)
  • Oracle SQL - 使用行转列PIVOT减少表重复扫描(实例)
  • 前端-CSS-day3
  • 【FreeRTOS】事件组
  • 决策树学习
  • OneCode3.0 VFS分布式文件管理API速查手册
  • 网络安全的基本练习
  • Autosar CanSM配置-Busoff参数-基于ETAS软件
  • 齿轮主要的几项精度指标及检测项目学习
  • houdini vat 学习笔记
  • 日志不再孤立!用 Jaeger + TraceId 实现链路级定位
  • 力扣刷题(第八十五天)
  • 【CMake】CMake构建项目入门
  • 【华为OD】MVP争夺战(C++、Java、Python)
  • 多表查询-4-外连接
  • 使用包管理工具CocoaPods、SPM、Carthage的利弊与趋势
  • 【机器学习入门巨详细】(研0版)二创OPEN MLSYS
  • CTFHub————Web{信息泄露[Git泄露(Stash、Index)]}
  • Linux进程管理的核心:task_struct中的双链表与网状数据结构
  • 数据结构之并查集和LRUCache