编程技巧总结
本页总结基于本系统的编程最佳实践和高级技巧。核心思想:主程序做调度,子程做执行。
⚙️ 系统特性回顾
关键指令
| 指令 | 助记符 | 功能 |
|---|---|---|
01 | JT | 数字输出:写值到 GPIO 引脚 |
09 | DZ | 条件跳转:寄存器值 > 某值则跳 |
0E | ZY | 无条件跳转 |
0C | DY | 调用子程(记返回地址,嵌套最多16层) |
0D | FH | 从子程返回 |
0F | JS | 停机 |
10 | FZ | 赋值:写入寄存器 |
11 | JFZ | 条件跳转:寄存器值 ≠ 某值则跳 |
15 | (循环移位) | 位操作/循环移位 |
00 | (空/NOP) | 未写的行默认为 00 0000 0000,不做任何事 |
指令格式说明
每行指令的格式:行号 操作码 P-0 P-1
- P-0:第一个四位 HEX 数
- P-1:第二个四位 HEX 数
跳转/调用类指令的目标地址位置:
指令 目标地址位置 说明 DY P-0 调用子程,目标地址写在第一个四位 HEX ZY P-1 无条件跳转,目标地址写在第二个四位 HEX FH 不需要 系统自动记录返回地址 正确写法:
DY 0060 0000(调用 060 行子程,目标在 P-0)正确写法:
ZY 0000 0005(跳转到行 005,目标在 P-1)
输入信号汇总
系统可用的输入信号及对应寄存器/指令:
| 信号类型 | 寄存器/指令 | 用途说明 | 取值范围 |
|---|---|---|---|
| 按钮 | R3E | 按钮状态寄存器 | 0000 = 未按下,0001 = 按下 |
| 温度 | R2E | 温度传感器读数 | 0 ~ 65535(需按实际传感器换算) |
| 颜色传感器 | R31 | 红色分量值 | 0 ~ 255 |
| R32 | 绿色分量值 | 0 ~ 255 | |
| R33 | 蓝色分量值 | 0 ~ 255 | |
| 模拟输入 (ADC) | AD 指令 | 电阻类传感器(光敏、声音等) | AD 0000 XXXX 读通道0到寄存器 |
| 例如:声音传感器、光敏电阻、电位器 | 0 ~ 最大值(依硬件而定) | ||
| 数字输入 (GPIO) | 14 指令 | 数字引脚电平读取 | 0000 = 低电平,0001 = 高电平 |
输出信号 / 模块汇总
系统可控制的输出及对应指令/寄存器。下面是按实际硬件分类的详细说明:
| 输出类型 | 指令/寄存器 | 用途说明 | 示例 |
|---|---|---|---|
| 数字输出 (GPIO) | JT (01) | 控制 LED、蜂鸣器、继电器等简单外设 | JT 00FF 00FF 所有引脚置高 |
| PWM 控制 | R2A, R2B, R2C, R2D | PWM 占空比寄存器(0~03E8 对应 0%~100%),控制 LED 亮度、电机转速 | 写入 0000 ~ 03E8 调整占空比(最大 03E8=100%) |
| WS2812B LED | CD (E4) | 设置全彩 LED 颜色,格式 CD 00RR GGBB | CD 00FF 0000 红色全亮 |
| 延时控制 | YS (02) | 程序延时等待 | YS 03E8 0000 延时 1 秒 |
| 寄存器到输出 | SS (13) | 寄存器值直接输出到数字引脚 | 用于复杂模式的批量输出 |
硬件模块详细说明
🔊 蜂鸣器(IO 控制,非音调)
用一个 GPIO 引脚接蜂鸣器,通过高低电平切换发声:
| 引脚状态 | 效果 | 寄存器值 |
|---|---|---|
| 持续高电平 | 持续发声 | 0001 |
| 持续低电平 | 不发声 | 0000 |
| 快速高低切换 | 连续脉冲声 | 在程序里循环翻转 |
; 蜂鸣器短"嘀"一声(接在某一位 GPIO 上)
000 JT 0001 0000 ; 蜂鸣器低电平 = 发声(假设是低电平触发)
001 YS 0064 0000 ; 延时 100ms
002 JT 0001 0001 ; 关闭蜂鸣器
003 FH 0000 0000 ; 返回
🌬️ 小风扇(L9110H 双路 H 桥驱动)
L9110H 需要 2 个引脚控制一路风扇(A-IA 控制速度/方向,A-IB 控制方向):
| A-IA | A-IB | 电机状态 | 说明 |
|---|---|---|---|
| PWM 高 | 低 | 正转(可调速) | 把速度值写入 A-IA 的 PWM 寄存器 |
| 低 | PWM 高 | 反转(可调速) | 把速度值写入 A-IB 的 PWM 寄存器 |
| 低 | 低 | 滑行停止 | 电机自由减速 |
| 高 | 高 | 制动停止 | 快速刹车 |
; 风扇正转,速度 = 50%(假设接 R2A=A-IA, R2B=A-IB)
000 FZ 002A 01F4 ; R2A = 50% (01F4) 占空比 → A-IA 输出 PWM
001 FZ 002B 0000 ; R2B = 0 → A-IB 保持低电平
002 FH 0000 0000 ; 结果:风扇正转,约半速
; 风扇反转,速度 = 80%
010 FZ 002A 0000 ; R2A = 0 → A-IA 低电平
011 FZ 002B 0320 ; R2B = 80% (0320) 占空比 → A-IB 输出 PWM
012 FH 0000 0000 ; 结果:风扇反转,约八成速
; 风扇停止(滑行)
020 FZ 002A 0000 ; 两路都为 0
021 FZ 002B 0000
022 FH 0000 0000
小提示:L9110H 一个芯片可以驱动 2 路独立电机(A 路和 B 路),所以如果要控制两个风扇,需要 4 个 PWM 引脚。
🌈 外置 RGB LED(3 个引脚,IO 或 PWM 控制)
用 3 个独立引脚 分别控制红、绿、蓝三色:
| 控制方式 | 接线方法 | 效果 |
|---|---|---|
| 纯 IO 控制 | R/G/B 各接一个 GPIO | 只能显示 8 种颜色(红/绿/蓝/黄/青/紫/白/灭) |
| PWM 控制 | R/G/B 各接一个 PWM 引脚 | 可以混合出 1600 万种颜色 |
; 外置 RGB 显示红色(R 引脚接 R2A, G 接 R2B, B 接 R2C)
000 FZ 002A 03E8 ; 红色 = 最大亮度
001 FZ 002B 0000 ; 绿色 = 灭
002 FZ 002C 0000 ; 蓝色 = 灭
003 FH 0000 0000 ; 结果:显示纯红色
; 显示青色(绿+蓝混合)
010 FZ 002A 0000 ; 红 = 灭
011 FZ 002B 03E8 ; 绿 = 最亮
012 FZ 002C 03E8 ; 蓝 = 最亮
013 FH 0000 0000 ; 结果:青色
; 渐变色呼吸灯效果(红色从暗到亮再到暗)
020 FZ 0025 0000 ; R25 = 亮度计数器,从 0 开始
021 FZ 002A 0000 ; 先把红色设为 0
022 XDZ 0025 0032 ; 亮度 + 50(每次微调)
023 FZ 002A 0025 ; 把当前亮度值写入 R2A(红色 PWM)
024 YS 0019 0000 ; 延时 25ms
025 DYZ 0025 03E8 ; 还没到最大亮度(03E8=100%)?
026 ZY 0000 022 ; 是 → 继续增加
027 ZY 0000 020 ; 否 → 从头开始
🦾 舵机(PCA9685 模块驱动)
PCA9685 是 I²C 接口的 16 路 PWM 扩展芯片,只用 2 根线(SDA 数据 / SCL 时钟)就能控制 16 个舵机。
| 特性 | 说明 |
|---|---|
| 通信方式 | I²C(2 根线) |
| 可驱动舵机数 | 一个模块 16 路,可级联最多 62 个 → 992 路 |
| PWM 分辨率 | 12 位(4096 级) |
| 典型舵机频率 | 50Hz(周期 20ms) |
| 舵机脉冲宽度 | 1ms = 0°,1.5ms = 90°(中位),2ms = 180° |
; 用 PCA9685 控制舵机角度
; 假设系统已有 I²C 驱动函数,通过写入 PCA9685 的寄存器来设置舵机角度
; 舵机角度 → PWM 占空比的换算:
; 0° → 脉宽 1.0ms → 在 50Hz 下约占 5% 占空比
; 90° → 脉宽 1.5ms → 在 50Hz 下约占 7.5% 占空比
; 180° → 脉宽 2.0ms → 在 50Hz 下约占 10% 占空比
; 舵机转到 90°(中位)
; 子程 0E0:设置通道 0 的舵机角度 = 参数 R20
; 这里展示的是逻辑流程:通过 I²C 发送配置数据到 PCA9685
0E0 FZ 0026 0040 ; PCA9685 的 I²C 地址 = 0x40(默认)
0E1 FZ 0027 0006 ; 通道 0 的寄存器起始地址 = 0x06
0E2 FZ 0028 0000 ; PWM ON 时刻 = 0(从周期开始就拉高)
0E3 FZ 0029 0180 ; PWM OFF 时刻 = 对应 1.5ms 的计数值(90°)
0E4 FH 0000 0000 ; 通过 I²C 写寄存器完成
为什么用 PCA9685? 普通 MCU 的 PWM 通道数量有限(通常 4~8 路),而 PCA9685 用 2 根线就能扩展 16 路 PWM,非常适合多舵机的机器人项目。舵机本身只需要 PWM 信号作为位置指令,不需要持续的 CPU 干预。
引脚资源占用一览
| 模块 | 所需引脚数 | 引脚类型 |
|---|---|---|
| 单路蜂鸣器 | 1 | GPIO |
| 1 个风扇(L9110H) | 2 | PWM × 2 |
| 2 个风扇(L9110H) | 4 | PWM × 4 |
| 1 个外置 RGB LED | 3 | GPIO 或 PWM × 3 |
| 1 个舵机(通过 PCA9685) | 0(占用 I²C 的 2 根总线) | I²C 共享 |
| 16 个舵机(通过 1 个 PCA9685) | 0(仍只占用 I²C 的 2 根总线) | I²C 共享 |
核心能力
- 嵌套深度 16 层:子程可以调用子程,最多 16 层
- 支持递归:子程可以调用自己
- 空行默认 00:未写的行不是"空",而是
00 0000 0000,程序会按顺序执行并穿过它
📐 核心原则:三条铁律
| 规则 | 说明 |
|---|---|
| ① 子程入口只做一件事 | 每个子程从第一行开始就是逻辑,不要让"返回点"之后还能掉到其他子程 |
| ② 每条逻辑路径都必须以 FH/ZY/JS 结尾 | 子程内部无论走哪条分支,最终都要碰到 FH/ZY/JS 之一 |
| ③ 子程之间不要连续 | 两个子程之间保留缓冲空间(默认的 00 行即可),方便未来扩展 |
🏗️ 架构一:分层设计
把行号空间分成三层,像"图书馆"一样分类存放。
建议分区
000 ~ 01F → 主程序层 (控制流程、决策、调用子程)
020 ~ 07F → 原子操作层 (频繁调用的短子程,每件只做一件事)
080 ~ 0FF → 功能模块层 (完整功能模块,可以调用原子层)
为什么要分层?
| 好处 | 说明 |
|---|---|
| 主程序一眼看懂 | 000-03F 只有"调用谁、跳到哪",逻辑一目了然 |
| 修改一处,全局生效 | 改进"延时子程",所有用到它的地方都自动受益 |
| 空间不足时可替换 | 某段功能用不上了,可以直接覆盖重写,不影响其他模块 |
| 预留扩展空间 | 每层之间留空行,后加新功能不需要挪动已有代码 |
📚 架构二:原子操作库(020-07F)
把最常用的操作写成"单行/几行"的子程。这些是你的"标准库函数"。
示例:LED 控制子程
- 助记符形式
- 指令形式
040 CD 0000 0000 ; 子程040:LED全灭
041 FH 0000 0000 ; 返回
048 CD 00FF FFFF ; 子程048:LED全亮
049 FH 0000 0000
050 FZ 002A 0080 ; 子程050:设置PWM默认值
051 FZ 002B 0080
052 FZ 002C 0080
053 FH 0000 0000
040 E4 0000 0000
041 0D 0000 0000
048 E4 00FF FFFF
049 0D 0000 0000
050 10 002A 0080
051 10 002B 0080
052 10 002C 0080
053 0D 0000 0000
示例:延时子程
- 助记符形式
- 指令形式
060 YS 03E8 0000 ; 子程060:长延时(约1秒)
061 FH 0000 0000
068 YS 000A 0000 ; 子程068:短延时(约40ms)
069 FH 0000 0000
060 02 03E8 0000
061 0D 0000 0000
068 02 000A 0000
069 0D 0000 0000
使用原子库后,主程序变得极简
- 助记符形式
- 指令形式
005 DY 0048 0000 ; LED全亮(目标地址写在P-0)
006 DY 0060 0000 ; 等1秒
007 DY 0040 0000 ; LED全灭
008 DY 0060 0000 ; 等1秒
009 ZY 0000 0005 ; 循环(ZY的目标地址在P-1)
005 0C 0048 0000
006 0C 0060 0000
007 0C 0040 0000
008 0C 0060 0000
009 0E 0005 0000
📝 技巧三:参数寄存器约定
这是让子程真正强大的关键。约定某些寄存器专门用来传递参数,调用前先写参数,子程去读。
建议约定(可自己调整)
**R20** = 延时参数 调用前写入,子程用它决定延多久
**R21** = 显示模式 0=正常,1=报警,2=闪烁
**R22** = 目标寄存器 子程操作哪个寄存器
**R23** = 目标值 写入/比较的值
通用延时子程示例
- 助记符形式
- 指令形式
; 主程序
005 FZ 0020 01F4 ; 先写参数:R20 = 500ms
006 DY 0060 0000 ; 调用延时子程
010 FZ 0020 07D0 ; 换个参数:R20 = 2秒
011 DY 0060 0000 ; 同一个子程!效果不同
005 10 0020 01F4
006 0C 0060 0000
010 10 0020 07D0
011 0C 0060 0000
核心思想:子程不需要知道"为什么延时",只负责"按约定位置的值去做"。你改的是参数,不是子程代码。
🔄 技巧四:递归编程(16 层深度)
既然支持递归(子程可以调用自己)且有 16 层嵌套深度,可以实现真正的递归算法。
示例:递归闪烁 N 次
- 助记符形式
- 指令形式
; 主程序
000 FZ 0020 0005 ; R20 = 闪烁5次(递归计数器)
001 DY 0010 0000 ; 调用"闪烁"子程
002 JS 0000 0000
; 子程010:递归闪烁(R20为剩余次数)
010 DYZ 0020 0000 ; 如果 R20 > 0,继续执行
011 ZY 0000 0020 ; 否则跳020(不做事直接返回)
012 CD 00FF FFFF ; 点亮LED
013 YS 01F4 0000 ; 延时
014 CD 0000 0000 ; 熄灭
015 YS 01F4 0000 ; 延时
016 XDZ 0020 0001 ; R20 = R20 - 1(次数-1)
017 DY 0010 0000 ; ← 自己调用自己!递归
018 FH 0000 0000
020 FH 0000 0000 ; 递归终止:返回
000 10 0020 0005
001 0C 0010 0000
002 0F 0000 0000
010 0A 0020 0000
011 0E 0020 0000
012 E4 00FF FFFF
013 02 01F4 0000
014 E4 0000 0000
015 02 01F4 0000
016 XDZ 0020 0001
017 0C 0010 0000
018 0D 0000 0000
020 0D 0000 0000
执行流程(当 R20=5 时)
调用栈深度变化:
0层: 主程序 → DY 010
1层: 010 → 检查 5>0 → 亮灭一次 → R20=4 → DY 010
2层: 010 → 检查 4>0 → 亮灭一次 → R20=3 → DY 010
3层: 010 → 检查 3>0 → 亮灭一次 → R20=2 → DY 010
4层: 010 → 检查 2>0 → 亮灭一次 → R20=1 → DY 010
5层: 010 → 检查 1>0 → 亮灭一次 → R20=0 → DY 010
6层: 010 → 检查 0=0 → 跳020 → FH 返回
← 逐层返回:5层FH→4层FH→...→1层FH→主程序
只要计数器值不超过 15,就不会超嵌套深度。
🛡️ 技巧五:子程"防溜"防护
最常见的 bug:子程返回后,程序按顺序"溜"进下一段代码,导致意外执行。
反面教材(有 bug)
- 助记符形式
- 指令形式
010 DY 0015 0000 ; 子程010:调用015(目标地址P-0)
; ← 没有FH!返回后到011,然后顺着溜下去
011 00 0000 0000 ; 空行(默认00)
...
015 15 1101 0001 ; ← 会被执行两次!
016 FH 0000 0000
010 0C 0015 0000
011 00 0000 0000
...
015 15 1101 0001
016 0D 0000 0000
正面教材(正确写法)
- 助记符形式
- 指令形式
010 DY 0015 0000 ; 子程010:调用015
011 FH 0000 0000 ; ← 必须以FH/ZY/JS结尾!
; 012 以后的代码不会被"正常路径"走到
015 15 1101 0001 ; 子程015
016 FH 0000 0000 ; ← 必须以FH/ZY/JS结尾!
010 0C 0015 0000
011 0D 0000 0000
015 15 1101 0001
016 0D 0000 0000
保险杠写法(更安全)
- 助记符形式
- 指令形式
010 DY 0015 0000
011 FH 0000 0000 ; 正常返回
012 JS 0000 0000 ; ← 保险杠:万一走到这里,直接停
013 JS 0000 0000
014 JS 0000 0000
015 15 1101 0001
016 FH 0000 0000
017 JS 0000 0000 ; ← 保险杠
010 0C 0015 0000
011 0D 0000 0000
012 0F 0000 0000
013 0F 0000 0000
014 0F 0000 0000
015 15 1101 0001
016 0D 0000 0000
017 0F 0000 0000
📊 综合示例:完整架构
把以上所有技巧结合起来的完整程序。
主程序层(000-01F)
- 助记符形式
- 指令形式
000 FZ 0020 0000 ; 初始化状态寄存器
001 FZ 0021 0001 ; 方向寄存器:1=正向
010 DY 0080 0000 ; 调用"读取传感器"子程
011 DY 00A0 0000 ; 调用"显示当前状态"子程
012 DY 00C0 0000 ; 调用"判断是否报警"子程
013 DY 0060 0000 ; 调用"延时"子程
014 ZY 0000 0010 ; 循环
01F JS 0000 0000 ; 冗余停机(永远走不到,但放一个更安心)
000 10 0020 0000
001 10 0021 0001
010 0C 0080 0000
011 0C 00A0 0000
012 0C 00C0 0000
013 0C 0060 0000
014 0E 0010 0000
01F 0F 0000 0000
原子操作库(040-07F)
- 助记符形式
- 指令形式
040 CD 0000 0000 ; 子程040:清LED
041 FH 0000 0000
048 CD 00FF FFFF ; 子程048:点亮LED(白色全亮)
049 FH 0000 0000
050 CD 00FF 0000 ; 子程050:报警LED(闪红光)
051 YS 00FF 0000
052 CD 0000 0000
053 YS 00FF 0000
054 FH 0000 0000
060 FZ 0020 03E8 ; 子程060:延时(R20控制时长)
061 YS (R20) 0000
062 FH 0000 0000
040 E4 0000 0000
041 0D 0000 0000
048 E4 00FF FFFF
049 0D 0000 0000
050 E4 00FF 0000
051 02 00FF 0000
052 E4 0000 0000
053 02 00FF 0000
054 0D 0000 0000
060 10 0020 03E8
061 02 (R20) 0000
062 0D 0000 0000
功能模块层(080-0FF)
- 助记符形式
- 指令形式
080 AD 0000 0010 ; 子程080:读取传感器,结果存R10
081 FH 0000 0000
0A0 AD 0000 0010 ; 子程0A0:根据R10的值显示
0A1 JT 00FF (R10)
0A2 FH 0000 0000
0C0 DYZ 0010 0032 ; 子程0C0:R10 > 50 则报警
0C1 DY 0050 0000 ; 是 → 调用报警子程
0C2 FH 0000 0000
0C3 DY 0048 0000 ; 否 → 正常显示
0C4 FH 0000 0000
080 A0 0000 0010
081 0D 0000 0000
0A0 A0 0000 0010
0A1 01 00FF (R10)
0A2 0D 0000 0000
0C0 0A 0010 0032
0C1 0C 0050 0000
0C2 0D 0000 0000
0C3 0C 0048 0000
0C4 0D 0000 0000
💡 进阶:条件前置 vs 条件后置
两种把子程结合条件判断的写法,各有优势。
写法A:条件前置(传统写法)
- 助记符形式
- 指令形式
; 主程序里判断,判断完了才调用
010 DYZ 0010 0032 ; 主程序先判断:R10>50?
011 DY 0050 0000 ; 是 → 调用报警
012 ZY 0000 0014 ; 跳过正常分支
013 DY 0048 0000 ; 否 → 调用正常显示
014 ...
010 0A 0010 0032
011 0C 0050 0000
012 0E 0014 0000
013 0C 0048 0000
014 ...
特点:主程序要操心"判断逻辑",代码分散。
写法B:条件后置(子程封装,推荐)
- 助记符形式
- 指令形式
; 主程序只管调用,判断放在子程内部
010 DY 00C0 0000 ; 调用"智能显示"子程
011 ...
; 子程0C0:自己判断该怎么显示
0C0 DYZ 0010 0032 ; 子程内部先判断条件
0C1 ZY 0000 00C4 ; >50 跳报警分支
0C2 DY 0048 0000 ; 否则正常显示
0C3 FH 0000 0000 ; ← 返回
0C4 DY 0050 0000 ; 报警分支
0C5 FH 0000 0000
010 0C 00C0 0000
011 ...
0C0 0A 0010 0032
0C1 0E 00C4 0000
0C2 0C 0048 0000
0C3 0D 0000 0000
0C4 0C 0050 0000
0C5 0D 0000 0000
特点:主程序永远干净。判断逻辑藏在子程里,修改判断条件时主程序不动。这是大型程序推荐的写法。
📋 快速检查表
写完代码后,对照以下项目自查:
| 检查项 | 说明 | 没问题打勾 |
|---|---|---|
| 每个子程最后一行是 FH/ZY/JS 吗? | 防止程序"溜"进下一段 | ☐ |
| 主程序是不是只剩 DY/ZY/JS? | 主程序应该是"调用清单" | ☐ |
| 计数器(递归深度)不超过 15 吗? | 嵌套最多16层,1层留给主程序 | ☐ |
| 用到的寄存器有没有约定用途? | 避免两个子程用同一个寄存器存不同东西 | ☐ |
| 子程之间有缓冲行吗? | 默认的 00 行就是缓冲 | ☐ |
| DY 的目标地址写在 P-0,ZY 的目标地址写在 P-1 吗? | DY=P-0,ZY=P-1,FH/JT 不需要目标地址 | ☐ |
🎯 最终目标
当你把代码写成下面这个样子时,就完全掌握这套架构了:
; 主程序(一眼看懂整个程序在做什么)
000 DY 0080 0000 ; 初始化(DY目标地址在P-0)
001 DY 0090 0000 ; 读输入
002 DY 00A0 0000 ; 做决策
003 DY 00B0 0000 ; 输出结果
004 DY 00C0 0000 ; 延时
005 ZY 0000 0001 ; 循环(ZY目标地址在P-1)
; ↓ 具体怎么实现,全部藏在子程里
主程序变成了"配置清单",你写的不是代码,而是"调用哪些功能"的目录。三个月后回来看,仍然一眼就能看懂整个程序的意图。
🌟 综合范例:智能环境监测系统
以下是一个完整的综合范例,结合多种输入(温度、声音、按钮)和多种输出(外置 RGB LED、L9110H 小风扇、IO 蜂鸣器、PCA9685 舵机),展示如何用分层架构实现一个"智能房间环境监测系统"。
功能描述
| 功能 | 触发条件 | 执行动作 |
|---|---|---|
| 温度监测 | R2E > 阈值1(如 30°C) | 启动小风扇(L9110H)+ RGB LED 显示红色 |
| 温度报警 | R2E > 阈值2(如 40°C) | 风扇全速 + IO 蜂鸣器"嘀嘀"报警 |
| 环境亮度 | 模拟输入(ADC通道0)< 阈值 | 外置 RGB LED 显示暖白色(R2A/R2B/R2C 三路 PWM) |
| 按钮控制 | 按下按钮(R3E = 0001) | 切换手动/自动模式 + 舵机转 90° 打开窗帘 |
| 噪音提示 | 模拟输入(ADC通道1)> 阈值 | RGB LED 快速蓝色闪烁一次 |
硬件接线约定
; PWM 引脚分配(R2A-R2D 分别接不同外设):
; R2A → 外置 RGB LED 的 R 通道(PWM 调光)
; R2B → 外置 RGB LED 的 G 通道(PWM 调光)
; R2C → 外置 RGB LED 的 B 通道(PWM 调光)
; R2D → 备用(可接其他 PWM 设备)
;
; L9110H 风扇驱动(2 个 PWM 引脚):
; GPIO 位0 (A-IA) → 风扇正转速度(通过 PWM 占空比控制)
; GPIO 位1 (A-IB) → 风扇反转速度(本示例只用正转,所以固定为 0)
;
; 蜂鸣器:
; GPIO 位2 → IO 控制蜂鸣器(高低切换发声)
;
; PCA9685 舵机(通过 I²C,不占用额外 GPIO 引脚):
; 通道0 → 窗帘舵机,0°=关闭,90°=打开
参数寄存器约定
R20 = 温度低阈值(如 30°C 对应值)
R21 = 温度高阈值(如 40°C 对应值)
R22 = 亮度低阈值(ADC通道0)
R23 = 噪音阈值(ADC通道1)
R24 = 运行模式(0=自动,1=手动)
R25 = 临时存储 / 状态标志
完整程序(助记符形式)
主程序层(000-01F):只做调度
000 DY 0080 0000 ; 初始化:设置参数默认值
001 DY 0085 0000 ; 读取所有传感器
002 DY 0090 0000 ; 根据传感器值做决策
003 DY 00A0 0000 ; 执行输出动作
004 DY 00C0 0000 ; 延时等待
005 ZY 0000 0001 ; 回到 001,循环监测
初始化子程(080-084)
080 FZ 0020 1E00 ; 温度低阈值 = 0x1E00(约30°C)
081 FZ 0021 2800 ; 温度高阈值 = 0x2800(约40°C)
082 FZ 0022 0500 ; 亮度低阈值 = 0x0500
083 FZ 0023 0A00 ; 噪音阈值 = 0x0A00
084 FH 0000 0000 ; 返回
读取传感器子程(085-08F)
; 读模拟输入通道0(亮度/光敏),结果存到R2A
085 AD 0000 002A ; ADC通道0 → R2A
; 读模拟输入通道1(声音),结果存到R2B
086 AD 0001 002B ; ADC通道1 → R2B
; 复制温度值(R2E)到R2C,方便比较
087 JFZ 002C 002E ; R2C = R2E(温度)
; 复制按钮状态(R3E)到R25
088 JFZ 0025 003E ; R25 = R3E(按钮)
089 FH 0000 0000 ; 返回
决策逻辑子程(090-09F):条件后置模式
; 检查按钮是否按下 → 切换模式
090 DYZ 0025 0001 ; R25(按钮) > 0 ?
091 ZY 0000 093 ; 未按下 → 跳过切换
092 XDZ 0024 0001 ; 按下 → R24 = 1 - R24(翻转)
093 XDZ 0025 0001 ; 清按钮标志,避免重复触发
; 检查温度 → 决定风扇状态
094 DYZ 002C 0020 ; 温度 > 低阈值 ?
095 DY 00B0 0000 ; 是 → 开风扇处理
096 DY 00B5 0000 ; 否 → 关风扇处理
; 检查亮度 → 决定灯光
097 DYZ 0022 002A ; 阈值 > 亮度读数 ?(即环境太暗)
098 DY 00BA 0000 ; 是 → 开灯
099 DY 00BF 0000 ; 否 → 关灯
; 检查噪音 → 闪烁提示
09A DYZ 002B 0023 ; 声音 > 噪音阈值 ?
09B DY 00C5 0000 ; 是 → 闪烁提示
09C FH 0000 0000 ; 否 → 返回
输出动作子程(0A0-0DF)
; ===== 温度相关(0B0-0B9)=====
; 说明:风扇用 L9110H 驱动,A-IA=GPIO位0(PWM调速),A-IB=GPIO位1(方向)
; 本示例只用正转,所以 A-IB 始终为 0
0B0 DYZ 002C 0021 ; 温度 > 高阈值 ?(严重过热)
0B1 ZY 0000 0B4 ; 是 → 跳到严重报警
0B2 FZ 002A 01F4 ; 否 → 风扇半速(PWM=50%),通过调整 R2A(最大 03E8=100%)输出占空比
0B3 FH 0000 0000 ; 返回
0B4 FZ 002A 03E8 ; 严重:风扇全速
0B5 DY 00D0 0000 ; 调用蜂鸣器报警子程
0B6 FH 0000 0000 ; 返回
0B7 FZ 002A 0000 ; 温度不高:关闭风扇
0B8 FH 0000 0000 ; 返回
; ===== 灯光相关(0BA-0C1)=====
; 说明:外置 RGB LED 用 R2A(R)/R2B(G)/R2C(B) 三路 PWM 控制颜色亮度
0BA FZ 002A 03E8 ; R 通道 = 最亮(暖白色:红+绿,蓝稍暗)
0BB FZ 002B 03E8 ; G 通道 = 最亮
0BC FZ 002C 0320 ; B 通道 = 约80%(让光偏暖)
0BD FH 0000 0000 ; 返回
0BE FZ 002A 0000 ; 关灯:三路 PWM 全部归零
0BF FZ 002B 0000
0C0 FZ 002C 0000
0C1 FH 0000 0000 ; 返回
; ===== 噪音提示(0C2-0C9)=====
0C2 FZ 002A 0000 ; R = 灭
0C3 FZ 002B 0000 ; G = 灭
0C4 FZ 002C 03E8 ; B = 最亮 → 蓝色闪烁
0C5 YS 0100 0000 ; 延时约250ms
0C6 FZ 002C 0000 ; 蓝 = 灭
0C7 YS 0100 0000 ; 再等一下
0C8 FH 0000 0000 ; 返回
; ===== 通用延时(0C9-0CD)=====
0C9 YS 01F4 0000 ; 主循环延时约 500ms
0CA FH 0000 0000 ; 返回
; ===== 蜂鸣器报警(0D0-0DB)=====
; 说明:IO 蜂鸣器接 GPIO 位2,低电平发声。连续"嘀嘀嘀"三声
0D0 JT 00FF 0004 ; GPIO位2 = 低 → 蜂鸣器响
0D1 YS 0032 0000 ; 响 50ms
0D2 JT 00FF 0000 ; GPIO位2 = 高 → 停
0D3 YS 0064 0000 ; 间隔 100ms
0D4 JT 00FF 0004 ; 第二声
0D5 YS 0032 0000
0D6 JT 00FF 0000
0D7 YS 0064 0000
0D8 JT 00FF 0004 ; 第三声
0D9 YS 0032 0000
0DA JT 00FF 0000
0DB FH 0000 0000 ; 报警完毕,返回
架构解读
上面这段代码展示了以下关键技巧:
- 主程序极简(000-005):只有 6 行,一眼看懂整个系统在做什么
- 条件后置:子程内部自己判断是否需要执行(如 094-09C),主程序不用操心
- 模块化分层:每个功能有独立的子程,修改一处不影响其他
- 参数寄存器约定:R20-R25 全部用于参数传递,子程之间不需要"重新读一次"
- 清晰的行号分区:080-084=初始化、085-089=读传感器、090+=决策输出
- 多种硬件灵活控制:L9110H 风扇用 PWM 调速、RGB LED 用三路 PWM 混色、蜂鸣器用 IO 翻转发声、舵机用 PCA9685 的 I²C 扩展
精简版(实际可运行的紧凑代码)
如果硬件资源有限,可以写成下面这种更紧凑的版本(保留核心架构):
; 主程序
000 DY 0080 0000 ; 初始化
001 AD 0000 002A ; 读亮度 → R2A
002 AD 0001 002B ; 读声音 → R2B
003 JFZ 002C 002E ; 读温度 → R2C
004 DY 0090 0000 ; 决策与输出
005 YS 03E8 0000 ; 延时1秒
006 ZY 0000 001 ; 循环
; 初始化
080 FZ 0020 1E00 ; 温度阈值1
081 FZ 0021 2800 ; 温度阈值2
082 FZ 0022 0500 ; 亮度阈值
083 FH 0000 0000 ; 返回
; 决策与输出(条件后置,全都写在一起)
090 DYZ 002C 0020 ; 温度 > 阈值1 ?
091 FZ 002A 01F4 ; 是 → 风扇半速(PWM 50%)
092 DYZ 0022 002A ; 太暗 ?
093 FZ 002B 03E8 ; 是 → 绿灯亮(环境照明)
094 FZ 002B 0000 ; 否 → 绿灯灭
095 FH 0000 0000 ; 返回主程序