一 本节介绍
📝本节我们将学习 machine.Timer 的基本用法,包括单次定时、周期定时、回调函数设计,以及定时器资源的安全释放。
教程同时适用于以下两款开发板:
- 立创·庐山派K230-CanMV开发板
- 立创·庐山派Lite-K230D-CanMV开发板
1.1 学习目标
🏆学习目标
1️⃣ 理解软件定时器、单次模式和周期模式的区别。
2️⃣ 掌握 Timer(-1)、init() 和 deinit() 的常用方法。
3️⃣ 理解 Timer 回调的执行约束,避免在回调中执行耗时或可能分配内存的操作。
4️⃣ 能正确复用同一个 Timer 对象,先完成单次定时,再切换到周期定时。
5️⃣ 能运行一份通过 get_board_info() 自动适配两款开发板的绿色 LED 例程。
1.2 重点提示
IMPORTANT
本教程统一使用 Timer(-1) 软件定时器。当前 CanMV v1.8 源码已经实现 0~5 号硬件定时器,但不同固件版本、系统组件和外设服务对硬件定时器资源的占用情况可能不同。基础教程使用软件定时器,可以减少资源冲突并提高两款开发板之间的可移植性。
程序运行后没有反应?请先升级固件
如果运行本节例程后没有终端输出、Timer 回调不触发,或板载绿色 LED 始终没有变化,请先不要修改例程参数。部分旧版 CanMV 固件的 Timer 功能可能存在 BUG。
请先备份 TF 卡中的脚本和数据,再按照快速入门完成固件升级。固件请从CanMV K230 Daily Build 官方目录下载,并选择与开发板型号匹配的最新 Daily Build 版本。
立创·庐山派K230-CanMV开发板与立创·庐山派Lite-K230D-CanMV开发板使用各自对应的固件镜像,两者不能混用。
⚠️回调函数必须足够轻量
当前 CanMV Timer 实现会把回调按硬中断回调处理。回调中不要执行 print()、sleep()、文件读写、网络通信、传感器读取、显示刷新或复杂对象创建。本教程的回调只设置一个布尔标志,LED 控制和终端打印全部放在主循环中完成。
最小可配置周期不等于定时精度
CanMV v1.8 源码要求换算后的定时周期不小于 5 ms,但这只表示参数能够通过检查,不代表回调延迟、抖动或任务执行周期能达到 5 ms 的硬实时保证。对稳定波形、较高频率或严格实时性有要求时,应优先使用 PWM、专用硬件外设或经过真板验证的硬件定时方案。
1.3 本节效果
运行完整例程后,程序会先启动一个 1000 ms 单次定时器。单次事件到达后,主循环再把同一个 Timer 对象切换为 500 ms 周期模式。板载绿色 LED 随后翻转 10 次,程序自动停止定时器、熄灭 LED 并释放资源。
二 软硬件准备
| 名称 | 数量 | 说明 |
|---|---|---|
| 立创·庐山派K230-CanMV开发板或立创·庐山派Lite-K230D-CanMV开发板 | 1 | 二选一,本教程同时适配 |
| TF 卡 | 1 | 已写入与板卡型号匹配的 CanMV MicroPython 固件 |
| Type-C 数据线 | 1 | 需要支持数据传输,不能只支持充电 |
| 电脑 | 1 | 用于连接开发板、运行脚本并查看串行终端 |
| 外接模块 | 0 | 基础实验直接使用软件定时器和板载绿色 LED |
固件要求:使用与开发板型号匹配的较新 CanMV MicroPython 固件。K230 与 Lite-K230D 的固件镜像不能混用。
开发环境:可以使用与当前固件配套的 CanMV IDE K230、CanMV IDE Web,或 VS Code 配合 CanMV 扩展。
三 双板兼容说明
| 项目 | 立创·庐山派K230-CanMV开发板 | 立创·庐山派Lite-K230D-CanMV开发板 |
|---|---|---|
| 是否支持本节实验 | 支持 | 支持 |
| 主控 | K230 | K230D |
| Timer API | machine.Timer | machine.Timer |
| 本例定时器 | Timer(-1) 软件定时器 | Timer(-1) 软件定时器 |
| Timer 外部接线 | 不需要 | 不需要 |
| 板载绿色 LED | GPIO20,低电平点亮 | GPIO66,高电平点亮 |
| 板卡适配方式 | get_board_info() 自动适配 | get_board_info() 自动适配 |
IMPORTANT
machine.Timer 的使用方法在两款开发板上相同,纯定时任务不需要区分板型。本教程为了直观展示周期事件,额外使用板载绿色 LED;两板的 LED 引脚和有效电平不同,因此完整例程必须同时适配 GPIO 和点亮电平。
四 基础知识与名词解释
4.1 什么是定时器
Timer 用于在指定时间间隔后触发回调。主程序不需要一直用 sleep() 阻塞等待,因此可以在等待期间继续处理其他任务。
| 名词 | 说明 |
|---|---|
| 定时周期 | 两次周期事件之间的目标时间间隔 |
| 回调函数 | 定时事件到达时,由运行环境调用的函数 |
| 软件定时器 | 由系统软件调度的定时器,本教程使用 Timer(-1) |
| 硬件定时器 | 由片上定时器外设提供的计时资源,当前绑定使用编号 0~5 |
| 抖动 | 实际触发时刻相对理想时刻发生的短时偏差 |
4.2 单次模式与周期模式
| 模式 | 常量 | 行为 | 常见用途 |
|---|---|---|---|
| 单次模式 | Timer.ONE_SHOT | 等待一个周期后触发一次,随后停止 | 延时启动、超时处理、一次性动作 |
| 周期模式 | Timer.PERIODIC | 每经过一个周期持续触发 | 状态采样、心跳任务、低频状态更新 |
单次模式触发后不会自动变成周期模式。如果需要像本教程一样先单次、再周期,应该等单次事件已经发生,再在主循环中调用 tim.init() 重新配置。
不要连续覆盖配置
下面的写法会立即用周期配置覆盖单次配置,单次回调通常还没来得及触发:
tim.init(period=100, mode=Timer.ONE_SHOT, callback=timer_callback)
tim.init(period=1000, mode=Timer.PERIODIC, callback=timer_callback)2
正确做法是等待单次回调设置事件标志,由主循环确认该事件后,再切换为周期模式。
4.3 软件定时器与硬件定时器
当前 CanMV v1.8 的 Timer 绑定接受 -1 和 0~5:
Timer(-1)表示软件定时器。Timer(0)~Timer(5)表示 6 个硬件定时器编号。
本教程选择 Timer(-1),原因是它不占用固定编号的硬件定时器,适合基础示例以及对硬实时性没有要求的低频任务。
一个容易忽略的实现细节
在当前 CanMV v1.8 实现中,软件定时器对象是全局复用的。多次调用 Timer(-1) 不能视为创建了多个相互独立的软件定时器;再次 init() 会重配置同一个软件定时器资源。需要多个并发定时任务时,建议使用一个基础节拍在主循环中分发任务,或在确认资源占用后使用不同编号的硬件定时器。
4.4 回调函数的设计原则
Timer 回调接收一个参数,该参数是触发回调的 Timer 对象:
timer_event_pending = False
def timer_callback(_timer):
global timer_event_pending
timer_event_pending = True2
3
4
5
6
上面的回调只进行全局布尔标志赋值。主循环检测到标志后,再完成 LED、日志或业务处理。
| 适合放在回调中 | 应放在主循环中 |
|---|---|
| 设置简单标志 | print() 与字符串格式化 |
| 更新经过验证的简单状态 | sleep() 或其他阻塞等待 |
| 写入预分配缓冲区 | 文件、网络和传感器访问 |
| 触发最小化的后续处理通知 | 显示刷新、图像处理和复杂 GPIO 流程 |
使用布尔标志意味着:如果主循环处理速度低于定时器触发速度,多个事件可能合并为一个待处理事件。本例采用 500 ms 周期和 10 ms 主循环休眠,处理余量充足。不能丢事件的工程任务应使用预分配的数据结构或专门调度机制,并在目标固件上进行压力测试。
4.5 Timer、ticks、RTC 与 PWM 如何选择
| 需求 | 推荐接口 | 说明 |
|---|---|---|
| 让当前代码暂停一段时间 | time.sleep_ms() | 简单,但会阻塞当前执行流程 |
| 低频、非阻塞地周期触发任务 | Timer | 回调只通知,业务放主循环 |
| 测量代码执行时间或两个事件间隔 | time.ticks_ms() / time.ticks_us() | 配合 time.ticks_diff() 正确处理计数器回绕 |
| 获取年月日和时分秒 | RTC | 用于日历时间,不适合替代间隔定时器 |
| 输出稳定周期波形或控制占空比 | PWM | 不要依赖 Python 回调翻转 GPIO 模拟高频波形 |
⚠️Timer 和 RTC 都不能解决掉电走时问题
Timer 只在开发板上电且脚本运行期间工作,掉电后定时任务会立即停止。两款庐山派在硬件设计时又都没有预留 RTC 后备供电,因此开发板供电丢失后,RTC 也会停止走时;重新上电后 RTC 时间会重置。
如果产品需要“每天某个时刻执行”,应在每次上电后通过 NTP 重新校时,或外接带后备电池的 RTC,再根据可靠的日历时间进行调度。
4.6 周期、精度与抖动
当前 CanMV v1.8 源码会拒绝换算后小于 5 ms 的周期。即使参数合法,实际触发仍会受到运行环境、中断占用和系统负载等因素影响:
period=5表示目标周期为 5 ms,不表示每次回调都能零误差到达。- 回调中的工作越多,其他任务受到的影响越明显。
- 终端打印速度较慢,不应作为高频定时精度的测量手段。
- 需要评估抖动时,应使用 GPIO 翻转配合示波器或逻辑分析仪,并把测试代码压缩到最小。
五 常用 API 说明
5.1 创建 Timer 对象
Timer 类位于 machine 模块中:
from machine import Timer
tim = Timer(-1)2
3
完整构造形式为:
Timer(index, mode=Timer.PERIODIC, freq=-1, period=-1, callback=None)| 参数 | 说明 |
|---|---|
index | -1 为软件定时器;当前 CanMV v1.8 绑定中 0~5 为硬件定时器编号 |
mode | Timer.ONE_SHOT 或 Timer.PERIODIC |
freq | 整数频率,单位为 Hz;只要不等于 -1,其优先级就高于 period |
period | 整数周期,单位为 ms;CanMV v1.8 要求换算后的周期不小于 5 ms |
callback | 可调用对象,必须接收一个 Timer 参数 |
API 版本差异
旧版资料中可能出现 arg=None 或“freq 支持浮点数”等描述。当前 CanMV v1.8 源码的构造函数和 init() 均没有 arg 参数,并按整数解析 freq 与 period。为了兼容和便于审查,本教程只使用上表中的稳定参数。
5.2 init() 方法
init() 用于启动或重新配置已经创建的 Timer 对象:
tim.init(
mode=Timer.PERIODIC,
period=500,
callback=timer_callback,
)2
3
4
5
方法形式为:
tim.init(mode=Timer.PERIODIC, freq=-1, period=-1, callback=None)参数含义与构造函数一致。建议在工程代码中使用实例方法 tim.init(),并优先使用含义直观的 period。
当 freq 与 period 同时提供时,freq 优先。当前源码使用整数关系 period_ms = 1000 // freq 进行换算,因此需要注意:
freq必须使用正整数,不能设置为0。freq=2对应约500 ms。- 低于
1 Hz的周期应直接使用period,例如period=2000。 - 频率换算后的周期仍必须满足不小于
5 ms的要求。
5.3 deinit() 方法
tim.deinit()deinit() 会停止定时器、注销回调并释放相关资源。不论脚本正常结束、用户停止还是发生异常,都应该在 finally 中调用它。
5.4 常用配置示例
# 1000 ms 后触发一次
tim.init(
period=1000,
mode=Timer.ONE_SHOT,
callback=timer_callback,
)
# 每 500 ms 周期触发
tim.init(
period=500,
mode=Timer.PERIODIC,
callback=timer_callback,
)
# 每秒触发一次;与 period=1000 的目标周期相同
tim.init(
freq=1,
mode=Timer.PERIODIC,
callback=timer_callback,
)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这些片段用于展示参数写法。不要像片段排列顺序那样连续执行多个 init();每次 init() 都会替换当前配置。
六 操作步骤
6.1 确认板卡和固件
- 查看开发板丝印,确认手中是立创·庐山派K230-CanMV开发板还是立创·庐山派Lite-K230D-CanMV开发板。
- 确认 TF 卡内固件与板卡型号匹配。
- 使用支持数据传输的 Type-C 数据线连接电脑。
⚠️固件不能混用
K230 与 Lite-K230D 使用各自对应的固件镜像。不要为了运行同一份 MicroPython 脚本而混刷另一款板卡的固件。
6.2 检查示例参数
完整代码顶部提供以下参数:
ONE_SHOT_DELAY_MS = 1000
PERIODIC_INTERVAL_MS = 500
PERIODIC_TOGGLE_LIMIT = 10
MAIN_LOOP_INTERVAL_MS = 102
3
4
ONE_SHOT_DELAY_MS:单次模式的等待时间。PERIODIC_INTERVAL_MS:切换到周期模式后的触发间隔。PERIODIC_TOGGLE_LIMIT:绿色 LED 翻转次数;使用偶数可让正常结束前 LED 回到熄灭状态。MAIN_LOOP_INTERVAL_MS:主循环检查事件标志的间隔。
保持周期不小于 5 ms。如果缩短定时周期,还必须重新评估主循环负载和事件是否可能合并。
6.3 运行程序
- 打开 CanMV 开发环境并连接开发板。
- 新建脚本,将“七 代码例程”中的完整代码复制进去。
- 点击运行按钮运行脚本。
- 观察串行终端中的板卡名称和单次事件提示。
- 确认单次事件触发后,绿色 LED 每
500 ms翻转一次。 - 完成 10 次翻转后,确认 Timer 已释放且 LED 熄灭。
七 代码例程
# 立创·庐山派-K230-CanMV开发板资料与相关扩展板软硬件资料官网全部开源
# 开发板官网:www.lckfb.com
# 技术支持常驻论坛,任何技术问题欢迎随时交流学习
# 立创论坛:www.jlc-bbs.com/lckfb
# 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
# 不靠卖板赚钱,以培养中国工程师为己任
# 编写者:LCKFB-YZH
from machine import FPIOA, Pin, Timer
import os
import time
# ==================== 用户可修改参数 ====================
# 单次定时器等待时间,单位:ms
ONE_SHOT_DELAY_MS = 1000
# 周期定时器触发间隔,单位:ms
PERIODIC_INTERVAL_MS = 500
# 周期阶段 LED 翻转次数;设置为偶数,正常结束时 LED 回到熄灭状态
PERIODIC_TOGGLE_LIMIT = 10
# 主循环轮询间隔,单位:ms
MAIN_LOOP_INTERVAL_MS = 10
# ======================================================
# Timer 回调与主循环之间共享的事件标志
timer_event_pending = False
def get_board_info():
"""自动识别庐山派型号,并返回本例需要的 LED 参数。"""
board_id = os.uname()[-1]
if board_id == "k230_canmv_lckfb":
return {
"board_name": "立创·庐山派K230-CanMV开发板",
"LED_G": 20,
"LED_ON_LEVEL": 0,
"LED_OFF_LEVEL": 1,
}
return {
"board_name": "立创·庐山派Lite-K230D-CanMV开发板",
"LED_G": 66,
"LED_ON_LEVEL": 1,
"LED_OFF_LEVEL": 0,
}
def init_green_led(board):
"""根据板卡参数初始化绿色 LED,并确保初始状态为熄灭。"""
led_pin = board["LED_G"]
fpioa = FPIOA()
fpioa.set_function(
led_pin,
getattr(FPIOA, "GPIO{}".format(led_pin)),
)
led = Pin(
led_pin,
Pin.OUT,
pull=Pin.PULL_NONE,
drive=7,
)
led.value(board["LED_OFF_LEVEL"])
return led
def timer_callback(_timer):
"""Timer 回调只置位标志,不执行打印、延时或 GPIO 操作。"""
global timer_event_pending
timer_event_pending = True
BOARD = get_board_info()
green_led = None
tim = None
try:
green_led = init_green_led(BOARD)
tim = Timer(-1)
print("当前板卡:{}".format(BOARD["board_name"]))
print("绿色 LED:GPIO{}".format(BOARD["LED_G"]))
# 第一阶段:启动单次定时器
tim.init(
period=ONE_SHOT_DELAY_MS,
mode=Timer.ONE_SHOT,
callback=timer_callback,
)
print("单次定时器已启动:{} ms".format(ONE_SHOT_DELAY_MS))
stage = "one_shot"
led_is_on = False
periodic_count = 0
while True:
# 允许用户在 CanMV 开发环境中停止脚本
os.exitpoint()
if timer_event_pending:
# 先清除事件,再在主循环中处理具体业务
timer_event_pending = False
if stage == "one_shot":
print("单次事件已触发")
# 单次事件确认发生后,复用同一个对象切换为周期模式
stage = "periodic"
tim.init(
period=PERIODIC_INTERVAL_MS,
mode=Timer.PERIODIC,
callback=timer_callback,
)
print(
"周期定时器已启动:每 {} ms 触发一次".format(
PERIODIC_INTERVAL_MS
)
)
else:
led_is_on = not led_is_on
led_level = (
BOARD["LED_ON_LEVEL"]
if led_is_on
else BOARD["LED_OFF_LEVEL"]
)
green_led.value(led_level)
periodic_count += 1
led_text = "点亮" if led_is_on else "熄灭"
print(
"周期事件 {}/{}:绿色 LED {}".format(
periodic_count,
PERIODIC_TOGGLE_LIMIT,
led_text,
)
)
if periodic_count >= PERIODIC_TOGGLE_LIMIT:
print("周期演示完成,准备释放 Timer")
break
# 避免主循环持续空转占满 CPU
time.sleep_ms(MAIN_LOOP_INTERVAL_MS)
except KeyboardInterrupt:
print("用户停止运行")
except Exception as exc:
print("Timer 示例运行异常:{}".format(exc))
finally:
# 先停止 Timer,避免清理 LED 时再次产生定时事件
if tim is not None:
try:
tim.deinit()
print("Timer 已释放")
except Exception as cleanup_exc:
print("Timer 释放异常:{}".format(cleanup_exc))
if green_led is not None:
try:
green_led.value(BOARD["LED_OFF_LEVEL"])
print("程序结束,绿色 LED 已关闭")
except Exception as cleanup_exc:
print("绿色 LED 关闭异常:{}".format(cleanup_exc))
print("程序结束")
else:
print("程序结束")2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
八 代码说明
8.1 板卡自动检测
get_board_info() 通过 os.uname()[-1] 判断当前板卡:
- 立创·庐山派K230-CanMV开发板使用 GPIO20,低电平点亮绿色 LED。
- 立创·庐山派Lite-K230D-CanMV开发板使用 GPIO66,高电平点亮绿色 LED。
引脚号和有效电平都存在差异,因此不能只替换 GPIO。完整例程把这两个参数放在同一个板卡配置字典中,后续业务逻辑无需反复判断板型。
8.2 为什么回调只设置标志
def timer_callback(_timer):
global timer_event_pending
timer_event_pending = True2
3
这段回调不打印、不休眠、不访问 LED,也不创建临时容器。这样可以缩短回调占用时间,降低中断上下文执行复杂操作带来的异常风险。
主循环每 10 ms 检查一次 timer_event_pending。检测到事件后先清除标志,再执行打印、状态切换和 LED 控制。
8.3 单次与周期状态切换
程序开始时只配置单次模式:
tim.init(
period=ONE_SHOT_DELAY_MS,
mode=Timer.ONE_SHOT,
callback=timer_callback,
)2
3
4
5
主循环确认单次事件已到达后,才复用 tim 切换到周期模式。这样不会出现第二次 init() 提前覆盖第一次配置的问题。
stage 保存当前阶段:
one_shot:下一次事件是单次事件,处理后启动周期模式。periodic:后续事件用于翻转绿色 LED 并累计次数。
8.4 LED 有效电平适配
主循环不直接写死高低电平,而是根据板卡配置选择:
led_level = (
BOARD["LED_ON_LEVEL"]
if led_is_on
else BOARD["LED_OFF_LEVEL"]
)
green_led.value(led_level)2
3
4
5
6
因此相同业务代码可以同时适配低电平点亮的标准版和高电平点亮的 Lite-K230D。
8.5 退出与资源回收
os.exitpoint()让 CanMV 开发环境能够停止主循环。time.sleep_ms()避免事件尚未到达时持续空转。try/except/finally同时覆盖正常结束、用户停止和运行异常。finally先调用tim.deinit(),再把 LED 恢复为熄灭状态。- Timer 和 LED 的清理分别捕获异常,避免其中一项失败后跳过另一项清理。
8.6 常改参数
| 参数 | 作用 | 建议 |
|---|---|---|
ONE_SHOT_DELAY_MS | 单次事件等待时间 | 保持不小于 5 ms;演示建议 500~2000 ms |
PERIODIC_INTERVAL_MS | 周期事件间隔 | 根据任务耗时留出足够处理余量 |
PERIODIC_TOGGLE_LIMIT | LED 翻转次数 | 使用正整数;偶数便于正常结束前回到熄灭状态 |
MAIN_LOOP_INTERVAL_MS | 主循环轮询间隔 | 应明显小于周期,但不要设为 0 造成空转 |
九 实际运行效果
以立创·庐山派Lite-K230D-CanMV开发板为例,终端输出形式如下;标准版只会显示不同的板卡名称和绿色 LED GPIO:
当前板卡:立创·庐山派Lite-K230D-CanMV开发板
绿色 LED:GPIO66
单次定时器已启动:1000 ms
单次事件已触发
周期定时器已启动:每 500 ms 触发一次
周期事件 1/10:绿色 LED 点亮
周期事件 2/10:绿色 LED 熄灭
周期事件 3/10:绿色 LED 点亮
周期事件 4/10:绿色 LED 熄灭
周期事件 5/10:绿色 LED 点亮
周期事件 6/10:绿色 LED 熄灭
周期事件 7/10:绿色 LED 点亮
周期事件 8/10:绿色 LED 熄灭
周期事件 9/10:绿色 LED 点亮
周期事件 10/10:绿色 LED 熄灭
周期演示完成,准备释放 Timer
Timer 已释放
程序结束,绿色 LED 已关闭2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
完整流程约持续 6 秒:前 1 秒等待单次事件,随后每 500 ms 翻转一次 LED,共执行 10 次。
十 常见问题
10.1 提示 invalid period or freq?
原因:当前 CanMV v1.8 源码要求换算后的周期不小于 5 ms。还可能是 freq=0,或错误地给整数参数传入浮点数。
解决方法:
- 优先使用整数
period,并确保period >= 5。 - 使用
freq时传入正整数,并确认1000 // freq >= 5。 - 低于
1 Hz时改用period,例如 2 秒使用period=2000。
10.2 单次模式为什么没有触发?
现象:预期先执行单次回调,但只看到周期事件。
原因:最常见的问题是在单次 init() 后立即再次调用 init() 配置周期模式,导致第一次配置被覆盖。
解决方法:等待单次回调置位事件标志,由主循环确认后再切换模式。不要通过紧邻的两次 init() 表达先后顺序。
10.3 在回调中打印后运行不稳定?
原因:当前 CanMV Timer 回调按硬中断回调处理。print()、字符串格式化、动态对象创建和阻塞操作都不适合在该上下文执行。
解决方法:回调中只设置简单标志;打印、LED、传感器和通信操作移到主循环。调试回调频率时,可以让主循环统计事件,再以较低频率输出统计结果。
10.4 终端输出正常,但绿色 LED 不亮?
- 查看终端打印的板卡名称和 GPIO。
- 标准版应为 GPIO20、低电平点亮;Lite-K230D 应为 GPIO66、高电平点亮。
- 确认
get_board_info()、LED_ON_LEVEL和LED_OFF_LEVEL没有被删改。 - 确认 TF 卡固件与实际板卡型号匹配。
10.5 两个 Timer(-1) 为什么互相影响?
当前 CanMV v1.8 实现会复用同一个软件定时器对象。再次创建或初始化 Timer(-1),可能得到同一资源并覆盖之前的周期和回调。
需要多个周期任务时,可以用一个软件定时器产生基础节拍,再由主循环按计数或 ticks_ms() 分发不同任务;也可以在明确固件版本和系统资源占用后,评估使用不同编号的硬件定时器。
10.6 为什么周期时间存在抖动?
软件定时器的触发和 Python 主循环处理都会受到系统负载影响。5 ms 是当前源码允许的最小配置周期,不是 5 ms 精度或零抖动承诺。
如果任务需要稳定波形,使用 PWM;如果需要验证定时抖动,使用示波器或逻辑分析仪测量最小化的 GPIO 测试代码,不要用终端打印时间反推微秒或毫秒精度。
10.7 掉电后 Timer 会继续计时吗?
不会。开发板掉电后,Timer 和脚本都会停止;重新上电只会重新开始运行程序,不会补算掉电期间的定时事件。
如果需求是按日期或每天固定时刻执行,应使用 RTC 或联网时间。但要特别注意:两款庐山派在硬件设计时均未预留 RTC 后备供电,一旦开发板供电丢失,RTC 就会停止走时;重新上电后 RTC 时间会重置。因此必须在每次上电后重新校时,或外接带后备电池的 RTC。
10.8 运行完整例程后没有任何反应?
现象:运行本节完整例程后,没有看到预期的终端输出、Timer 事件或绿色 LED 变化。
原因:部分旧版 CanMV 固件的 Timer 功能可能存在 BUG;也可能是固件镜像与实际板卡型号不匹配。
解决方法:
- 确认使用的是立创·庐山派K230-CanMV开发板,还是立创·庐山派Lite-K230D-CanMV开发板。
- 备份 TF 卡中的脚本和数据,按照快速入门重新检查固件升级流程。
- 从CanMV K230 Daily Build 官方目录下载与板卡型号匹配的最新 Daily Build 固件并完成升级。两款开发板的固件镜像不能混用。
- 升级后重新上电、连接开发环境,并在不修改参数的情况下再次运行本节完整例程。
- 如果问题仍然存在,请记录终端报错、固件版本和程序打印的板卡名称,再结合 10.4 节检查板载绿色 LED 的 GPIO 与有效电平。
十一 总结
本节完成了 Timer 单次模式、周期模式和双板绿色 LED 演示。核心要点如下:
- 两款庐山派使用相同的
machine.TimerAPI,本教程统一选择Timer(-1)软件定时器。 - 当前 CanMV v1.8 的
freq和period按整数处理,换算后的周期必须不小于5 ms。 freq的优先级高于period,工程代码优先使用含义清晰的period。- 每次
init()都会重配置 Timer,单次事件发生前不能立即切换到周期模式。 - Timer 回调只设置事件标志,LED、打印和业务处理放在主循环。
get_board_info()同时适配两板的 LED 引脚与有效电平。deinit()必须放入finally,确保正常结束和异常退出都能释放资源。- Timer 不保存日历时间;两款庐山派又都没有 RTC 后备供电,掉电后需要重新启动定时任务并重新校准 RTC。