应用程序开发

Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.


应用启动流程与注册机制

UniRTOS应用层的启动由SDK中 qos_applications/app_init/apps_init.c 统一管理。系统上电后调用 apps_init(),该函数通过链接器Section机制收集所有已注册的初始化入口,按优先级排序逐一调用。

入口函数调用链

../../_images/board_ZKfHwToi2hsufTbQqLucaPhKnaf.jpg

外部应用注册

增加外部应用时使用 UNIRTOS_APP_EXPORT 注册外部应用的程序入口,源码定义位于SDK路径 qos_applications/app_init/unirtos_app_init_registry.h

typedef void (*unirtos_app_init_fn_t)(void);

typedef struct
{
    unsigned short        order;   // Initialization order: the smaller the value, the earlier it executes
    const char           *name;    // Application Name
    unirtos_app_init_fn_t fn;      // Initialize function pointer
} unirtos_app_init_entry_t;

#define UNIRTOS_APP_EXPORT(order_value, entry_name, entry_fn)          \
    static const unirtos_app_init_entry_t __unirtos_app_init_##entry_fn \
    UNIRTOS_APP_INIT_USED UNIRTOS_APP_INIT_SECTION = {                  \
        order_value,                                                    \
        entry_name,                                                     \
        entry_fn                                                        \
    }

该宏调用后即完成注册,无需修改任何SDK内部文件。在系统启动时会自动调用已注册的函数入口。参考示例如下:

#include "qosa_def.h"
#include "qosa_sys.h"
#include "qosa_log.h"
#define QOS_LOG_TAG   LOG_TAG_DEMO

//Include the header file required for the UNIRTOS_APP_EXPORT
#include "unirtos_app_init_registry.h"

//Define the main process function for the hello world task
static void unir_hello_world_demo_process(void *ctx)
{
    /* Omit the specific implementation */
}

//Initialization function for the hello world demo
void unir_hello_world_init(void)
{
    /* Omit the specific implementation */
}
/* 
* Parameter 1: 700    Order value, which determines the initialization sequence. 
* Parameter 2: unir_hello_world_demo  Application entry name, must be unique within the same firmware. 
* Parameter 3: unir_hello_world_init  Actual initialization function with the signature void fn(void). 
*/
UNIRTOS_APP_EXPORT(700, "unir_hello_world_demo", unir_hello_world_init);

多应用注册

UniRTOS支持在同一项目中同时注册多个函数入口。通常情况下,项目只需注册一个函数入口即可。当项目中包含多个子应用时,开发者可在编写的子应用代码中使用 UNIRTOS_APP_EXPORT 分别注册对应的子应用程序入口即可。需要注意各个子应用的依赖关系,通过修改 UNIRTOS_APP_EXPORT 注册时的order参数值决定初始化顺序。多应用启动结构示意图如下:

image

应用开发示例

以开发一个“GPIO控制LED”应用为例,通过不断改变GPIO的输出电平,使LED达到闪烁的效果。

创建新项目

使用unirtos-cli工具创建一个新应用,应用名称为new-app。new命令详细说明 参考。在PowerShell窗口执行:

unirtos-cli new new-app -d E:\unirtos-cli_demos

拉取编译环境

创建项目成功后,当前目录下将生成 new-app 项目文件夹。进入该目录后,根据实际需要修改新建项目目录下的 env_config.json 配置文件,有关配置项的详细说明,请参考 配置文件说明文档

env_config.json 配置文件示例:

{
  "unirtos_root": "",
  "build": {
    "module": "EG800ZCN_LA",
    "version": "EG800ZCNLAR01A01_BETA_OCPU_20260707",
    "jobs": 8
  },
  "sdk": {
    "version": "1.0.1",
  },
  "libraries": {
    "list": [
      {
        "name": "lib-name",
        "version": "2.0.0"
      }
    ]
  }
}

修改配置之后,在PowerShell窗口执行:

unirtos-cli env-setup

关键头文件说明

OS基础类型(qos_components/system/os/):

头文件

包含内容

qosa_def.h

基础数据类型定义(qosa_uint8_t、qosa_int32_t等)

qosa_sys.h

系统级API声明(任务创建、信号量、互斥锁等)

qosa_errno.h

错误码定义

qosa_defer_time.h

延时接口声明

常用外设接口(qos_components/system/hal/),以下仅以部分外设举例,其他外设可在目录下自行查看。

头文件

包含内容

qosa_adc.h

ADC功能相关API声明,如“qosa_adc_get_volt()

qosa_gpio.h

GPIO功能相关API声明,如“qosa_gpio_init()

qosa_uart.h

UART功能相关API声明,如“qosa_uart_open()

qosa_iic.h

I2C功能相关API声明,如“qosa_i2c_init()

常用组件头文件,通常需要在menuconfig中同步开启对应功能,以部分组件举例:

头文件

具体路径

包含内容

qcm_mqtt.h

