1、实验简介

参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/a3_kernel_timer

1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 软件定时器(Timer) 的基本概念与编程方法。通过本实验,你将学会:

  1. 理解软件定时器的工作原理及其与系统 Tick 时钟的关系;
  2. 使用 CMSIS-RTOS v2 标准接口创建、启动和管理定时器;
  3. 通过定时器回调函数实现周期性任务调度;
  4. 完成案例代码的编译、烧录与串口调试。

1.2、实验内容

本案例在 LZ3863-星闪开发板上创建 两个周期性软件定时器

定时器名称 类型 触发周期 回调函数 输出内容
timer1 周期性 约 1 秒 timer1_timeout This is Timer1 Timeout function
timer2 周期性 约 2 秒 timer2_timeout This is Timer2 Timeout function

两个定时器独立运行,由内核定时器模块按各自设定的周期触发回调函数,通过串口周期性打印日志,直观展示多定时器并发工作效果。

1.3、实验环境

项目 说明
硬件 LZ3863-星闪开发板、USB 数据线
软件 OpenHarmony v5.1.0 源码、hb 编译工具
调试工具 串口助手(波特率 115200)
案例路径 applications/sample/wifi-iot/app/a3_kernel_timer/

2、基础知识

2.1、什么是软件定时器

软件定时器 是基于系统 Tick 时钟中断,由软件模拟实现的定时器。当经过设定的 Tick 计数值后,会触发用户定义的回调函数。定时器的精度与系统 Tick 时钟周期直接相关。

与硬件定时器相比,软件定时器具有以下特点:

  • 数量不受硬件限制:硬件定时器数量有限,软件定时器可满足更多并发定时需求;
  • 基于系统 Tick:精度取决于内核时钟节拍(本系统默认 1 tick = 10 ms);
  • 回调函数执行:超时后在定时器任务上下文中调用用户注册的回调函数。

在 OpenHarmony LiteOS-M 中,软件定时器是系统资源,模块初始化时已分配连续内存,并使用系统队列和任务资源进行管理。定时器触发遵循 先进先出 的队列规则,定时时间短的定时器优先被触发。

2.2、定时器类型

CMSIS-RTOS v2 提供两种定时器类型:

类型 宏定义 说明
单次定时器 osTimerOnce 超时触发一次回调后自动停止
周期性定时器 osTimerPeriodic 超时触发回调后自动重启,按周期反复触发

本实验使用 周期性定时器osTimerPeriodic),定时器启动后会按设定间隔持续触发回调函数。

2.3、定时器与任务的区别

对比项 软件定时器 任务(线程)
执行方式 超时后调用回调函数,回调执行完毕后返回 独立线程持续运行,通过 osDelay 周期性执行
资源占用 共享定时器任务资源,开销较小 每个任务需要独立栈空间,开销较大
适用场景 简单的周期性触发、超时检测 复杂的持续运行逻辑、多步骤处理
创建接口 osTimerNew osThreadNew

2.4、核心 API 介绍

2.4.1、头文件
#include "ohos_init.h"
#include "cmsis_os2.h"
#include <stdio.h>
2.4.2、常用定时器 API
API 名称 功能说明
osTimerNew 创建并初始化定时器
osTimerStart 启动或重启指定定时器
osTimerStop 停止指定定时器
osTimerIsRunning 检查定时器是否正在运行
osTimerGetName 获取定时器名称
osTimerDelete 删除定时器
2.4.3、osTimerNew — 创建定时器
osTimerId_t osTimerNew(osTimerFunc_t func, osTimerType_t type,
                       void *argument, const osTimerAttr_t *attr);
参数 说明
func 定时器超时回调函数
type 定时器类型:osTimerOnce(单次)或 osTimerPeriodic(周期)
argument 传递给回调函数的用户参数
attr 定时器属性(名称等),可为 NULL 使用默认值

注意:回调函数应尽量简短,避免执行耗时操作,以免影响其他定时器的及时触发。

2.4.4、osTimerStart — 启动定时器
osStatus_t osTimerStart(osTimerId_t timer_id, uint32_t ticks);
参数 说明
timer_id 定时器 ID,由 osTimerNew 返回
ticks 首次超时及周期间隔的 Tick 数(本系统 1 tick = 10 ms)
  • osTimerStart(timer_id, 100) ≈ 首次 1 秒后触发,之后每 1 秒触发一次;
  • osTimerStart(timer_id, 200) ≈ 首次 2 秒后触发,之后每 2 秒触发一次。
2.4.5、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(timer_example);

该宏将 timer_example 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。


3、程序设计

3.1、程序架构

本案例文件目录结构

a3_kernel_timer/
├── kernel_timer_example.c   # 主程序源码
├── BUILD.gn                 # GN 编译配置
├── README_zh.md             # 案例简要说明
└── 实验手册.md               # 本实验手册

本案例采用 定时器回调结构

系统启动
    │
    ▼
timer_example()              ← APP_FEATURE_INIT 注册,系统启动时自动执行
    ├── osTimerNew + osTimerStart → timer1(周期 1 秒)
    │       └── timer1_timeout()  ← 超时回调,打印 Timer1 日志
    └── osTimerNew + osTimerStart → timer2(周期 2 秒)
            └── timer2_timeout()  ← 超时回调,打印 Timer2 日志

3.2、源文件说明

文件 说明
kernel_timer_example.c 主程序源码,包含定时器创建与回调函数
BUILD.gn GN 编译配置文件,定义静态库 timer_example

3.3、关键代码分析

(1)定时器 1 回调函数 — timer1_timeout

定时器 1 每次超时时被内核调用,打印日志:

void timer1_timeout(void *arg)
{
    (void)arg;
    printf("This is Timer1 Timeout function\n");
}
(2)定时器 2 回调函数 — timer2_timeout

定时器 2 每次超时时被内核调用,打印日志:

void timer2_timeout(void *arg)
{
    (void)arg;
    printf("This is Timer2 Timeout function\n");
}
(3)应用入口 — timer_example

系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,创建并启动两个周期性定时器:

void timer_example()
{
    osTimerId_t timer_id1, timer_id2;
    osStatus_t ret;

    // 创建周期性定时器 1
    timer_id1 = osTimerNew(timer1_timeout, osTimerPeriodic, NULL, NULL);
    if (timer_id1 == NULL) {
        printf("create timer1 fail\n");
        return;
    }

    // 启动定时器 1,首次延迟约 1 秒,之后每 1 秒触发一次
    ret = osTimerStart(timer_id1, 100);
    if (ret != osOK) {
        printf("start timer1 fail ret:%d\n", ret);
        return;
    }

    // 创建周期性定时器 2
    timer_id2 = osTimerNew(timer2_timeout, osTimerPeriodic, NULL, NULL);
    if (timer_id2 == NULL) {
        printf("create timer2 fail\n");
        return;
    }

    // 启动定时器 2,首次延迟约 2 秒,之后每 2 秒触发一次
    ret = osTimerStart(timer_id2, 200);
    if (ret != osOK) {
        printf("start timer2 fail ret:%d\n", ret);
        return;
    }
}

APP_FEATURE_INIT(timer_example);

3.4、程序执行流程

timer2_timeout timer1_timeout timer2 timer1 timer_example 系统启动 timer2_timeout timer1_timeout timer2 timer1 timer_example 系统启动 loop [每约 1 秒] loop [每约 2 秒] APP_FEATURE_INIT 触发 osTimerNew + osTimerStart(100) osTimerNew + osTimerStart(200) 超时触发回调 printf("Timer1 Timeout...") 超时触发回调 printf("Timer2 Timeout...")

4、编译步骤

4.1、确认案例目录

确认案例已位于 OpenHarmony 源码目录下:

applications/sample/wifi-iot/app/a3_kernel_timer/
├── kernel_timer_example.c
├── BUILD.gn
└── 实验手册.md

若从外部复制,请将 a3_kernel_timer 目录放到上述 app/ 路径下。

4.2、修改 BUILD.gn(注册编译组件)

编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:

lite_component("app") {
  features = [
    "startup",
    "a3_kernel_timer:timer_example",   // 添加此行
  ]
}

4.3、修改 SDK 配置文件

步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py

找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:

"timer_example"

步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake

找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:

"timer_example"

4.4、执行编译

在 OpenHarmony 源码根目录下执行:

rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 ws63-liteos-app)
hb build -f

编译成功后,固件输出路径通常在 out/ws63/ 目录下。

4.5、烧录固件

使用开发板配套的烧录工具,将编译生成的固件烧写到 LZ3863-星闪开发板。具体烧录步骤请参考开发板用户手册。


5、运行结果

5.1、串口配置

参数
波特率 115200
数据位 8
停止位 1
校验位

5.2、预期输出

烧录并复位开发板后,串口助手应显示类似以下日志:

This is Timer1 Timeout function
This is Timer1 Timeout function
This is Timer2 Timeout function
This is Timer1 Timeout function
This is Timer1 Timeout function
This is Timer2 Timeout function
This is Timer1 Timeout function
This is Timer1 Timeout function
This is Timer2 Timeout function
...

5.3、结果分析

现象 说明
Timer1 日志约每 1 秒出现 1 次 osTimerStart(timer_id1, 100) 生效,100 tick ≈ 1 秒
Timer2 日志约每 2 秒出现 1 次 osTimerStart(timer_id2, 200) 生效,200 tick ≈ 2 秒
每 2 秒内 Timer1 出现 2 次、Timer2 出现 1 次 两个定时器按各自周期独立触发,互不干扰
create timer fail 等错误日志 定时器创建与启动均成功

5.4、常见问题排查

问题 可能原因 解决方法
无任何串口输出 串口参数错误或线序接错 确认波特率 115200,检查 TX/RX 接线
编译报错找不到 timer_example BUILD.gn 或 config.py 未正确修改 逐步核对 4.2、4.3 节的三处配置
打印 create timer fail 系统定时器资源耗尽 检查是否有其他模块占用过多定时器,或减少定时器数量
打印 start timer fail 定时器 ID 无效或参数错误 确认 osTimerNew 返回值非 NULL 后再启动
触发频率不对 内核 tick 配置不同 确认 tick 为 10 ms,或相应调整 osTimerStart 参数
回调函数未执行但无报错 定时器任务优先级过低或被阻塞 检查回调函数中是否有阻塞调用,保持回调简短

6、实验扩展

完成基本实验后,可尝试以下扩展练习:

  1. 改为单次定时器:将 osTimerPeriodic 改为 osTimerOnce,观察定时器只触发一次后停止的行为;
  2. 动态停止与重启:在回调函数中调用 osTimerStop 停止定时器,延时后通过 osTimerStart 重新启动;
  3. 传递用户参数:创建定时器时将 argument 设为自定义数据,在回调函数中解析并打印;
  4. 设置定时器名称:通过 osTimerAttr_t 结构体为定时器指定名称,并用 osTimerGetName 验证;
  5. 对比任务实现:参照 a1_kernel_task 案例,用 osThreadNew + osDelay 实现相同周期输出,比较两种方式的差异。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