qos_components/components/qcm_mqtt/public/

MQTT组件功能相关API声明,如“qcm_mqtt_client_publish()

qcm_websocket.h

qos_components/components/qcm_websocket/public/

Websocket功能相关API声明,如“qcm_ws_open_proc()

qcm_ntp_app.h

qos_components/components/qcm_ntp/public/

NTP功能相关API声明,如“qcm_ntp_client_new()

当前仅介绍少许常用头文件路径,其他未提及的头文件均可在SDK的“qos_components/”路径下查询。

CMakeLists编写

UniRTOS中所有自定义模块(包括Demo和组件)都会编译成静态库(STATIC),由CMake管理。构建系统通过 add_apps_libraries() 将库路径汇总到全局变量 apps_libraries,最终统一参与链接。

编写CMakeList是为了将当前外部应用加入构建过程,模版 CMakeList.txt 位于项目目录下,参考如下:

message(STATUS "cmake config ${CMAKE_CURRENT_SOURCE_DIR}")

# This CMakeLists is designed to be imported by SDK root CMake via add_subdirectory.
# It follows the UniRTOS external app contract (same pattern as unirtos project create).

# Application target name: from environment variable or fallback to app root folder name.
if(DEFINED ENV{UNIRTOS_APP_TARGET_NAME})
    set(target $ENV{UNIRTOS_APP_TARGET_NAME})
else()
    get_filename_component(target ${CMAKE_CURRENT_SOURCE_DIR} NAME)
endif()
message(STATUS "UniRTOS app target name: ${target}")
add_apps_libraries($<TARGET_FILE:${target}>)
add_library(${target} STATIC)
set_target_properties(${target} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${out_unir_lib_dir})

# Allow nested user components in common conventions.
# add_subdirectory_if_exist(app_components)

# SDK global include roots are exposed through SOURCE_TOP_DIR in SDK CMake.
target_include_directories(${target} PRIVATE 
    ${SOURCE_TOP_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/main/src
)

# Add your include directories here.
target_include_directories(${target} PUBLIC 
    ${CMAKE_CURRENT_SOURCE_DIR}/main/inc
)

# Add your source files here.
file(GLOB_RECURSE APP_SRC
    ${CMAKE_CURRENT_SOURCE_DIR}/main/src/*.c
)

target_sources(${target} PRIVATE ${APP_SRC})

# Automatically link external libraries declared in env_config.json.
# This lets app sources include library headers and use library APIs directly.
set(_app_external_library_targets)
if(DEFINED UNIRTOS_EXTERNAL_LIBRARY_TARGETS AND NOT "${UNIRTOS_EXTERNAL_LIBRARY_TARGETS}" STREQUAL "")
        list(APPEND _app_external_library_targets ${UNIRTOS_EXTERNAL_LIBRARY_TARGETS})
endif()

get_property(_global_external_library_targets GLOBAL PROPERTY unirtos_external_library_targets)
if(_global_external_library_targets)
        list(APPEND _app_external_library_targets ${_global_external_library_targets})
endif()

if(_app_external_library_targets)
        list(REMOVE_DUPLICATES _app_external_library_targets)
        message(STATUS "[app-target-link] ${target} links external library targets from env_config: ${_app_external_library_targets}")
        target_link_libraries(${target} PUBLIC ${_app_external_library_targets})
endif()

基于unirtos-cli创建的模板工程,若新编写的应用源码与头文件分别位于 main/srcmain/inc 目录下,则模板中的 CMakeLists.txt 无需额外配置——其内置规则会自动包含inc目录下的所有头文件,并将src目录中的所有 .c 源文件纳入编译。

若项目结构与模版略有不同,CMakeLists.txt 的主体结构也可完全复用,仅需关注如下两项:

  1. 头文件搜索路径,在 target_include_directories 当中补充自定义头文件目录,确保编译器可解析外部函数接口。

  2. 源文件所在路径,在 file(GLOB_RECURSE APP_SRC ${CMAKE_CURRENT_SOURCE_DIR}/main/src/*.c) 中增加或修改源文件所在路径,使之指向当前应用的源码存放位置,从而保证函数实现的正确链接。

逻辑脚本编写

逻辑脚本的编写只需正常导入头文件,调用相关API接口,实现需要的功能逻辑,最后使用 UNIRTOS_APP_EXPORT 宏注册对应的程序入口即可。UNIRTOS_APP_EXPORT 相关介绍请参考 外部应用注册。以调用GPIO相关API接口实现LED闪烁逻辑为例,编写逻辑脚本 led.c 并存放在项目目录下的 main/src 中,led.c 内容如下:

/*****************************************************************/ /**
* @file led_gpio.c
* @brief
* The demo initializes a GPIO pin connected to an LED, then toggles the LED on and off in a loop with a delay. 
* @author lysander.li@quectel.com
* @date 2026-03-27
*
**********************************************************************/
#include "qosa_sys.h"
#include "qosa_gpio.h"
#include "qosa_pinctrl.h"
#include "qosa_def.h"
#include "qosa_log.h"
#include "unirtos_app_init_registry.h"

#define QOS_LOG_TAG   LOG_TAG_DEMO

#define UniRTOS_LED_DEMO_TASK_STACK_SIZE 1024  // Task stack size 1KB

#define UniRTOS_LED_DEMO_TASK_PRIO QOSA_PRIORITY_NORMAL // Normal priority

static qosa_task_t g_quec_test_demo_task = QOSA_NULL;

#define LED_PIN_NUM 19

qosa_pin_cfg_t pin_cfg; // Global variable to store the LED pin configuration, used for both initialization and level setting

/*
    Name: unir_led_init
    Description: Initialize the LED GPIO pin.
    @return 0 on success, 1 on failure
*/
static qosa_uint8_t unir_led_init(void)
{
    qosa_memset(&pin_cfg, 0, sizeof(qosa_pin_cfg_t));
    qosa_get_pin_default_cfg(LED_PIN_NUM, &pin_cfg);
    qosa_pin_set_func(LED_PIN_NUM, pin_cfg.gpio_func);
    // Initialize the LED GPIO pin as output, with pull-up and default level high (LED off)
    if (qosa_gpio_init(pin_cfg.gpio_num, QOSA_GPIO_DIRECTION_OUTPUT, QOSA_GPIO_PULL_UP, QOSA_GPIO_LEVEL_HIGH) != QOSA_GPIO_SUCCESS)
    {
        QLOGD("[led]Failed to initialize LED GPIO");
        return 1; // Return 1 on failure
    }
    QLOGI("[led]]LED GPIO initialized successfully, pin_num: %d, gpio_num: %d, level: %d", LED_PIN_NUM, pin_cfg.gpio_num, QOSA_GPIO_LEVEL_HIGH);
    return 0;
}

/*
    Name: unir_led_set
    Description: Set the LED GPIO level to on or off.
    @param gpio_level: The desired GPIO level for the LED, where QOSA_GPIO_LEVEL_LOW turns the LED on and QOSA_GPIO_LEVEL_HIGH turns it off.
    @return 0 on success, 1 on failure
*/
static qosa_uint8_t unir_led_set(qosa_gpio_level_e gpio_level)
{ 
    if(qosa_gpio_set_level(pin_cfg.gpio_num, gpio_level) != QOSA_GPIO_SUCCESS)
    {
        QLOGD("[led]]Failed to set LED GPIO level");
        return 1; // Return 1 on failure
    }
    return 0;
}

/*
    Name: unir_led_demo_process
    Description: The main process function for the TEST Demo, which initializes the LED and toggles it on and off in a loop.
    @param ctx: Task context pointer, reserved for future use, currently not used
    @return None
*/
static void unir_led_demo_process(void *ctx)
{
    unir_led_init();
    while (1)
    {
        unir_led_set(QOSA_GPIO_LEVEL_LOW);
        QLOGI("[led]]LED ON");
        qosa_task_sleep_ms(1000);
        unir_led_set(QOSA_GPIO_LEVEL_HIGH);
        QLOGI("[led]]LED OFF");
        qosa_task_sleep_ms(1000);
    }
    
}

/*
    Name: unir_led_demo_init
    Description: Initialize the TEST Demo, create a task to run the demo.
    @param None
*/
void unir_led_demo_init(void)
{
    // Log the entry of the TEST Demo initialization
    QLOGV("[led]]enter TEST DEMO !!!");

    // Create a task for the TEST Demo using qosa_task_create, with specified stack size, priority, name, and entry function
    if (g_quec_test_demo_task == QOSA_NULL) // Check if the TEST Demo task has already been created
    {       
        
        qosa_task_create(
            &g_quec_test_demo_task,
            UniRTOS_LED_DEMO_TASK_STACK_SIZE,     // Task stack size
            UniRTOS_LED_DEMO_TASK_PRIO,           // Task priority
            "test_demo",                           // Task name
            unir_led_demo_process,                // Task entry function
            QOSA_NULL                             // Task context (not used in this case)
        );
    }
}

UNIRTOS_APP_EXPORT(700, "unir_led_demo", unir_led_demo_init);

配置额外功能

应用程序开发过程中,可能依赖若干UniRTOS系统默认关闭的功能组件,例如云平台对接常用的MQTT协议栈及 TLS加密库。此类组件默认未启用,开发者须在menuconfig配置界面中主动打开对应的宏控开关。该操作是正确引用组件头文件及调用其API接口的前提条件。在当前的“GPIO控制LED”应用中无需开启额外功能,因此无需进行配置。menuconfig的使用可参考 上文

编译外部应用

在当前项目目录打开PowerShell窗口,执行命令:

unirtos-cli build

编译生成的固件位于项目目录的qos_build/release中,将固件烧录至目标开发板以验证功能即可。烧录固件 参考