init: Air780EPM 官方 LuatOS 项目代码基线
- 来源: 合宙 LuatOS 官方仓库 air780epm 模块完整代码 - 路径: luatos/air780epm/module/Air780EPM/demo 含官方 demo(含 mqtt/mqtts/socket/uart 等) - 后续: 基于 demo 开发 UART<->MQTT 数据上报功能
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
--[[
|
||||
@module main
|
||||
@summary LuatOS用户应用脚本文件入口,只负责模块加载调度
|
||||
@version 1.0
|
||||
@date 2026.08.18
|
||||
@author 杨乔杉
|
||||
@usage
|
||||
本 demo 演示 WS2812 22×22 全彩点阵的多种动效,包括色块覆盖/灯珠检测、
|
||||
彩虹渐变、蛇形扫描、星点闪烁、矩形收缩、滚动文字。
|
||||
|
||||
适用产品范围:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
灯板尺寸不是 22×22 时,请修改 ws2812_config.lua 中的 LED_W / LED_H / LED_COUNT。
|
||||
|
||||
硬件接线:
|
||||
WS2812 DIN → GPIO16 (PIN97)
|
||||
5V/GND → 外接 5V 电源(亮度较高时建议 5V/10A 以上)
|
||||
|
||||
更多说明参考本目录下的 readme.md 文件。
|
||||
]]
|
||||
|
||||
-- PROJECT / VERSION 必须定义为 ASCII 字符串,供 Luatools 识别项目和远程升级匹配版本
|
||||
PROJECT = "WS2812_testdemo"
|
||||
VERSION = "001.999.000"
|
||||
|
||||
log.info("main", "project name is ", PROJECT, "version is ", VERSION)
|
||||
|
||||
-- ==================== 模块加载(require) ====================
|
||||
-- 加载顺序:ws2812_config 必须最先,因为它定义全局表 WS2812_CFG,
|
||||
-- 后续所有效果模块和命令模块都直接读取它。
|
||||
require "ws2812_config"
|
||||
|
||||
-- 效果模块:同一时间只启用一个循环渲染任务,其余保持注释,
|
||||
-- 否则多个任务同时写灯带会造成画面错乱。
|
||||
|
||||
-- 色块覆盖 / LED 灯珠检测(默认启用)
|
||||
require "ws2812_blocks_task"
|
||||
|
||||
-- 彩虹渐变
|
||||
-- require "ws2812_rainbow_task"
|
||||
|
||||
-- 蛇形扫描
|
||||
-- require "ws2812_snake_task"
|
||||
|
||||
-- 星点闪烁
|
||||
-- require "ws2812_sparkle_task"
|
||||
|
||||
-- 矩形收缩扩散
|
||||
-- require "ws2812_rect_task"
|
||||
|
||||
-- 滚动文字("欢迎使用LuatOS",依赖 ws2812_fonts)
|
||||
-- require "ws2812_scroll_task"
|
||||
|
||||
-- 串口命令接口:通过 USB 虚拟串口运行时调节亮度和速度
|
||||
-- 命令:b=NNN 亮度0~255、s=NNN 帧间隔20~2000ms、b? 查询亮度、s? 查询速度
|
||||
require "ws2812_cmd"
|
||||
|
||||
-- sys.run() 启动 LuatOS 事件循环,正常运行时永不返回,后面不要加任何代码
|
||||
sys.run()
|
||||
@@ -0,0 +1,589 @@
|
||||
# WS2812 多模块 Demo
|
||||
|
||||
> **适用产品范围**:本 demo 仅适用于合宙 **Air1780P / Air1780H / Air1780HV**。
|
||||
|
||||
> 面向 **AI 代理 / 开发者** 的自包含文档。本文档描述模块化 WS2812 demo 的目的、结构、API、扩展方式与故障排除。
|
||||
> 维护约定:新增/修改 `_task.lua` 后请同步更新「效果模块」与「项目结构」两节。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [项目概述](#1-项目概述)
|
||||
2. [技术栈](#2-技术栈)
|
||||
3. [硬件信息](#3-硬件信息)
|
||||
4. [环境配置](#4-环境配置)
|
||||
5. [项目结构解析](#5-项目结构解析)
|
||||
6. [快速开始](#6-快速开始)
|
||||
7. [效果模块详解](#7-效果模块详解)
|
||||
8. [公共 API(ws2812_config.lua)](#8-公共-apiws2812_configlua)
|
||||
9. [字模与文本条带(ws2812_fonts.lua)](#9-字模与文本条带ws2812_fontslua)
|
||||
10. [运行时配置与串口命令](#10-运行时配置与串口命令)
|
||||
11. [编写自定义效果](#11-编写自定义效果)
|
||||
12. [故障排除](#12-故障排除)
|
||||
13. [FAQ](#13-faq)
|
||||
14. [官方支持渠道](#14-官方支持渠道)
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
在合宙 **Air1780P / Air1780H** 上驱动一块 **22×22 = 484 颗 WS2812 RGB 灯珠**的全彩灯板,实现稳定、清晰的滚动文字显示和多种灯光动效。
|
||||
|
||||
灯光效果拆成**独立、可插拔的任务模块**,每个文件对应一个可独立运行的小 demo,`main.lua` 只负责 `require` 调度,方便学习、复用与扩展。
|
||||
|
||||
### 1.1 主要功能
|
||||
|
||||
- 六个开箱即用的灯光效果(默认启用色块覆盖/灯珠检测,其他按需取消注释):
|
||||
|
||||
| 模块 | 效果 | 速度系数 |
|
||||
|------|------|---------:|
|
||||
| `ws2812_blocks_task` | 色块覆盖 + 红/绿/蓝/白纯色停留,用于 LED 灯珠坏点检测 | 1× |
|
||||
| `ws2812_rainbow_task` | HSV 彩虹渐变,颜色在矩阵上流动 | 1× |
|
||||
| `ws2812_snake_task` | 带 6 像素拖尾的光点按蛇形走线 S 形跑动 | 2× |
|
||||
| `ws2812_sparkle_task` | 每帧随机 30 颗彩灯,星空闪烁 | 2× |
|
||||
| `ws2812_rect_task` | 从中心向外扩散、再向内收缩的彩色方框 | 3× |
|
||||
| `ws2812_scroll_task` | 横向滚动 **"欢迎使用LuatOS"**(中文 22×22 + 英文 22×11 半宽) | 1× |
|
||||
|
||||
- 公共硬件初始化、蛇形坐标映射、HSV 颜色工具集中在 `ws2812_config.lua`;
|
||||
- 字模和 UTF-8 条带构建集中在 `ws2812_fonts.lua`;
|
||||
- 支持 UART 串口命令实时调节亮度和速度(`b=NNN` / `s=NNN`),无需重新烧录;
|
||||
- 内置开机复位清零,消除第一颗 LED 上电鬼影。
|
||||
|
||||
### 1.2 与根目录 main.lua 的关系
|
||||
|
||||
| 对比项 | 根目录 `main.lua` | `ws2812/` |
|
||||
|--------|-------------------|----------------|
|
||||
| 定位 | 正式产品程序(色块 2 圈 + 滚动文字) | 学习/扩展用的模块化 demo 集合 |
|
||||
| 组织 | 单文件全部逻辑 | 多文件按职责拆分 |
|
||||
| 效果 | 固定两阶段 | 6 种效果可任意组合(色块覆盖 + 5 种循环效果) |
|
||||
| 字模 | 内置 | 抽到 `ws2812_fonts.lua` |
|
||||
| 风格 | 应用代码 | 模块化(`@module` 注释、`sys.taskInit`、`require` 调度) |
|
||||
|
||||
> Luatools 同一时刻只能把一个 `main.lua` 设为 MAIN。两者不互相依赖,可独立烧录。
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈
|
||||
|
||||
| 层级 | 技术 |
|
||||
|------|------|
|
||||
| 脚本语言 | Lua 5.3(支持位运算符 `<< >> & \|`) |
|
||||
| 框架 | LuatOS(Air1780P / Air1780H) |
|
||||
| LED 协议 | WS2812 单总线归零码(通过 LuatOS `ws2812` 固件组件驱动) |
|
||||
| 依赖(固件内置) | `sys`、`log`、`ws2812`、`uart`、`math`、`string`、`table`、`os` |
|
||||
| 外部依赖 | 无 |
|
||||
| 编码 | UTF-8(中文 3 字节解析) |
|
||||
| 烧录工具 | Luatools 3.x |
|
||||
|
||||
---
|
||||
|
||||
## 3. 硬件信息
|
||||
|
||||
> **适用产品范围**:本 demo 仅适用于合宙 **Air1780P / Air1780H**。
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 主控 | 合宙 Air1780P / Air1780H |
|
||||
| 灯板 | 22 × 22 = **484 颗** WS2812 |
|
||||
| 数据引脚 | **GPIO16(PIN97)**,`ws2812.GPIO` 模式 |
|
||||
| 通信 | USB 虚拟串口,命令 115200bps |
|
||||
|
||||
**蛇形走线**(本项目物理实际):
|
||||
|
||||
```
|
||||
y=0 偶行: LED 0 → 1 → ... → 21 左→右
|
||||
y=1 奇行: LED 43 → 42 → ... → 22 右→左
|
||||
y=2 偶行: LED 44 → 45 → ... → 65 左→右
|
||||
...
|
||||
```
|
||||
|
||||
模块通过 [ws2812_xy()](#8-公共-apiws2812_configlua) 把逻辑坐标 `(x,y)` 自动映射成正确的物理 LED 编号,业务代码不用关心蛇形。
|
||||
|
||||
**供电**:WS2812 单颗全白约 60mA,484 颗峰值约 25–30A。彩色实际 2–6A,建议外接 **5V/10A 以上** 电源并共地;USB 只用于低亮度调试(`brightness ≤ 50`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 环境配置
|
||||
|
||||
### 4.1 开发环境
|
||||
|
||||
1. 安装 [Luatools 3.x](https://docs.openluat.com/);
|
||||
2. 下载 Air1780P 对应 LuatOS SoC 固件(`.soc`,**必须含 `ws2812` 组件**,固件定义可在 [docs.openluat.com](https://docs.openluat.com/) 查询);
|
||||
3. Type-C USB 连接开发板;
|
||||
4. Luatools 新建项目,选择固件。
|
||||
|
||||
### 4.2 生产环境
|
||||
|
||||
脚本随固件烧录到片上 Flash(脚本分区约 384KB),上电自动执行 `main.lua`,独立运行无需额外运行时。灯板需外接 5V 电源并与模组共地(详见第 3 节供电)。
|
||||
|
||||
### 4.3 串口与调试
|
||||
|
||||
- USB 虚拟串口同时承载日志输出与命令输入;
|
||||
- 命令通道波特率 **115200**,在 `main.lua` 通过 `uart.setup(1, 115200)` 初始化;
|
||||
- 支持 `b=NNN`(亮度)、`s=NNN`(速度)等实时命令,详见第 10 节;
|
||||
- 若只看日志不发命令,无需额外接线。
|
||||
|
||||
### 4.4 导入 demo 文件
|
||||
|
||||
把 `ws2812/` 目录下所有 `.lua` 文件加入 Luatools 项目的脚本资源:
|
||||
|
||||
```
|
||||
ws2812/
|
||||
├── main.lua ← 勾选为 MAIN 主脚本
|
||||
├── ws2812_config.lua
|
||||
├── ws2812_fonts.lua
|
||||
├── ws2812_cmd.lua ← 串口命令模块(随 main.lua 一起加载)
|
||||
└── ws2812_*_task.lua ← 全部加入脚本资源(是否启用由 main.lua 控制)
|
||||
```
|
||||
|
||||
> 即使 `main.lua` 里某行 `require` 被注释,建议也把对应 `_task.lua` 一起烧录,方便随时取消注释切换效果,只需下次下载脚本即可(1–2 秒)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 项目结构解析
|
||||
|
||||
```
|
||||
ws2812/
|
||||
├── main.lua # 入口:只负责 require 模块加载调度
|
||||
├── ws2812_config.lua # 硬件、坐标、HSV 工具、WS2812 句柄、全局 API
|
||||
├── ws2812_fonts.lua # 字模表 + UTF-8 条带构建(滚动任务依赖)
|
||||
├── ws2812_cmd.lua # 串口命令接口(b=NNN 亮度 / s=NNN 速度)
|
||||
├── ws2812_blocks_task.lua # 色块覆盖 + LED 灯珠检测(红绿蓝白纯色循环)
|
||||
├── ws2812_rainbow_task.lua # 彩虹渐变
|
||||
├── ws2812_snake_task.lua # 蛇形扫描
|
||||
├── ws2812_sparkle_task.lua # 星点闪烁
|
||||
├── ws2812_rect_task.lua # 矩形收缩扩散
|
||||
├── ws2812_scroll_task.lua # 滚动"欢迎使用LuatOS"
|
||||
└── readme.md # 本文档
|
||||
```
|
||||
|
||||
**加载顺序**:
|
||||
|
||||
```
|
||||
main.lua
|
||||
├─ require "ws2812_config" ① 最先:初始化 WS2812_LEDS、全局工具
|
||||
├─ require "ws2812_*_task" ② 各效果任务内部再 require config / fonts
|
||||
├─ require "ws2812_cmd" ③ 串口命令接口(修改 WS2812_CFG 运行时参数)
|
||||
└─ sys.run() ④ 启动 LuatOS 调度器
|
||||
```
|
||||
|
||||
每个 `_task.lua` 自己调用 `sys.taskInit(fn)`,LuatOS 调度器会让它们**并发协作**(协作式多任务,`sys.wait()` 让出 CPU)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 快速开始
|
||||
|
||||
### 6.1 启用/切换效果
|
||||
|
||||
编辑 [main.lua](file:///c:/Users/Administrator/Desktop/1780P跑马灯/ws2812/main.lua):
|
||||
|
||||
```lua
|
||||
require "ws2812_config" -- 必须最先加载
|
||||
|
||||
-- 取消/添加注释来启停各效果(默认只启用 LED 检测,避免多个任务抢帧缓冲):
|
||||
require "ws2812_blocks_task" -- LED 灯珠检测(逐颗覆盖 + 红绿蓝白纯色循环)
|
||||
-- require "ws2812_rainbow_task"
|
||||
-- require "ws2812_snake_task"
|
||||
-- require "ws2812_sparkle_task"
|
||||
-- require "ws2812_rect_task"
|
||||
-- require "ws2812_scroll_task" -- 若要滚动文字,需把 ws2812_blocks_task.lua 里 DETECT_LOOP 改为 false
|
||||
|
||||
require "ws2812_cmd" -- 串口命令接口(b=NNN/s=NNN,建议保持开启)
|
||||
```
|
||||
|
||||
保存后在 Luatools 里只下载脚本即可,无需重烧固件。
|
||||
|
||||
### 6.2 多效果并行
|
||||
|
||||
多个 `require` 同时打开时,每个任务都是独立协程,会交替刷新同一帧缓冲。
|
||||
|
||||
> ⚠️ 两个任务同时调用 `ws2812_clear()` + `ws2812.send()` 会互相覆盖,出现闪烁。并行仅适合**分工明确**的任务(如一个负责背景、一个负责前景),或接受快速闪烁的测试场景。日常使用建议**一次只启用一个效果**。
|
||||
|
||||
### 6.3 调亮度/速度
|
||||
|
||||
- 改默认:编辑 [ws2812_config.lua](file:///c:/Users/Administrator/Desktop/1780P跑马灯/ws2812/ws2812_config.lua#L25-L28):
|
||||
```lua
|
||||
WS2812_CFG = {
|
||||
brightness = 50,
|
||||
speed_ms = 50,
|
||||
}
|
||||
```
|
||||
- 运行时:用串口助手发送 `b=80` 调亮度、`s=30` 调速度(详见 [第 10 节](#10-运行时配置与串口命令))。
|
||||
|
||||
---
|
||||
|
||||
## 7. 效果模块详解
|
||||
|
||||
### 7.1 `ws2812_blocks_task`(色块覆盖 / LED 灯珠检测)
|
||||
|
||||
用于客户验灯,方便逐色检查每颗 WS2812 是否存在坏点、虚焊、颜色通道缺失。默认 `DETECT_LOOP = true` 无限循环:
|
||||
|
||||
1. **逐颗覆盖**:按物理蛇形顺序从 0 → 483 逐颗点亮随机颜色,验证走线顺序和焊接;
|
||||
2. **全红停留 2 秒**:检查红色通道;
|
||||
3. **全绿停留 2 秒**:检查绿色通道;
|
||||
4. **全蓝停留 2 秒**:检查蓝色通道;
|
||||
5. **全白停留 2 秒**:检查三通道同时点亮(最耗电,注意供电);
|
||||
6. 清屏 1 秒后回到步骤 1。
|
||||
|
||||
**关键参数**(文件顶部可改):
|
||||
|
||||
| 参数 | 默认 | 含义 |
|
||||
|------|----:|------|
|
||||
| `DETECT_LOOP` | `true` | `true`=无限循环检测模式;`false`=开机过渡模式(跑 `BLOCK_ROUNDS` 圈后退出) |
|
||||
| `BLOCK_ROUNDS` | `2` | 非循环模式下的覆盖圈数 |
|
||||
| `FILL_STEP_MS` | `10` | 逐颗覆盖时每颗的间隔 ms |
|
||||
| `HOLD_MS` | `300` | 每轮覆盖填满后的停顿 ms |
|
||||
| `DETECT_HOLD_MS` | `2000` | 红/绿/蓝/白每色停留时间 ms |
|
||||
| `GAP_MS` | `1000` | 一轮结束后清屏停留 ms |
|
||||
|
||||
**坏点判断方法**:
|
||||
- 某位置在所有纯色下都不亮 → LED 损坏或虚焊;
|
||||
- 只在某一通道不亮(例如全红时缺一颗)→ 该通道 LED 芯片损坏;
|
||||
- 颜色顺序错乱或前一颗影响后一颗 → DIN 走线/焊接问题;
|
||||
- 白色时整板变暗/闪烁 → 电源电流不足,降低 `brightness`。
|
||||
|
||||
> ⚠️ 全白模式 484 颗同时点亮电流最大(亮度 255 时约 25–30A),默认亮度 50 时约 5–6A,USB 供电无法支撑,必须外接 5V 电源并共地。
|
||||
|
||||
### 7.2 `ws2812_rainbow_task`(彩虹渐变)
|
||||
|
||||
- 每帧遍历 22×22 所有 LED;
|
||||
- 色相 `h = ((x+y)*360/44 + frame*3) % 360`,斜向彩虹随帧向右上流动;
|
||||
- 一轮 120 帧后循环。
|
||||
|
||||
**关键参数**(文件顶部可改):`steps = 120`、色相步进 `frame*3`。
|
||||
|
||||
### 7.3 `ws2812_snake_task`(蛇形扫描)
|
||||
|
||||
- 光点按 **物理 LED 编号** 0→483 跑动,拖尾 6 颗线性渐暗;
|
||||
- 因为灯板蛇形走线,视觉上光点"S 形"逐行扫描,是验证蛇形映射最直观的效果;
|
||||
- 颜色随位置色相变化。
|
||||
|
||||
**关键参数**:`trail = 6`(拖尾长度),间隔 `speed_ms * 2`。
|
||||
|
||||
### 7.4 `ws2812_sparkle_task`(星点闪烁)
|
||||
|
||||
- 每帧随机点亮 30 颗 LED,每颗随机色相;
|
||||
- 每 100 帧后重新一轮。
|
||||
|
||||
**关键参数**:每轮帧数 `100`、每帧点数 `30`。
|
||||
|
||||
### 7.5 `ws2812_rect_task`(矩形收缩扩散)
|
||||
|
||||
- 从中心点 `(11,11)` 开始画矩形框,半径从 0 扩到 11 再收回;
|
||||
- 颜色 `h = rr*20 % 360` 随半径变化;
|
||||
- 越界由 `ws2812_set()` 自动忽略。
|
||||
|
||||
**关键参数**:间隔 `speed_ms * 3`(呼吸感更明显)。
|
||||
|
||||
### 7.6 `ws2812_scroll_task`(滚动文字)
|
||||
|
||||
- 文本默认 `"欢迎使用LuatOS"`(UTF-8);
|
||||
- 中文字模 22×22(`FONT_CN`),英文半宽 22×11(`FONT_EN22`),未收录的 ASCII 回退到 5×7 放大字模;
|
||||
- 英文字模渲染时 `voffset=2` 下移 2 排与中文视觉对齐;
|
||||
- 文字从右侧(offset = -22)滑入、向左滑出(offset = strip_w-1),无空白帧循环;
|
||||
- 每轮通过 6 色区 LCG 选一个新颜色。
|
||||
|
||||
**改文本**:修改 [ws2812_scroll_task.lua#L26](file:///c:/Users/Administrator/Desktop/1780P跑马灯/ws2812/ws2812_scroll_task.lua#L26) `local SCROLL_TEXT = "欢迎使用LuatOS"`。若含未收录汉字,需要在 `ws2812_fonts.lua` 的 `FONT_CN` 里补字模。
|
||||
|
||||
---
|
||||
|
||||
## 8. 公共 API(ws2812_config.lua)
|
||||
|
||||
`require "ws2812_config"` 后以下符号全局可用。
|
||||
|
||||
### 8.1 常量
|
||||
|
||||
| 名称 | 类型 | 值 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `LED_W` | number | 22 | 灯板列数 |
|
||||
| `LED_H` | number | 22 | 灯板行数 |
|
||||
| `LED_COUNT` | number | 484 | LED 总数 |
|
||||
| `LED_GPIO` | number | 16 | DIN 引脚编号(PIN97) |
|
||||
| `WS2812_LEDS` | userdata | — | ws2812 句柄,失败为 `nil` |
|
||||
| `WS2812_CFG` | table | `{brightness=50, speed_ms=50}` | 运行时配置,各任务读取 |
|
||||
|
||||
### 8.2 函数
|
||||
|
||||
#### `ws2812_xy(x, y) -> index`
|
||||
|
||||
蛇形逻辑坐标 → 物理 LED 索引(0-based)。
|
||||
- `x, y` 范围:`0..LED_W-1`、`0..LED_H-1`;
|
||||
- 奇数行自动水平翻转以匹配蛇形走线。
|
||||
|
||||
```lua
|
||||
local idx = ws2812_xy(0, 1) -- 第 1 行第 0 列 → 物理 43(右→左)
|
||||
```
|
||||
|
||||
#### `ws2812_hsv2rgb(h, s, v) -> color`
|
||||
|
||||
HSV 转 24-bit RGB 整数。
|
||||
- `h` 色相 0–359;`s` 饱和度 0–255;`v` 明度 0–255;
|
||||
- 返回值可直接传给 `ws2812.set` / `ws2812_set`。
|
||||
|
||||
```lua
|
||||
local red = ws2812_hsv2rgb(0, 255, 100)
|
||||
```
|
||||
|
||||
#### `ws2812_rgb(r, g, b) -> color`
|
||||
|
||||
RGB 打包为 24-bit 整数。
|
||||
|
||||
```lua
|
||||
local white = ws2812_rgb(80, 80, 80)
|
||||
```
|
||||
|
||||
#### `ws2812_set(x, y, color)`
|
||||
|
||||
按**逻辑坐标**点亮一颗 LED。越界自动忽略,句柄未就绪时静默丢弃,不会抛错。内部先 `ws2812_xy` 映射再写缓冲。
|
||||
|
||||
```lua
|
||||
ws2812_set(11, 11, ws2812_rgb(255, 0, 0)) -- 中心点红灯
|
||||
ws2812_send() -- 记得发送
|
||||
```
|
||||
|
||||
#### `ws2812_fill(color)`
|
||||
|
||||
整片填充同一颜色并立即 `send`。句柄未就绪时静默返回。
|
||||
|
||||
```lua
|
||||
ws2812_fill(ws2812_rgb(0, 0, 60)) -- 整片暗蓝
|
||||
```
|
||||
|
||||
#### `ws2812_clear()`
|
||||
|
||||
把整片缓冲写 0。**不发送**,调用后需要 `ws2812_send()` 才会真正熄灭。
|
||||
|
||||
```lua
|
||||
ws2812_clear()
|
||||
ws2812_send()
|
||||
```
|
||||
|
||||
#### `ws2812_send()`
|
||||
|
||||
发送当前缓冲到灯板。所有 `ws2812_set/clear` 都是写缓冲,必须调用此函数才会刷新 LED。句柄未就绪(`ws2812.create` 失败)时静默返回,不会对 nil 调用 C 函数导致崩溃。
|
||||
|
||||
```lua
|
||||
ws2812_send()
|
||||
```
|
||||
|
||||
### 8.3 底层 ws2812 固件 API
|
||||
|
||||
公共函数未覆盖的高级用法可直接调用固件 API:
|
||||
|
||||
```lua
|
||||
ws2812.create(ws2812.GPIO, LED_COUNT, LED_GPIO)
|
||||
ws2812.args(WS2812_LEDS, 20, 30, 35, 20, 0) -- 本项目实测时序,勿改
|
||||
ws2812.set(WS2812_LEDS, physical_index, color)
|
||||
ws2812.send(WS2812_LEDS)
|
||||
```
|
||||
|
||||
> 时序 `(20,30,35,20,0)` 单位 ns,经实测稳定,乱改可能导致颜色错乱或不亮。
|
||||
|
||||
---
|
||||
|
||||
## 9. 字模与文本条带(ws2812_fonts.lua)
|
||||
|
||||
`ws2812_scroll_task` 依赖此模块,其他效果不需要。
|
||||
|
||||
### 9.1 字模表
|
||||
|
||||
| 表 | 尺寸 | 内容 |
|
||||
|----|------|------|
|
||||
| `FONT_CN` | 22 行 × 22 列 | 中文 欢/迎/使/用 |
|
||||
| `FONT_EN22` | 22 行 × 11 列 | 英文 L/u/a/t/O/S(Consolas 半宽) |
|
||||
| `FONT5x7` | 5 字节/字 | A–Z 大写、部分小写,作为 ASCII 回退 |
|
||||
|
||||
字符串点阵格式:等长字符串,`#` = 亮,`.` = 灭。
|
||||
|
||||
### 9.2 列掩码函数
|
||||
|
||||
```lua
|
||||
ws2812_col_mask(glyph, col, voffset) -> integer
|
||||
```
|
||||
|
||||
把某字形的某一列打包成 22-bit 整数(bit n 表示第 n 行是否亮)。`voffset` 用于垂直偏移(英文字模传 2 下移 2 排)。
|
||||
|
||||
### 9.3 条带构建
|
||||
|
||||
```lua
|
||||
local columns = ws2812_build_strip("欢迎使用LuatOS")
|
||||
-- columns 是数组,每项是一列的 22-bit 掩码
|
||||
-- 字间距自动加 2 列空白
|
||||
```
|
||||
|
||||
内部 UTF-8 解析使用 `while` 循环(中文 3 字节、ASCII 1 字节)。⚠️ 切勿改成数值 `for` + 体内 `i=i+3`,Lua for 循环变量不接受体内修改,会导致中文乱码。
|
||||
|
||||
### 9.4 渲染一帧示例
|
||||
|
||||
```lua
|
||||
for y = 0, 21 do
|
||||
local bit = 1 << y
|
||||
for x = 0, 21 do
|
||||
local col_idx = x + offset + 1
|
||||
if col_idx >= 1 and col_idx <= #columns then
|
||||
if (columns[col_idx] & bit) ~= 0 then
|
||||
ws2812_set(x, y, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
ws2812_send()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 运行时配置与串口命令
|
||||
|
||||
### 10.1 配置表
|
||||
|
||||
所有任务每帧直接读 `WS2812_CFG`,运行时修改立即生效(下一帧)。
|
||||
|
||||
```lua
|
||||
WS2812_CFG.brightness = 80 -- 0–255
|
||||
WS2812_CFG.speed_ms = 60 -- 20–2000 ms
|
||||
```
|
||||
|
||||
### 10.2 串口命令
|
||||
|
||||
[ws2812_cmd.lua](file:///c:/Users/Administrator/Desktop/1780P跑马灯/ws2812/ws2812_cmd.lua) 在 UART1(USB 虚拟串口,115200)注册了命令解析。用任意串口助手(Luatools 自带、SSCOM、minicom 等)发送:
|
||||
|
||||
| 命令 | 作用 | 范围 |
|
||||
|------|------|------|
|
||||
| `b=80\r\n` | 设置亮度 | 0–255 |
|
||||
| `s=60\r\n` | 设置帧间隔 | 20–2000 ms(越小越快) |
|
||||
| `b?\r\n` | 查询当前亮度 | — |
|
||||
| `s?\r\n` | 查询当前速度 | — |
|
||||
|
||||
**交互示例**:
|
||||
|
||||
```
|
||||
> b=100
|
||||
< [cmd] brightness = 100
|
||||
> s=30
|
||||
< [cmd] speed_ms = 30
|
||||
> b?
|
||||
< [cmd] brightness = 100
|
||||
```
|
||||
|
||||
**实现要点**:
|
||||
- 命令以 `\r` 或 `\n` 结尾,支持 `\r\n`;
|
||||
- 非法参数自动钳制范围,不会崩溃;
|
||||
- 命令在独立 `sys.task` 里处理,不阻塞 UART 接收回调。
|
||||
|
||||
### 10.3 速度系数
|
||||
|
||||
各任务内部对 `speed_ms` 的使用并不统一,以实现不同的"节奏感":
|
||||
|
||||
| 任务 | 实际帧间隔 |
|
||||
|------|-----------|
|
||||
| rainbow / scroll | `speed_ms` × 1 |
|
||||
| snake / sparkle | `speed_ms` × 2 |
|
||||
| rect | `speed_ms` × 3 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 编写自定义效果
|
||||
|
||||
### 11.1 模板
|
||||
|
||||
在 `ws2812/` 新建 `ws2812_myeffect_task.lua`:
|
||||
|
||||
```lua
|
||||
--[[
|
||||
@module ws2812_myeffect_task
|
||||
@summary 我的自定义 WS2812 效果
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
核心业务逻辑:每帧画一个对角线上移动的红点
|
||||
]]
|
||||
|
||||
require "ws2812_config"
|
||||
|
||||
local function myeffect_task()
|
||||
if not WS2812_LEDS then return end
|
||||
sys.wait(100) -- 必须等开机复位清零完成
|
||||
|
||||
local pos = 0
|
||||
while 1 do
|
||||
ws2812_clear()
|
||||
local color = ws2812_hsv2rgb(0, 255, WS2812_CFG.brightness)
|
||||
ws2812_set(pos, pos, color) -- 对角线上移动
|
||||
ws2812_set(LED_W - 1 - pos, pos, color)
|
||||
ws2812_send()
|
||||
pos = (pos + 1) % LED_W
|
||||
sys.wait(WS2812_CFG.speed_ms)
|
||||
end
|
||||
end
|
||||
|
||||
sys.taskInit(myeffect_task)
|
||||
```
|
||||
|
||||
### 11.2 在 main.lua 注册
|
||||
|
||||
```lua
|
||||
require "ws2812_myeffect_task"
|
||||
```
|
||||
|
||||
### 11.3 注意事项
|
||||
|
||||
1. **开头 `sys.wait(100)`**:等 config 里 50ms 开机复位发送完成,避免第一帧竞争。
|
||||
2. **每帧 clear → set → send**:WS2812 没有自动清屏,遗漏 `clear()` 会让上一帧残留。
|
||||
3. **用逻辑坐标 `ws2812_set(x,y,color)`**,不要直接 `ws2812.set(LEDS, idx, color)` 除非你明确要操作物理索引(如 snake 任务)。
|
||||
4. **不要在回调里跑循环**:长逻辑放在 `sys.taskInit` 协程里,UART/GPIO 回调只做标志设置。
|
||||
5. **避免忙等**:用 `sys.wait(ms)` 让出 CPU,禁止 `while true do end` 无等待。
|
||||
|
||||
---
|
||||
|
||||
## 12. 故障排除
|
||||
|
||||
| 现象 | 可能原因 | 解决 |
|
||||
|------|---------|------|
|
||||
| 启动报 `bad argument #2 to 'timer_start' (number expected, got nil)` | 任务里 `sys.wait(WS2812_CFG.speed_ms)` 取到 nil | 确认 `require "ws2812_config"` 在任务文件顶部;config 中字段名是 `speed_ms` 不是 `scroll_ms` |
|
||||
| 开机只有左上角 1 颗微亮 | DIN 上电杂波被锁存(鬼影) | config 已内置 50ms 复位;若仍存在,检查 330Ω 串联电阻、电源稳定性 |
|
||||
| 完全不亮 | GPIO/电源/接线错误 | DIN 接 GPIO16/PIN97;VCC 5V;GND 共地;勿改 `ws2812.args` |
|
||||
| 竖线呈之字形 | 蛇形映射未生效 | 不要直接使用物理索引;用 `ws2812_set(x,y,...)`;若走线方向相反,在 config 中翻转奇数行判定 |
|
||||
| 颜色错乱、绿色闪烁 | 电源电流不足 | 外接 5V/10A+;降低 `brightness`;检查共地 |
|
||||
| 滚动中文乱码 / 字后多空白列 | UTF-8 用了数值 for | 必须用 `while` 循环(见 9.3) |
|
||||
| 串口命令无反应 | 串口号/波特率/换行 | 用 Luatools 日志所在虚拟串口;115200;命令末尾回车 `\r\n` |
|
||||
| 两个效果同时启用时严重闪烁 | 两任务都 clear+send 抢缓冲 | 同一时刻只启用一个效果,或合并到一个任务 |
|
||||
| 日志报 "ws2812.create 失败" | 固件不含 ws2812 组件 | 换用含 ws2812 的 Air1780P LuatOS SoC 固件(固件定义可在 [docs.openluat.com](https://docs.openluat.com/) 查询) |
|
||||
| 改了脚本但灯效没变 | Luatools MAIN 指向了另一个 main.lua | 确认 `ws2812/main.lua` 被勾选为 MAIN 主脚本 |
|
||||
|
||||
---
|
||||
|
||||
## 13. FAQ
|
||||
|
||||
**Q0:本 demo 支持哪些合宙模组?**
|
||||
A:本 demo **仅适用于合宙 Air1780P / Air1780H**。
|
||||
|
||||
**Q1:为什么拆成这么多文件?**
|
||||
A:每个 `xxx_task.lua` 是一个独立可参考的最小示例,方便学习和复制;公共部分抽到 config/fonts,避免重复。
|
||||
|
||||
**Q2:能同时跑彩虹 + 滚动文字吗?**
|
||||
A:技术上可以(都 `require` 即可),但两个任务都在 `clear + send`,会互相覆盖导致闪烁。推荐一次只开一个;如果确实要叠加,需要修改其中一个任务不要 clear、只写自己负责的像素。
|
||||
|
||||
**Q3:怎么改灯板尺寸(比如改成 16×16)?**
|
||||
A:改 `ws2812_config.lua` 里 `LED_W/LED_H/LED_COUNT/LED_GPIO`,蛇形映射 `ws2812_xy` 自动适配;字模是 22 宽,换尺寸后滚动文字可能需要换字模或调整坐标裁剪。
|
||||
|
||||
**Q4:怎么加新的中文字模?**
|
||||
A:在 `ws2812_fonts.lua` 的 `FONT_CN` 里加 `["新字"] = { "....", ... }`,必须 22 行、每行 22 字符。字模可由点阵字模工具生成后解码为 `#`/`.`。
|
||||
|
||||
**Q5:亮度设多少合适?**
|
||||
A:USB 供电建议 ≤ 50;外接 5V/10A 电源可设 80–150;长时间运行不建议长期 255(电流大、发热明显)。
|
||||
|
||||
**Q6:为什么英文下移 2 排?**
|
||||
A:中文字模基本占满 22 行,英文 22×11 字模上下留白较多,整体视觉偏上;下移 2 排后中英文视觉重心对齐。
|
||||
|
||||
**Q7:`speed_ms` 设多小会死机?**
|
||||
A:代码里串口命令钳制最小 20ms。低于 20ms 可能 WS2812 发送(484 颗约 15ms)占满 CPU 影响系统响应;建议 ≥ 30ms。
|
||||
|
||||
---
|
||||
|
||||
## 14. 官方支持渠道
|
||||
|
||||
- 文档中心:https://docs.openluat.com/
|
||||
- 开源代码:https://gitee.com/openLuat/LuatOS/tree/master/module
|
||||
- 官方企业微信群:https://docs.openluat.com/ 网站底部二维码,扫码加入
|
||||
- 官方淘宝店:https://luat.taobao.com/ (选购核心板、开发板,对比验证)
|
||||
@@ -0,0 +1,198 @@
|
||||
--[[
|
||||
@module ws2812_blocks_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 色块覆盖 / LED 灯珠检测
|
||||
@version 1.1
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
本任务直接对 WS2812 物理编号 0..LED_COUNT-1 操作,灯板尺寸不是 22×22 时请修改 LED_COUNT。
|
||||
|
||||
本文件用于开机色块整板覆盖演示,同时可作为 LED 灯珠检测 demo,
|
||||
方便客户逐色检查每颗 WS2812 是否存在坏点、虚焊、颜色通道缺失。
|
||||
|
||||
检测流程(循环执行):
|
||||
1. 逐颗覆盖(红):按物理蛇形顺序从 0 到 483 逐颗点亮,验证走线顺序与焊接
|
||||
2. 全红停留 2 秒:检查红色通道
|
||||
3. 全绿停留 2 秒:检查绿色通道
|
||||
4. 全蓝停留 2 秒:检查蓝色通道
|
||||
5. 全白停留 2 秒:检查三通道同时点亮(最耗电,注意供电)
|
||||
6. 清屏 1 秒后回到步骤 1 切换下一种颜色重新覆盖
|
||||
|
||||
若只需要开机过渡效果(跑 2 圈后退出),把 DETECT_LOOP 改为 false。
|
||||
|
||||
依赖:ws2812_config.lua 中需提供 LED_COUNT / WS2812_LEDS / ws2812_send /
|
||||
ws2812_clear / ws2812_hsv2rgb / ws2812_rgb 等全局量。
|
||||
]]
|
||||
|
||||
require "ws2812_config"
|
||||
|
||||
-- 配置缺省值兜底:外部未传 WS2812_CFG 时给一组安全默认值
|
||||
if not WS2812_CFG then WS2812_CFG = { brightness = 50, speed_ms = 50 } end
|
||||
if not WS2812_CFG.brightness then WS2812_CFG.brightness = 50 end
|
||||
|
||||
-- ====== 配置参数 ======
|
||||
|
||||
-- DETECT_LOOP 两种工作模式:
|
||||
-- true = 灯珠检测模式:无限循环“逐颗覆盖 + 红绿蓝白纯色全屏”,供产线/客户验灯;
|
||||
-- false = 开机过渡模式:只跑 BLOCK_ROUNDS 圈随机色覆盖后清屏退出,不做纯色检测。
|
||||
local DETECT_LOOP = true
|
||||
-- 非循环模式下跑几圈
|
||||
local BLOCK_ROUNDS = 2
|
||||
-- 每推进一颗灯的间隔 ms(覆盖阶段)
|
||||
local FILL_STEP_MS = 10
|
||||
-- 每填满一轮后停顿 ms
|
||||
local HOLD_MS = 300
|
||||
-- 纯色检测停留时间 ms(红/绿/蓝/白各停多久)
|
||||
local DETECT_HOLD_MS = 2000
|
||||
-- 轮次之间清屏停留 ms
|
||||
local GAP_MS = 1000
|
||||
|
||||
-- 检测阶段使用的纯色序列(顺序:红→绿→蓝→白)
|
||||
-- 每通道 255 为“满量程原色”,实际显示时会再按 brightness 缩放
|
||||
local DETECT_COLORS = {
|
||||
{ r = 255, g = 0, b = 0, name = "RED" },
|
||||
{ r = 0, g = 255, b = 0, name = "GREEN" },
|
||||
{ r = 0, g = 0, b = 255, name = "BLUE" },
|
||||
{ r = 255, g = 255, b = 255, name = "WHITE" },
|
||||
}
|
||||
|
||||
-- 6 个互斥色区(覆盖阶段随机选色,保证相邻轮次颜色明显不同)
|
||||
-- 每个区间是 HSV 色相 H 的 [起, 止] 范围(0~359 度),分别对应红/黄/绿/青/蓝/紫
|
||||
local COLOR_ZONES = {
|
||||
{ 0, 59}, -- 红
|
||||
{ 60, 119}, -- 黄
|
||||
{120, 179}, -- 绿
|
||||
{180, 239}, -- 青
|
||||
{240, 299}, -- 蓝
|
||||
{300, 359}, -- 紫
|
||||
}
|
||||
|
||||
-- ====== 随机颜色(自带 LCG,不依赖 math.random) ======
|
||||
-- 这里没有直接用 math.random,原因:
|
||||
-- 1) 嵌入式 LuatOS 固件中 math.random 的可用性/种子行为不稳定,
|
||||
-- 未显式 seed 时多次开机可能拿到相同序列;
|
||||
-- 2) 色块演示只需要“看起来随机且每次不同”,用 glibc 经典 LCG
|
||||
-- (Linear Congruential Generator,线性同余发生器)即可,
|
||||
-- 纯整数运算、无浮点、无外部依赖、可预测且足够轻量。
|
||||
-- 常量来源:glibc rand(),公式 seed' = (seed * 1103515245 + 12345) mod 2^31
|
||||
-- 1103515245 与 12345 是 glibc 选定的乘数/增量,周期约 2^31;
|
||||
-- 2147483647 = 2^31 - 1,用于把 os.time() 截到 31 位有符号正数范围作种子;
|
||||
-- 2147483648 = 2^31,是 LCG 状态的模值(结果落在 [0, 2^31-1])。
|
||||
local lcg_seed = os.time() % 2147483647
|
||||
|
||||
--- 返回下一个 0~32767 的伪随机整数
|
||||
-- @return integer 伪随机值,范围 [0, 32767]
|
||||
-- 取高 15 位:先 /65536 右移 16 位,再 %32768 保留 15 位(glibc rand 习惯)
|
||||
local function lcg_next()
|
||||
lcg_seed = (lcg_seed * 1103515245 + 12345) % 2147483648
|
||||
return math.floor(lcg_seed / 65536) % 32768
|
||||
end
|
||||
|
||||
-- 记录上一次选中的色区,初始 -1 保证第一次一定能选到任意色区
|
||||
local prev_zone = -1
|
||||
|
||||
--- 从 6 个色区中挑一个(与上轮不同),再在该区内随机取一个色相,
|
||||
--- 转成受 brightness 约束的 RGB 颜色。
|
||||
-- @return integer ws2812 可用的颜色值(已按全局亮度缩放 V 分量)
|
||||
local function pick_color()
|
||||
local idx
|
||||
repeat
|
||||
-- % #COLOR_ZONES 把随机数映射到 [1,6] 的数组下标减一(Lua 数组从 1 开始)
|
||||
idx = lcg_next() % #COLOR_ZONES
|
||||
until idx ~= prev_zone
|
||||
prev_zone = idx
|
||||
local z = COLOR_ZONES[idx + 1]
|
||||
-- 在色区 [z[1], z[2]] 内均匀取一个色相值 H
|
||||
local h = z[1] + lcg_next() % (z[2] - z[1] + 1)
|
||||
-- S=255 高饱和;V=brightness 直接受全局亮度钳位,避免覆盖阶段过亮
|
||||
return ws2812_hsv2rgb(h, 255, WS2812_CFG.brightness)
|
||||
end
|
||||
|
||||
-- ====== 填充函数 ======
|
||||
|
||||
--- 把整板所有灯珠填成同一颜色并发送
|
||||
-- @param color integer ws2812 颜色值
|
||||
-- @return 无
|
||||
local function fill_all(color)
|
||||
for i = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, i, color)
|
||||
end
|
||||
ws2812_send()
|
||||
end
|
||||
|
||||
--- 按蛇形物理顺序逐颗点亮整板(已点亮的保持 color 不熄灭)
|
||||
-- @param color integer ws2812 颜色值
|
||||
-- @return 无
|
||||
-- 批量发送策略:每推进 8 颗或到达最后一颗时才调用一次 send。
|
||||
-- head % 8 == 0 即“每行 8 颗对齐”,既保证动画有连贯的流动感,
|
||||
-- 又避免每点亮一颗就关中断发送一次(WS2812 发送期间 CPU 被占用,
|
||||
-- 发得太密会拖慢系统、心跳/其他任务卡顿)。
|
||||
local function sequential_cover(color)
|
||||
for head = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, head, color)
|
||||
if (head % 8 == 0) or (head == LED_COUNT - 1) then
|
||||
ws2812_send()
|
||||
end
|
||||
sys.wait(FILL_STEP_MS)
|
||||
end
|
||||
-- 兜底再发一次,确保最后一批 set 的数据真正送到灯带
|
||||
ws2812_send()
|
||||
end
|
||||
|
||||
-- ====== 任务主函数 ======
|
||||
|
||||
--- 色块覆盖 / 灯珠检测任务主体
|
||||
-- 根据 DETECT_LOOP 选择无限循环验灯或开机过渡 N 圈后退出。
|
||||
-- @return 无
|
||||
local function blocks_task()
|
||||
if not WS2812_LEDS then return end
|
||||
sys.wait(100) -- 等待 config 开机复位清零完成
|
||||
|
||||
if DETECT_LOOP then
|
||||
-- ==================== 灯珠检测模式(无限循环) ====================
|
||||
log.info("blocks", "LED 灯珠检测模式启动,共", LED_COUNT, "颗灯珠")
|
||||
while true do
|
||||
-- 阶段 1:逐颗覆盖(用随机彩色,方便观察蛇形走线是否正确)
|
||||
sequential_cover(pick_color())
|
||||
sys.wait(HOLD_MS)
|
||||
|
||||
-- 阶段 2:红/绿/蓝/白 纯色全屏,每色停留 2 秒
|
||||
for _, c in ipairs(DETECT_COLORS) do
|
||||
-- 纯色必须按 brightness 缩放:
|
||||
-- 483 颗 WS2812 三通道满亮时峰值电流可达数安甚至近 30A,
|
||||
-- 远超 USB/常规供电能力,会导致压降、复位、颜色失真;
|
||||
-- 按亮度比例缩放 R/G/B 可把整板功率压在供电安全范围内,
|
||||
-- 同时仍能完整检验每个颜色通道是否正常。
|
||||
local b = WS2812_CFG.brightness
|
||||
local color = ws2812_rgb(
|
||||
math.floor(c.r * b / 255),
|
||||
math.floor(c.g * b / 255),
|
||||
math.floor(c.b * b / 255)
|
||||
)
|
||||
fill_all(color)
|
||||
log.info("blocks", "检测颜色:", c.name, "亮度:", b)
|
||||
sys.wait(DETECT_HOLD_MS)
|
||||
end
|
||||
|
||||
-- 清屏停顿
|
||||
ws2812_clear()
|
||||
ws2812_send()
|
||||
sys.wait(GAP_MS)
|
||||
end
|
||||
else
|
||||
-- ==================== 开机过渡模式(跑 N 圈后退出) ====================
|
||||
for round = 1, BLOCK_ROUNDS do
|
||||
local color = pick_color()
|
||||
log.info("blocks", "第", round, "圈开始")
|
||||
sequential_cover(color)
|
||||
log.info("blocks", "第", round, "圈完成")
|
||||
sys.wait(HOLD_MS)
|
||||
end
|
||||
ws2812_clear()
|
||||
ws2812_send()
|
||||
log.info("blocks", "色块覆盖结束")
|
||||
end
|
||||
end
|
||||
|
||||
-- 执行色块覆盖任务函数
|
||||
sys.taskInit(blocks_task)
|
||||
@@ -0,0 +1,95 @@
|
||||
--[[
|
||||
@module ws2812_cmd
|
||||
@summary Air1780P/H/HV 串口命令接口,运行时调节亮度和速度
|
||||
@version 1.0
|
||||
@date 2026.08.18
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
|
||||
本模块负责通过 UART1(USB 虚拟串口)接收文本命令,动态修改
|
||||
WS2812_CFG.brightness 和 WS2812_CFG.speed_ms,无需重新烧录。
|
||||
|
||||
支持命令(以回车或换行结尾):
|
||||
b=NNN 设置亮度 0~255
|
||||
s=NNN 设置帧间隔 20~2000ms
|
||||
b? 查询当前亮度
|
||||
s? 查询当前帧间隔
|
||||
|
||||
通信参数:115200 8N1。
|
||||
]]
|
||||
|
||||
-- UART_ID = 1:在 Air 系列模组上,UART1 默认映射为 USB 虚拟串口,
|
||||
-- 插 USB 后电脑会枚举出一个 COM 口,直接发命令即可,不需要额外接 USB-TTL。
|
||||
local UART_ID = 1
|
||||
|
||||
-- rx_buf:接收累积缓冲区(字符串)。
|
||||
-- 串口数据是流式到达的,一条 "b=128\r\n" 可能被底层拆成多次 recv 回调
|
||||
-- (比如先收到 "b=1",再收到 "28\r",再收到 "\n"),不能假设一次回调就是一条完整命令。
|
||||
-- 每次收到数据就拼到 rx_buf 末尾,再用 string.find 查找换行符,切出完整的一行,
|
||||
-- 剩余未换行的残片继续留在 rx_buf 里等下一次拼接。
|
||||
local rx_buf = ""
|
||||
|
||||
-- cmd_queue:命令队列(FIFO)。
|
||||
-- uart.on 的 "recv" 回调运行在中断/事件上下文中,里面绝对不能做耗时操作
|
||||
-- (比如 sys.wait、大量 log、写 WS2812 灯带),否则会阻塞串口接收,造成丢字节。
|
||||
-- 正确做法:回调里只做"读字节 + 拼包 + 切行",把切好的完整命令塞进 cmd_queue,
|
||||
-- 然后立刻返回;由独立协程 task 每 20ms 轮询队列,取出命令慢慢处理。
|
||||
local cmd_queue = {}
|
||||
|
||||
-- clamp:数值范围保护,把输入值钳制在 [lo, hi] 闭区间内。
|
||||
-- tonumber 失败时用 lo 作为默认值;math.floor 保证是整数。
|
||||
local function clamp(v, lo, hi)
|
||||
v = math.floor(tonumber(v) or lo)
|
||||
if v < lo then v = lo elseif v > hi then v = hi end
|
||||
return v
|
||||
end
|
||||
|
||||
-- 独立 task:从命令队列取命令并处理
|
||||
sys.taskInit(function()
|
||||
while true do
|
||||
if #cmd_queue > 0 then
|
||||
local line = table.remove(cmd_queue, 1)
|
||||
if line == "b?" then
|
||||
log.info("cmd", "brightness =", WS2812_CFG.brightness)
|
||||
elseif line == "s?" then
|
||||
log.info("cmd", "speed_ms =", WS2812_CFG.speed_ms)
|
||||
else
|
||||
-- 匹配 b=数字 或 s=数字 形式
|
||||
local k, v = string.match(line, "^([bs])=(%-?%d+)$")
|
||||
if k == "b" then
|
||||
WS2812_CFG.brightness = clamp(v, 0, 255)
|
||||
log.info("cmd", "brightness =", WS2812_CFG.brightness)
|
||||
elseif k == "s" then
|
||||
WS2812_CFG.speed_ms = clamp(v, 20, 2000)
|
||||
log.info("cmd", "speed_ms =", WS2812_CFG.speed_ms)
|
||||
else
|
||||
log.info("cmd", "unknown:", line, "(use b=NNN s=NNN b? s?)")
|
||||
end
|
||||
end
|
||||
end
|
||||
sys.wait(20)
|
||||
end
|
||||
end)
|
||||
|
||||
-- 初始化串口;if uart then 是防御性判断,防止精简固件无 uart 模块时崩溃
|
||||
if uart then
|
||||
uart.setup(UART_ID, 115200)
|
||||
uart.on(UART_ID, "recv", function(id, len)
|
||||
local data = uart.read(id, len)
|
||||
if not data then return end
|
||||
rx_buf = rx_buf .. data
|
||||
while true do
|
||||
-- 兼容 \n / \r / \r\n 三种换行风格
|
||||
local s, e = string.find(rx_buf, "[\r\n]+")
|
||||
if not s then break end
|
||||
local line = string.sub(rx_buf, 1, s - 1)
|
||||
rx_buf = string.sub(rx_buf, e + 1)
|
||||
if line ~= "" then
|
||||
cmd_queue[#cmd_queue + 1] = line
|
||||
end
|
||||
end
|
||||
end)
|
||||
log.info("cmd", "串口命令已就绪 (115200): b=NNN s=NNN b? s?")
|
||||
else
|
||||
log.warn("cmd", "固件未启用 uart,无法使用串口命令")
|
||||
end
|
||||
@@ -0,0 +1,162 @@
|
||||
--[[
|
||||
@module ws2812_config
|
||||
@summary WS2812 22×22 点阵公共配置(硬件参数、坐标映射、颜色工具、句柄初始化)
|
||||
@version 1.1
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
灯板尺寸不是 22×22 时,请同步修改 LED_W / LED_H / LED_COUNT。
|
||||
|
||||
本文件由 main.lua 通过 require 自动加载,为各效果模块提供全局 WS2812 句柄与工具函数:
|
||||
- LED_W / LED_H / LED_COUNT / LED_GPIO 硬件参数
|
||||
- WS2812_CFG 运行时配置(brightness / speed_ms)
|
||||
- WS2812_LEDS ws2812 句柄(创建失败为 nil)
|
||||
- ws2812_xy(x, y) 蛇形逻辑坐标 → 物理 LED 编号
|
||||
- ws2812_hsv2rgb(h, s, v) HSV → RGB888 整数
|
||||
- ws2812_rgb(r, g, b) RGB → 24bit 整数
|
||||
- ws2812_set(x, y, color) 按逻辑坐标点亮(写缓冲,不发送)
|
||||
- ws2812_fill(color) 整片填充并立即发送
|
||||
- ws2812_clear() 整片清屏(写缓冲,不发送)
|
||||
- ws2812_send() 发送当前缓冲到灯板
|
||||
]]
|
||||
|
||||
-- ==================== 硬件参数 ====================
|
||||
-- 根据实际灯板修改这几个值即可适配其他尺寸
|
||||
LED_W = 22 -- 灯板列数(水平方向 LED 数量)
|
||||
LED_H = 22 -- 灯板行数(垂直方向 LED 数量)
|
||||
LED_COUNT = LED_W * LED_H -- LED 总数 = 484
|
||||
LED_GPIO = 16 -- DIN 数据引脚接 Air1780P/H/HV 的 GPIO16(PIN97)
|
||||
|
||||
-- ==================== 运行时可调参数 ====================
|
||||
-- 可通过串口命令 b=NNN / s=NNN 在运行时修改,无需重新烧录
|
||||
WS2812_CFG = {
|
||||
brightness = 50, -- 亮度 0~255。全白 255 时整机峰值电流约 25-30A,注意电源
|
||||
speed_ms = 50, -- 默认帧间隔 ms,越小动画越快。各任务会再乘自己的速度系数
|
||||
}
|
||||
|
||||
-- ==================== 蛇形走线映射 ====================
|
||||
-- 本灯板物理走线为蛇形:偶数行从左到右,奇数行从右到左
|
||||
-- 视觉上的逻辑坐标 (x=列, y=行) 需要通过此函数转换成 ws2812 串中的物理编号
|
||||
local SERPENTINE = true
|
||||
|
||||
--- 逻辑坐标 → 物理 LED 编号
|
||||
-- @param x 列 0..LED_W-1(从左到右)
|
||||
-- @param y 行 0..LED_H-1(从上到下)
|
||||
-- @return 物理 LED 编号 0..LED_COUNT-1
|
||||
function ws2812_xy(x, y)
|
||||
if SERPENTINE and (y % 2 == 1) then
|
||||
-- 奇数行走线方向相反,水平翻转
|
||||
x = LED_W - 1 - x
|
||||
end
|
||||
return y * LED_W + x
|
||||
end
|
||||
|
||||
-- ==================== 颜色工具 ====================
|
||||
|
||||
--- HSV 颜色空间转 24-bit RGB 整数
|
||||
-- @param h 色相 0~359(0=红, 120=绿, 240=蓝)
|
||||
-- @param s 饱和度 0~255(0=灰白, 255=纯色)
|
||||
-- @param v 明度 0~255(0=黑, 255=最亮)
|
||||
-- @return 24-bit RGB 整数,可直接传给 ws2812_set / ws2812.fill
|
||||
function ws2812_hsv2rgb(h, s, v)
|
||||
-- 标准 HSV→RGB 算法,参考 https://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
local c = v * s / 255 / 255 -- 色度(chroma)
|
||||
local x = c * (1 - math.abs((h / 60) % 2 - 1)) -- 中间色分量
|
||||
local m = v / 255 - c -- 明度匹配增量
|
||||
local r, g, b
|
||||
-- 根据色相所在的 60° 扇区决定 RGB 排列
|
||||
if h < 60 then r, g, b = c, x, 0
|
||||
elseif h < 120 then r, g, b = x, c, 0
|
||||
elseif h < 180 then r, g, b = 0, c, x
|
||||
elseif h < 240 then r, g, b = 0, x, c
|
||||
elseif h < 300 then r, g, b = x, 0, c
|
||||
else r, g, b = c, 0, x
|
||||
end
|
||||
-- 加上明度增量并打包成 0xRRGGBB
|
||||
return math.floor((r + m) * 255 + 0.5) * 65536
|
||||
+ math.floor((g + m) * 255 + 0.5) * 256
|
||||
+ math.floor((b + m) * 255 + 0.5)
|
||||
end
|
||||
|
||||
--- 直接用 RGB 三原色打包 24-bit 整数
|
||||
-- @param r 红 0~255
|
||||
-- @param g 绿 0~255
|
||||
-- @param b 蓝 0~255
|
||||
-- @return 24-bit RGB 整数
|
||||
function ws2812_rgb(r, g, b)
|
||||
return r * 65536 + g * 256 + b
|
||||
end
|
||||
|
||||
-- ==================== WS2812 句柄初始化 ====================
|
||||
-- ws2812.create(mode, count, pin): 创建 ws2812 句柄
|
||||
-- mode = ws2812.GPIO 表示使用普通 GPIO 外设驱动
|
||||
-- count = LED_COUNT 灯珠总数
|
||||
-- pin = LED_GPIO 数据引脚
|
||||
WS2812_LEDS = ws2812.create(ws2812.GPIO, LED_COUNT, LED_GPIO)
|
||||
if not WS2812_LEDS then
|
||||
log.error("ws2812_config", "ws2812.create 失败,请确认固件含 ws2812 组件")
|
||||
else
|
||||
-- 设置 WS2812 时序(单位 ns),这是本灯板实测稳定的参数:
|
||||
-- T0H=20 T0L=30 T1H=35 T1L=20 T_RESET=0 us
|
||||
-- 乱改可能导致颜色错乱或整板不亮
|
||||
ws2812.args(WS2812_LEDS, 20, 30, 35, 20, 0)
|
||||
|
||||
-- 第一次清屏:创建句柄后立即把缓冲区置 0
|
||||
-- 避免上电瞬间 DIN 引脚电气杂波被第一颗 LED 误识别为数据位并锁存(开机鬼影)
|
||||
for i = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, i, 0)
|
||||
end
|
||||
|
||||
-- 后台任务:50ms 后再发送一次真正的复位信号
|
||||
-- 此时电源和 DIN 已稳定,能可靠清除所有 LED 的随机锁存
|
||||
sys.taskInit(function()
|
||||
sys.wait(50)
|
||||
for i = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, i, 0)
|
||||
end
|
||||
ws2812.send(WS2812_LEDS)
|
||||
log.info("ws2812_config", "LED 已复位清零,共", LED_COUNT, "颗")
|
||||
end)
|
||||
end
|
||||
|
||||
-- ==================== 帧缓冲操作函数 ====================
|
||||
-- ws2812 的工作方式:先把每颗 LED 的颜色写入内部缓冲,再调用 send() 一次性刷新到灯板
|
||||
-- 下面这组函数都做了 nil 句柄保护,即使 ws2812.create 失败也不会崩溃
|
||||
|
||||
--- 按逻辑坐标点亮一颗 LED(写缓冲,不发送)
|
||||
-- @param x 列 0..LED_W-1
|
||||
-- @param y 行 0..LED_H-1
|
||||
-- @param color 24-bit RGB 整数(ws2812_hsv2rgb / ws2812_rgb 的返回值)
|
||||
function ws2812_set(x, y, color)
|
||||
if not WS2812_LEDS then return end
|
||||
-- 越界保护:不在灯板范围内的坐标静默忽略
|
||||
if x >= 0 and x < LED_W and y >= 0 and y < LED_H then
|
||||
ws2812.set(WS2812_LEDS, ws2812_xy(x, y), color)
|
||||
end
|
||||
end
|
||||
|
||||
--- 整片填充同一颜色并立即发送
|
||||
-- @param color 24-bit RGB 整数
|
||||
function ws2812_fill(color)
|
||||
if not WS2812_LEDS then return end
|
||||
for i = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, i, color)
|
||||
end
|
||||
ws2812.send(WS2812_LEDS)
|
||||
end
|
||||
|
||||
--- 整片清屏(写缓冲,不发送)
|
||||
-- 调用后必须再调用 ws2812_send() 才会真正熄灭 LED
|
||||
function ws2812_clear()
|
||||
if not WS2812_LEDS then return end
|
||||
for i = 0, LED_COUNT - 1 do
|
||||
ws2812.set(WS2812_LEDS, i, 0)
|
||||
end
|
||||
end
|
||||
|
||||
--- 发送当前缓冲区到灯板(刷新一帧)
|
||||
-- 所有 ws2812_set / ws2812_clear 都是写缓冲,必须调用此函数才会生效
|
||||
function ws2812_send()
|
||||
if not WS2812_LEDS then return end
|
||||
ws2812.send(WS2812_LEDS)
|
||||
end
|
||||
@@ -0,0 +1,359 @@
|
||||
--[[
|
||||
@module ws2812_fonts
|
||||
@summary WS2812 演示使用的字模数据与文本条带构建(中文 22×22 / 英文 22×11 / ASCII 5×7)
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
字模尺寸与灯板 22×22 强绑定,灯板尺寸非 22×22 时需重新生成字模。
|
||||
|
||||
本模块由 ws2812_scroll_task 调用,提供:
|
||||
WS2812_FONT5x7 5×7 ASCII 小字模(列优先,bit0=顶行),放大 2× 后渲染
|
||||
WS2812_FONT_CN 中文 22×22 字模(欢迎使用),行优先字符串
|
||||
WS2812_FONT_EN22 英文半宽 22×11 大字模(LuatOS),行优先字符串
|
||||
ws2812_build_strip(text) 将 UTF-8 文本打包为列掩码数组
|
||||
最终输出"列掩码数组" columns:每个元素是一个 22-bit 整数,
|
||||
第 y 位为 1 表示该列第 y 行的 LED 点亮。scroll_task 按列偏移遍历即可渲染。
|
||||
]]
|
||||
|
||||
-- ====== 5×7 ASCII 字模(列优先,bit0=顶行) ======
|
||||
-- 格式说明:
|
||||
-- 每个字符是一个含 5 个字节的数组,分别对应 5 列;
|
||||
-- 每个字节的 bit0~bit6 对应该列自顶向下的 7 行,
|
||||
-- 即 bit0=最顶行、bit6=最底行,置 1 表示该像素点亮。
|
||||
-- 渲染时会通过 expand_small_col 将 7 行放大为 14 行(每行翻倍),
|
||||
-- 再垂直居中下移 EN_VOFFSET_SMALL 排,最终得到约 14 像素高、10 像素宽的字符。
|
||||
-- 键名使用 ASCII 码(如 65 = 'A')。
|
||||
local WS2812_FONT5x7 = {
|
||||
[65] = {0x7E,0x09,0x09,0x09,0x7E}, -- A
|
||||
[66] = {0x7F,0x49,0x49,0x49,0x36}, -- B
|
||||
[67] = {0x3E,0x41,0x41,0x41,0x22}, -- C
|
||||
[68] = {0x7F,0x41,0x41,0x22,0x1C}, -- D
|
||||
[69] = {0x7F,0x49,0x49,0x49,0x41}, -- E
|
||||
[70] = {0x7F,0x09,0x09,0x09,0x01}, -- F
|
||||
[71] = {0x3E,0x49,0x49,0x49,0x7A}, -- G
|
||||
[72] = {0x7F,0x08,0x08,0x08,0x7F}, -- H
|
||||
[73] = {0x00,0x41,0x7F,0x41,0x00}, -- I
|
||||
[74] = {0x20,0x41,0x41,0x3F,0x00}, -- J
|
||||
[75] = {0x7F,0x08,0x14,0x22,0x41}, -- K
|
||||
[76] = {0x7F,0x40,0x40,0x40,0x40}, -- L
|
||||
[77] = {0x7F,0x02,0x04,0x02,0x7F}, -- M
|
||||
[78] = {0x7F,0x04,0x08,0x10,0x7F}, -- N
|
||||
[79] = {0x3E,0x41,0x41,0x41,0x3E}, -- O
|
||||
[80] = {0x7F,0x09,0x09,0x09,0x06}, -- P
|
||||
[81] = {0x3E,0x41,0x51,0x21,0x5E}, -- Q
|
||||
[82] = {0x7F,0x09,0x19,0x29,0x46}, -- R
|
||||
[83] = {0x46,0x49,0x49,0x49,0x31}, -- S
|
||||
[84] = {0x01,0x01,0x7F,0x01,0x01}, -- T
|
||||
[85] = {0x3F,0x40,0x40,0x40,0x3F}, -- U
|
||||
[86] = {0x1F,0x20,0x40,0x20,0x1F}, -- V
|
||||
[87] = {0x3F,0x40,0x38,0x40,0x3F}, -- W
|
||||
[88] = {0x63,0x14,0x08,0x14,0x63}, -- X
|
||||
[89] = {0x07,0x08,0x70,0x08,0x07}, -- Y
|
||||
[90] = {0x71,0x69,0x55,0x4D,0x47}, -- Z
|
||||
[97] = {0x78,0x14,0x14,0x14,0x78}, -- a
|
||||
[116] = {0x04,0x04,0x7F,0x44,0x44}, -- t
|
||||
[117] = {0x3C,0x40,0x40,0x40,0x7C}, -- u
|
||||
}
|
||||
|
||||
-- ====== 中文 22×22 字模(行优先字符串,# = 亮,. = 灭) ======
|
||||
-- 格式说明:
|
||||
-- 每个字符由 22 个字符串组成,每个字符串固定 22 个字符宽;
|
||||
-- 字符串数组顺序是行序(第 1 个字符串 = 最顶行),
|
||||
-- 字符串内从左到右是列序,'#' 表示该像素亮、'.' 表示灭。
|
||||
-- 渲染时按列扫描:col_mask(glyph, col, voffset) 对每一行取该列字符,
|
||||
-- 为 '#' 就把对应行位置 1,最终拼成一个 22-bit 的列掩码。
|
||||
-- 键名是 UTF-8 中文字符串(占 3 字节),build_strip 中用 string.sub 取出 3 字节作为键。
|
||||
local WS2812_FONT_CN = {
|
||||
["欢"] = {
|
||||
"......................",
|
||||
"............##........",
|
||||
"...........###........",
|
||||
"...........##.........",
|
||||
"..#######..##.........",
|
||||
"..#######..#########..",
|
||||
".......##.##########..",
|
||||
".......##.##......##..",
|
||||
"..###..#..##.##..##...",
|
||||
"...##.##.##..##.......",
|
||||
"....####.....##.......",
|
||||
"....###......##.......",
|
||||
".....###.....###......",
|
||||
"....####.....###......",
|
||||
"....#####...####......",
|
||||
"...###.##...##.##.....",
|
||||
"...##...##.###.###....",
|
||||
"..##......###....###..",
|
||||
".###.....###.....####.",
|
||||
"........###.......###.",
|
||||
"........##............",
|
||||
"......................",
|
||||
},
|
||||
["迎"] = {
|
||||
"......................",
|
||||
"...#.......#..........",
|
||||
"..##....############..",
|
||||
"..###..###...#######..",
|
||||
"...##..##....##...##..",
|
||||
"...##..##....##...##..",
|
||||
".......##....##...##..",
|
||||
".......##....##...##..",
|
||||
".####..##....##...##..",
|
||||
".####..##....##...##..",
|
||||
"...##..##....##...##..",
|
||||
"...##..##....##...##..",
|
||||
"...##..##....##...##..",
|
||||
"...##..##..#.##...##..",
|
||||
"...##..#####.##..###..",
|
||||
"...##..####..##.###...",
|
||||
"...##..##....##.......",
|
||||
"...##........##.......",
|
||||
"...###.......##.......",
|
||||
"..#######.............",
|
||||
".###..###############.",
|
||||
".##.......###########.",
|
||||
},
|
||||
["使"] = {
|
||||
"......................",
|
||||
".....#......##........",
|
||||
"....##......##........",
|
||||
"....##......#.........",
|
||||
"....##.##############.",
|
||||
"...##...############..",
|
||||
"...##........#........",
|
||||
"..###...############..",
|
||||
"..###...############..",
|
||||
".####...#....#....##..",
|
||||
".##.#...#....#....##..",
|
||||
"....#...############..",
|
||||
"....#...###########...",
|
||||
"....#...##..##........",
|
||||
"....#...###.##........",
|
||||
"....#....#####........",
|
||||
"....#.....####........",
|
||||
"....#......###........",
|
||||
"....#....########.....",
|
||||
"....#...###...#######.",
|
||||
"....#..###.......###..",
|
||||
"......................",
|
||||
},
|
||||
["用"] = {
|
||||
"......................",
|
||||
"......................",
|
||||
"...################...",
|
||||
"...################...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"...################...",
|
||||
"...################...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"...################...",
|
||||
"...################...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"...##.....##.....##...",
|
||||
"..##......##.....##...",
|
||||
"..##......##....###...",
|
||||
".###......##...####...",
|
||||
"..#...................",
|
||||
"......................",
|
||||
},
|
||||
}
|
||||
|
||||
-- ====== 英文半宽大字模 22 行 × 11 列(行优先字符串) ======
|
||||
-- 格式说明:
|
||||
-- 每个字符由 22 个字符串组成,每个字符串宽 11 字符(半宽,约为中文的一半);
|
||||
-- 行序、'#'/'.' 含义与中文字模一致。
|
||||
-- 渲染时通过 col_mask(big, col, 2) 扫描每一列,voffset=2 下移 2 排,
|
||||
-- 使其与 22×22 中文在视觉基线上对齐(英文上下留白较多)。
|
||||
-- 键名使用 ASCII 码(如 76 = 'L'),仅包含本演示用到的 L/u/a/t/O/S。
|
||||
local WS2812_FONT_EN22 = {
|
||||
[76] = { -- L
|
||||
"...........","...........","..##.......","..##.......","..##.......",
|
||||
"..##.......","..##.......","..##.......","..##.......","..##.......",
|
||||
"..##.......","..##.......","..##.......","..##.......","..########.",
|
||||
"..########.","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
[117] = { -- u
|
||||
"...........","...........","...........","...........","...........",
|
||||
".##.....##.",".##.....##.",".##.....##.",".##.....##.",".##.....##.",
|
||||
".##.....##.",".##.....##.",".##....###.",".###..####.","..########.",
|
||||
"..#####.##.","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
[97] = { -- a
|
||||
"...........","...........","...........","...........","...........",
|
||||
"...#####...","..#######..","..#....###.","........##.","...#######.",
|
||||
".#########.",".###....##.",".##.....##.",".###..####.",".#########.",
|
||||
"..#####.##.","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
[116] = { -- t
|
||||
"...........","...........","...##......","...##......","...##......",
|
||||
"##########.","##########.","...##......","...##......","...##......",
|
||||
"...##......","...##......","...##......","...###.....","....######.",
|
||||
".....#####.","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
[79] = { -- O
|
||||
"...........","...........","...#####...","..########.",".###...###.",
|
||||
".##.....###","###......##","##.......##","##.......##","##.......##",
|
||||
"##.......##","##.......##","###.....###",".###...###.",".########..",
|
||||
"...#####...","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
[83] = { -- S
|
||||
"...........","...........","...######..","..#######..",".###....#..",
|
||||
".##........",".###.......",".####......","..#####....","....#####..",
|
||||
".......###.","........##.","........##.",".#.....###.",".#########.",
|
||||
"..######...","...........","...........","...........","...........",
|
||||
"...........","...........",
|
||||
},
|
||||
}
|
||||
|
||||
-- ====== 条带尺寸常量 ======
|
||||
|
||||
-- 条带虚拟高度(与 ws2812_config 中的 LED_H 对齐,均为 22 行)。
|
||||
-- 列掩码最多使用 bit0~bit21,超过 STRIP_H 的位会被 col_mask 丢弃。
|
||||
local STRIP_H = 22
|
||||
|
||||
-- 5×7 小字模放大 2× 后垂直居中下移的排数。
|
||||
-- 计算方式:5×7 字形高 7 像素,放大 2× 后高 14 像素;
|
||||
-- 在 22 像素高的条带里垂直居中,需要下移 (22-14)/2 = 4 排。
|
||||
local EN_VOFFSET_SMALL = 4 -- 5×7 英文放大 2× 后下移居中
|
||||
|
||||
-- ====== 5×7 列字节 → 放大 2× 后的位掩码 ======
|
||||
|
||||
--- 将 5×7 字模的单列字节(7 像素)放大为 14 像素高的位掩码
|
||||
-- 放大规则:每个原始像素在垂直方向翻倍占两行(2× 缩放)。
|
||||
-- 例:原 bit r 为 1,则在结果掩码中置位 (r*2+voff) 和 (r*2+voff+1) 两个 bit。
|
||||
-- 使用 (3 << ((r*2)+voff)):3 的二进制是 11,左移后恰好是相邻两个 1。
|
||||
-- @param byte integer 5×7 字模某一列的字节(bit0=顶行)
|
||||
-- @return integer 放大并垂直居中后的 22-bit 列掩码
|
||||
local function expand_small_col(byte)
|
||||
local mask = 0
|
||||
-- 遍历 7 行
|
||||
for r = 0, 6 do
|
||||
-- (byte >> r) & 1 取出第 r 位
|
||||
if (byte >> r) & 1 == 1 then
|
||||
-- 3 = 二进制 11,左移 (r*2)+EN_VOFFSET_SMALL 位:
|
||||
-- 原始第 r 像素 → 放大后占第 (r*2+voff) 和 (r*2+voff+1) 两排
|
||||
-- |= 把这两个 bit 合并进总掩码
|
||||
mask = mask | (3 << ((r * 2) + EN_VOFFSET_SMALL))
|
||||
end
|
||||
end
|
||||
return mask
|
||||
end
|
||||
|
||||
-- ====== 行优先字模 → 单列位掩码 ======
|
||||
|
||||
--- 从行优先字符串字模中取出某一列,拼成列掩码
|
||||
-- 双向越界保护:r + off 可能小于 0(voffset 为负、字形靠上)
|
||||
-- 或大于等于 STRIP_H(字形靠下超出 22 行),这两种情况都跳过,
|
||||
-- 避免位运算产生无效 bit 或写到条带范围外。
|
||||
-- @param glyph table 字模表(22 个字符串,行序)
|
||||
-- @param col integer 要取的列号(0 起始)
|
||||
-- @param voffset integer 垂直偏移(可正可负,nil 视为 0)
|
||||
-- @return integer 22-bit 列掩码
|
||||
local function col_mask(glyph, col, voffset)
|
||||
local mask = 0
|
||||
local h = #glyph
|
||||
local off = voffset or 0
|
||||
-- 遍历字形的每一行
|
||||
for r = 0, h - 1 do
|
||||
-- 取出第 r 行、第 col 列的字符;Lua 字符串索引从 1 开始故 +1
|
||||
local ch = string.sub(glyph[r + 1], col + 1, col + 1)
|
||||
if ch == "#" then
|
||||
-- 该像素亮:计算它落在条带中的实际行号
|
||||
local bit = r + off
|
||||
-- 双向越界保护:bit>=0 防止负偏移(左移负数位未定义),
|
||||
-- bit<STRIP_H 防止超出 22 行虚拟高度
|
||||
if bit >= 0 and bit < STRIP_H then
|
||||
mask = mask | (1 << bit)
|
||||
end
|
||||
end
|
||||
end
|
||||
return mask
|
||||
end
|
||||
|
||||
-- ====== UTF-8 文本 → 列掩码数组 ======
|
||||
|
||||
--- 将 UTF-8 文本打包成滚动条带用的列掩码数组
|
||||
-- UTF-8 编码要点:
|
||||
-- * ASCII 字符(0x00~0x7F)占 1 字节
|
||||
-- * 0x80 及以上的字节是多字节序列的起始或后续字节
|
||||
-- * 常用中文在 UTF-8 中占 3 字节(1110xxxx 10xxxxxx 10xxxxxx)
|
||||
-- 为什么用 while 而不是 for:
|
||||
-- Lua 数值 for 循环在循环开始时就固化了起始/终止/步长,
|
||||
-- 循环体内对 i 的赋值(如 i=i+3)不会改变下一次迭代值,
|
||||
-- 无法实现"中文跳 3 字节、ASCII 走 1 字节"的变长步进;
|
||||
-- 而 while + 手动 i=i+n 可以精确控制每次前进的字节数。
|
||||
-- ASCII 字符的字模优先级:
|
||||
-- 1) 先查 WS2812_FONT_EN22(22×11 大尺寸英文字模,最清晰)
|
||||
-- 2) 查不到再回退到 WS2812_FONT5x7(5×7 小字模,放大 2× 后使用)
|
||||
-- @param text string UTF-8 编码的文本
|
||||
-- @return table 列掩码数组(每元素是 22-bit 整数,含字间距空白列)
|
||||
function ws2812_build_strip(text)
|
||||
local columns = {}
|
||||
local i = 1
|
||||
-- while 手动步进:i 在中文分支 +3,ASCII 分支 +1
|
||||
while i <= #text do
|
||||
local byte = string.byte(text, i)
|
||||
if byte >= 0x80 then
|
||||
-- ====== 多字节分支:按中文 3 字节处理 ======
|
||||
-- 取当前位置起连续 3 个字节,得到一个完整的 UTF-8 中文字符串,
|
||||
-- 直接用作 WS2812_FONT_CN 的键
|
||||
local full = string.sub(text, i, i + 2)
|
||||
local glyph = WS2812_FONT_CN[full]
|
||||
-- 中文字模标准宽度 22 列;查不到字模时也保留 22 列空白占位,
|
||||
-- 避免后续列错位(不会因为缺字把后面的字符挤到前面)
|
||||
local gw = glyph and #glyph[1] or 22
|
||||
for c = 0, gw - 1 do
|
||||
-- voffset=0:中文 22×22 正好占满条带高度,无需垂直偏移
|
||||
columns[#columns + 1] = glyph and col_mask(glyph, c, 0) or 0
|
||||
end
|
||||
-- 中文占 3 字节,前进 3
|
||||
i = i + 3
|
||||
else
|
||||
-- ====== ASCII 分支:1 字节 ======
|
||||
-- 优先尝试 22×11 大字模
|
||||
local big = WS2812_FONT_EN22[byte]
|
||||
if big then
|
||||
local ew = #big[1]
|
||||
for c = 0, ew - 1 do
|
||||
-- voffset=2:英文字模整体下移 2 排,
|
||||
-- 让英文视觉基线与旁边的中文对齐(英文字形上下留白较多)
|
||||
columns[#columns + 1] = col_mask(big, c, 2)
|
||||
end
|
||||
else
|
||||
-- 回退到 5×7 小字模:放大 2× 后渲染
|
||||
local glyph = WS2812_FONT5x7[byte]
|
||||
if glyph then
|
||||
-- 5×7 共 5 列
|
||||
for c = 0, 4 do
|
||||
local m = expand_small_col(glyph[c + 1])
|
||||
-- 水平也放大 2×:每列重复写两次,得到 10 像素宽
|
||||
columns[#columns + 1] = m
|
||||
columns[#columns + 1] = m
|
||||
end
|
||||
end
|
||||
-- 若 5×7 也没有该字符,则什么都不输出(不占列、字符直接消失)
|
||||
end
|
||||
-- ASCII 占 1 字节,前进一步
|
||||
i = i + 1
|
||||
end
|
||||
-- ====== 字间距:每个字符后追加 2 列空白 ======
|
||||
-- 0 表示该列没有任何像素点亮,形成字符间的可见间隔,
|
||||
-- 避免中英文挤在一起难以辨认
|
||||
columns[#columns + 1] = 0
|
||||
columns[#columns + 1] = 0
|
||||
end
|
||||
return columns
|
||||
end
|
||||
|
||||
-- 导出条带高度给 scroll_task 使用(渲染时遍历 0~WS2812_STRIP_H-1 行)
|
||||
WS2812_STRIP_H = STRIP_H
|
||||
@@ -0,0 +1,48 @@
|
||||
--[[
|
||||
@module ws2812_rainbow_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 彩虹渐变效果
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
色相公式依赖 LED_W+LED_H,灯板尺寸非 22×22 时会自动适配,但效果观感可能变化。
|
||||
|
||||
本文件为 WS2812 22×22 点阵演示彩虹渐变效果的代码示例,核心业务逻辑为:
|
||||
1. 遍历 22×22 每一颗灯珠
|
||||
2. 根据 (x+y) 坐标与当前帧数计算 HSV 色相
|
||||
3. 刷新整片,形成色彩流动的彩虹效果
|
||||
]]
|
||||
|
||||
-- 定义任务函数
|
||||
local function rainbow_task()
|
||||
-- 防御性检查:若硬件初始化失败或 WS2812_LEDS 未生成,直接退出本任务
|
||||
if not WS2812_LEDS then return end
|
||||
-- 等待 100ms,让 ws2812_config 完成开机复位与显存清零,避免首帧花屏
|
||||
sys.wait(100)
|
||||
-- steps:一轮完整色彩循环所需的帧数(120 帧后色相回到起点)
|
||||
local steps = 120
|
||||
while 1 do
|
||||
-- 逐帧推进,frame 作为全局色相偏移量
|
||||
for frame = 0, steps - 1 do
|
||||
-- 按行扫描每个灯珠
|
||||
for y = 0, LED_H - 1 do
|
||||
for x = 0, LED_W - 1 do
|
||||
-- 色相 h 由两部分叠加:
|
||||
-- (x+y)*360/(LED_W+LED_H):空间项,使对角线上同色,形成斜向彩虹条纹
|
||||
-- frame*3:时间项,每帧色相推进 3°,整轮 120 帧正好推进 360°
|
||||
-- 取模 360 保证色相落在 0~359° 区间
|
||||
local h = ((x + y) * 360 / (LED_W + LED_H) + frame * 3) % 360
|
||||
-- 饱和度固定 255(最鲜艳),亮度使用配置值,写入对应坐标
|
||||
ws2812_set(x, y, ws2812_hsv2rgb(h, 255, WS2812_CFG.brightness))
|
||||
end
|
||||
end
|
||||
-- 整帧填充完毕后一次性推送至灯板
|
||||
ws2812_send()
|
||||
-- 按配置的帧间隔延时,控制动画速度
|
||||
sys.wait(WS2812_CFG.speed_ms)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 执行彩虹任务函数
|
||||
sys.taskInit(rainbow_task)
|
||||
@@ -0,0 +1,71 @@
|
||||
--[[
|
||||
@module ws2812_rect_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 矩形收缩扩散效果
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
中心点 (11,11) 按 22×22 几何中心计算,灯板尺寸非 22×22 时需同步修改。
|
||||
|
||||
本文件为 WS2812 22×22 点阵演示矩形收缩扩散效果的代码示例,核心业务逻辑为:
|
||||
1. 从中心点开始画矩形框
|
||||
2. 矩形半径由小到大、再由大到小,形成呼吸般的扩散/收缩
|
||||
3. 颜色随半径变化
|
||||
]]
|
||||
|
||||
-- 定义任务函数
|
||||
local function rect_task()
|
||||
-- 防御性检查:硬件未就绪直接退出,避免后续访问空显存
|
||||
if not WS2812_LEDS then return end
|
||||
-- 等待 100ms,让 config 完成开机复位清零,防止首帧花屏
|
||||
sys.wait(100)
|
||||
-- max_r:矩形最大"半径"(半边长),取宽高较小值的一半,保证矩形不超出点阵
|
||||
-- math.floor 取整是为了让 r 的 numeric for 走整数步长,避免浮点累计误差
|
||||
local max_r = math.floor(math.min(LED_W, LED_H) / 2)
|
||||
while 1 do
|
||||
-- 两阶段动画:
|
||||
-- phase = 0:扩散(半径 0 → max_r)
|
||||
-- phase = 1:收缩(半径 max_r → 0),通过 rr 反向映射实现
|
||||
for phase = 0, 1 do
|
||||
-- r 作为统一推进变量,始终从 0 增长到 max_r
|
||||
for r = 0, max_r do
|
||||
-- 每帧清屏,只保留当前半径对应的矩形框
|
||||
ws2812_clear()
|
||||
-- 实际渲染半径:扩散阶段用 r,收缩阶段用 max_r - r,从而形成呼吸节奏
|
||||
local rr = (phase == 0) and r or (max_r - r)
|
||||
-- 中心点 (11,11):22×22 点阵的几何中心(22/2 = 11)
|
||||
local cx = math.floor(LED_W / 2)
|
||||
local cy = math.floor(LED_H / 2)
|
||||
-- 矩形四条边的边界坐标:
|
||||
-- x0/y0:左上,x1/y1:右下;-1 是因为 rr 表示"半边长",
|
||||
-- 边长为 2*rr 时右/下边界需要减 1,避免单边多出一列/一行
|
||||
local x0 = cx - rr
|
||||
local x1 = cx + rr - 1
|
||||
local y0 = cy - rr
|
||||
local y1 = cy + rr - 1
|
||||
-- 色相随半径变化:半径每增加 1,色相推进 20°,取模 360 实现循环变色
|
||||
local h = (rr * 20) % 360
|
||||
-- 预计算当前帧颜色,四条边复用同一个颜色值
|
||||
local c = ws2812_hsv2rgb(h, 255, WS2812_CFG.brightness)
|
||||
-- 绘制上下两条水平边:每个 x 坐标在 y0 和 y1 上各点亮一颗
|
||||
for x = x0, x1 do
|
||||
ws2812_set(x, y0, c)
|
||||
ws2812_set(x, y1, c)
|
||||
end
|
||||
-- 绘制左右两条垂直边:每个 y 坐标在 x0 和 x1 上各点亮一颗
|
||||
-- 四个角点会被重复 set,但同色写入无副作用
|
||||
for y = y0, y1 do
|
||||
ws2812_set(x0, y, c)
|
||||
ws2812_set(x1, y, c)
|
||||
end
|
||||
-- 整帧刷新
|
||||
ws2812_send()
|
||||
-- 速度系数 *3:矩形呼吸需要沉稳节奏,比基础帧间隔慢两倍,视觉更柔和
|
||||
sys.wait(WS2812_CFG.speed_ms * 3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 执行矩形任务函数
|
||||
sys.taskInit(rect_task)
|
||||
@@ -0,0 +1,173 @@
|
||||
--[[
|
||||
@module ws2812_scroll_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 滚动文字效果(欢迎使用LuatOS)
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
字模尺寸为 22 行高,与本灯板 22×22 强绑定;灯板高度非 22 行时需重新生成字模。
|
||||
|
||||
本文件为 WS2812 22×22 点阵演示横向滚动文字的代码示例,核心业务逻辑为:
|
||||
1. 中文 22×22 字模"欢迎使用" + 英文半宽 22×11 字模"LuatOS"
|
||||
2. 文字从右侧滑入、左侧滑出,循环滚动
|
||||
3. 每播完一轮自动切换颜色(6 色区随机,且相邻轮次不落在同一色区)
|
||||
4. 可通过 WS2812_CFG.speed_ms 调速,WS2812_CFG.brightness 调亮度
|
||||
|
||||
整体流程:
|
||||
系统启动 → 加载字模/配置 → 构建整条文本的"列掩码"缓冲 →
|
||||
进入 while 1 循环:每轮 pick_color 选一种与上轮不同色区的颜色 →
|
||||
offset 从 -LED_W 走到 strip_w-1,逐帧渲染并等待 speed_ms →
|
||||
一轮结束后重新选色再播。
|
||||
]]
|
||||
|
||||
-- ====== 模块依赖与配置兜底 ======
|
||||
|
||||
-- 加载字模与条带构建工具(依赖 ws2812_config 已初始化 WS2812_LEDS)
|
||||
require "ws2812_config"
|
||||
require "ws2812_fonts"
|
||||
|
||||
-- 防止 config 未正确加载时的兜底默认值
|
||||
-- brightness:亮度(0~255,送入 HSV 的 V 通道)
|
||||
-- speed_ms:每帧滚动间隔,毫秒;数值越小滚动越快
|
||||
if not WS2812_CFG then
|
||||
WS2812_CFG = { brightness = 50, speed_ms = 80 }
|
||||
end
|
||||
if not WS2812_CFG.speed_ms then WS2812_CFG.speed_ms = 80 end
|
||||
if not WS2812_CFG.brightness then WS2812_CFG.brightness = 50 end
|
||||
|
||||
-- ====== 滚动文本 ======
|
||||
|
||||
-- 滚动文本(UTF-8 编码);构建条带时按字节解析,
|
||||
-- 中文走 3 字节分支、ASCII 走 1 字节分支,详见 ws2812_fonts.lua
|
||||
local SCROLL_TEXT = "欢迎使用LuatOS"
|
||||
|
||||
-- ====== 颜色分区(6 色区互斥) ======
|
||||
|
||||
-- 将 HSV 色相环 0~359 切成 6 段,每段 60°,分别对应红/黄/绿/青/蓝/紫。
|
||||
-- 互斥的目的:相邻两轮如果落在相邻色区(例如红→黄、绿→青),
|
||||
-- 视觉上颜色变化不明显,观感像"没换色"。
|
||||
-- 因此 pick_color 用 repeat...until 强制抽到与 prev_zone 不同的区,
|
||||
-- 保证每一轮的颜色都与上一轮有显著色差。
|
||||
local COLOR_ZONES = {
|
||||
{ 0, 59}, -- 红
|
||||
{ 60, 119}, -- 黄
|
||||
{120, 179}, -- 绿
|
||||
{180, 239}, -- 青
|
||||
{240, 299}, -- 蓝
|
||||
{300, 359}, -- 紫
|
||||
}
|
||||
|
||||
-- ====== LCG 伪随机数发生器 ======
|
||||
|
||||
-- 采用 glibc 风格的线性同余发生器(LCG, Linear Congruential Generator):
|
||||
-- seed = (seed * 1103515245 + 12345) mod 2^31
|
||||
-- 这是 C 语言 rand() 的经典公式,分布足够均匀,且不依赖 Lua 的 math.random
|
||||
-- (嵌入式固件中 math.random 可能未播种或不可用)。
|
||||
-- 种子取 os.time() 对 2^31-1 取模,保证每次上电随机序列不同。
|
||||
local lcg_seed = os.time() % 2147483647
|
||||
|
||||
--- 产出下一个 0~32767 范围的伪随机整数
|
||||
-- @return integer 伪随机数(0~32767)
|
||||
local function lcg_next()
|
||||
-- 递推:固定乘数 1103515245、增量 12345、模数 2^31
|
||||
lcg_seed = (lcg_seed * 1103515245 + 12345) % 2147483648
|
||||
-- 取高 15 位(除以 65536 后再 mod 32768),低位随机性较差故舍弃
|
||||
return math.floor(lcg_seed / 65536) % 32768
|
||||
end
|
||||
|
||||
-- 记录上一轮使用的色区索引,初值 -1 保证第一轮必能抽到任意色区
|
||||
local prev_zone = -1
|
||||
|
||||
--- 从与上一轮不同的色区中随机选一种颜色
|
||||
-- @return table {r,g,b},经 ws2812_hsv2rgb 转换后的 RGB 三元组
|
||||
local function pick_color()
|
||||
local idx
|
||||
-- repeat...until:反复抽色区,直到抽到的索引与 prev_zone 不同,
|
||||
-- 从而避免相邻两轮落在同色区导致视觉颜色相近。
|
||||
repeat
|
||||
idx = lcg_next() % #COLOR_ZONES
|
||||
until idx ~= prev_zone
|
||||
prev_zone = idx
|
||||
|
||||
-- 从选中的色区中再随机取一个具体色相 h,S=255(最饱和),V=亮度配置
|
||||
local z = COLOR_ZONES[idx + 1]
|
||||
local h = z[1] + lcg_next() % (z[2] - z[1] + 1)
|
||||
return ws2812_hsv2rgb(h, 255, WS2812_CFG.brightness)
|
||||
end
|
||||
|
||||
-- ====== 单帧渲染 ======
|
||||
|
||||
--- 将整条文本条带按当前 offset 渲染到 WS2812 并发送
|
||||
-- columns 是"列掩码"数组:每一个元素是一个 22-bit 整数,
|
||||
-- 第 y 位为 1 表示该列第 y 行的 LED 需要点亮。
|
||||
-- 这样一整行只需一次按位与即可判断是否亮灯,渲染效率高。
|
||||
-- @param columns table 由 ws2812_build_strip 构建的列位数组
|
||||
-- @param offset integer 文本相对屏幕左边缘的水平偏移(负数表示文本还在屏幕右侧外)
|
||||
-- @param color table {r,g,b} 当前轮次统一使用的颜色
|
||||
local function render_frame(columns, offset, color)
|
||||
ws2812_clear()
|
||||
-- 从上到下遍历 22 行
|
||||
for y = 0, WS2812_STRIP_H - 1 do
|
||||
-- bit = 1<<y:构造当前行的位掩码。
|
||||
-- 列掩码第 y 位为 1 时,(columns[c] & bit) ~= 0 即表示该位置亮。
|
||||
local bit = 1 << y
|
||||
-- 从左到右遍历屏幕的 LED_W 列
|
||||
for x = 0, LED_W - 1 do
|
||||
-- col_idx = x + offset + 1
|
||||
-- x :屏幕列号(0 起始)
|
||||
-- offset :文本滚动偏移
|
||||
-- +1 :Lua 数组从 1 起始,columns[1] 对应文本第 1 列
|
||||
-- 含义:屏幕第 x 列当前对应文本的第 (x+offset) 列。
|
||||
local col_idx = x + offset + 1 -- Lua 数组 1 起始
|
||||
-- 双向越界保护:offset 为负或文本已滚出左侧时,col_idx 会落在数组外
|
||||
if col_idx >= 1 and col_idx <= #columns then
|
||||
if (columns[col_idx] & bit) ~= 0 then
|
||||
ws2812_set(x, y, color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
ws2812_send()
|
||||
end
|
||||
|
||||
-- ====== 滚动任务主循环 ======
|
||||
|
||||
--- 滚动文字任务函数(由 sys.taskInit 启动为协程)
|
||||
local function scroll_task()
|
||||
-- WS2812_LEDS 未初始化说明硬件未就绪,直接退出避免空指针
|
||||
if not WS2812_LEDS then return end
|
||||
|
||||
-- 等待 config 的开机复位清零(50ms)完成,避免首帧与复位冲突
|
||||
sys.wait(100)
|
||||
|
||||
-- 一次性把整段文本展开成列掩码数组(包含字间距空白列),
|
||||
-- strip_w 即整条文本的总列数,后面滚动范围由它决定
|
||||
local columns = ws2812_build_strip(SCROLL_TEXT)
|
||||
local strip_w = #columns
|
||||
log.info("scroll", "文本:", SCROLL_TEXT, "条带宽度:", strip_w)
|
||||
|
||||
while 1 do
|
||||
-- 每轮重新 pick_color:一轮完整滚动结束后换一种颜色再播,
|
||||
-- 让画面有"翻篇"感;repeat...until 已保证与上轮色区不同
|
||||
local color = pick_color()
|
||||
|
||||
-- 滚动循环:
|
||||
-- offset = -LED_W 起始:文本整体在屏幕右侧之外,屏幕全黑(右侧滑入前一刻)
|
||||
-- offset = -LED_W+1 开始:文本第一列从屏幕最右列 (x=LED_W-1) 出现
|
||||
-- offset = 0 文本首列正好对齐屏幕首列
|
||||
-- offset = strip_w-1 终值:文本最后一列刚划过屏幕第 0 列(左侧滑出完成)
|
||||
-- 循环结束即整条文本完全离开屏幕,随后 while 1 重新选色再开一轮。
|
||||
for offset = -LED_W, strip_w - 1 do
|
||||
render_frame(columns, offset, color)
|
||||
-- 每帧之间等待 speed_ms 毫秒,让出 CPU 给其他协程,
|
||||
-- 同时控制滚动速度:值越小滚动越快、越流畅;值越大越慢、越卡顿
|
||||
sys.wait(WS2812_CFG.speed_ms)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ====== 任务启动 ======
|
||||
|
||||
-- 执行滚动文字任务函数:sys.taskInit 将其作为 LuatOS 协程调度,
|
||||
-- 内部 sys.wait 不会阻塞系统其他任务
|
||||
sys.taskInit(scroll_task)
|
||||
@@ -0,0 +1,54 @@
|
||||
--[[
|
||||
@module ws2812_snake_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 蛇形扫描效果
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
本任务按物理 LED 编号顺序扫描,灯板尺寸非 22×22 时请确认蛇形走线方向与本灯板一致。
|
||||
|
||||
本文件为 WS2812 22×22 点阵演示蛇形扫描效果的代码示例,核心业务逻辑为:
|
||||
1. 一颗带拖尾的光点按物理 LED 编号顺序跑动
|
||||
2. 由于灯板蛇形走线,视觉上呈"S 形"逐行扫描
|
||||
3. 拖尾 6 颗,颜色随位置渐变
|
||||
]]
|
||||
|
||||
-- 定义任务函数
|
||||
local function snake_task()
|
||||
-- 防御性检查:WS2812_LEDS 不存在说明硬件未就绪,直接退出避免后续 ws2812.set 报错
|
||||
if not WS2812_LEDS then return end
|
||||
-- 等待 100ms,让 config 完成开机复位和显存清零
|
||||
sys.wait(100)
|
||||
-- trail:光点拖尾长度(含光点本身共 7 个位置,t 从 0 到 6)
|
||||
local trail = 6
|
||||
while 1 do
|
||||
-- i 是光点当前"虚拟头位置",多跑 trail 个位置,让光点完全滑出后再循环,收尾更自然
|
||||
for i = 0, LED_COUNT + trail do
|
||||
-- 每帧先清空,再重绘拖尾,避免残影叠加
|
||||
ws2812_clear()
|
||||
-- 依次绘制拖尾上的每一颗:t=0 是头部(最亮),t=trail 是尾部(最暗)
|
||||
for t = 0, trail do
|
||||
-- idx = i - t:头部位置往回倒 t 个索引,得到第 t 节拖尾的物理 LED 编号
|
||||
local idx = i - t
|
||||
-- 越界保护:光点进入/滑出边界时,拖尾可能落在 0~LED_COUNT-1 之外
|
||||
if idx >= 0 and idx < LED_COUNT then
|
||||
-- fade:线性渐暗系数,t=0 时为 1(全亮),t=trail 时为 0(熄灭)
|
||||
local fade = (trail - t) / trail
|
||||
-- 根据渐暗系数缩放配置亮度,得到该节拖尾的实际明度
|
||||
local v = math.floor(WS2812_CFG.brightness * fade)
|
||||
-- 色相按物理 LED 编号均匀分布在 0~360°,让蛇身呈现彩虹渐变
|
||||
local h = (idx * 360 / LED_COUNT) % 360
|
||||
-- 直接调用底层 ws2812.set 写入物理索引(本效果按编号顺序而非坐标)
|
||||
ws2812.set(WS2812_LEDS, idx, ws2812_hsv2rgb(h, 255, v))
|
||||
end
|
||||
end
|
||||
-- 整帧刷新
|
||||
ws2812_send()
|
||||
-- 速度系数 *2:蛇形需要明显的移动节奏感,比基础帧间隔放慢一倍更耐看
|
||||
sys.wait(WS2812_CFG.speed_ms * 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 执行蛇形扫描任务函数
|
||||
sys.taskInit(snake_task)
|
||||
@@ -0,0 +1,47 @@
|
||||
--[[
|
||||
@module ws2812_sparkle_task
|
||||
@summary Air1780P/H/HV 演示 WS2812 随机星点闪烁效果
|
||||
@version 1.0
|
||||
@date 2026.08.17
|
||||
@usage
|
||||
适用产品:合宙 Air1780P / Air1780H / Air1780HV。
|
||||
星点数量和色相范围通过 LED_W/LED_H 计算,灯板尺寸非 22×22 时会自动适配。
|
||||
|
||||
本文件为 WS2812 22×22 点阵演示随机星点闪烁效果的代码示例,核心业务逻辑为:
|
||||
1. 每帧随机点亮 30 颗灯珠
|
||||
2. 每颗灯珠使用随机色相
|
||||
3. 形成类似星空闪烁的视觉效果
|
||||
]]
|
||||
|
||||
-- 定义任务函数
|
||||
local function sparkle_task()
|
||||
-- 防御性检查:显存未初始化则直接返回,防止后续写显存时空引用
|
||||
if not WS2812_LEDS then return end
|
||||
-- 等待 100ms,让 config 完成开机复位清零,避免首帧残留
|
||||
sys.wait(100)
|
||||
while 1 do
|
||||
-- 外层循环:一轮包含 100 帧,随后重新开始(数学上无差别,仅用于划分"一轮")
|
||||
for _ = 1, 100 do
|
||||
-- 每帧先清空整片,让上一帧星点全部熄灭,形成"闪烁"而非"叠加"
|
||||
ws2812_clear()
|
||||
-- 每帧随机点亮 30 颗灯珠(约占 484 颗总数的 6%,稀疏度适中)
|
||||
for _ = 1, 30 do
|
||||
-- x 随机范围 [0, LED_W-1],即 0~21
|
||||
local x = math.random(0, LED_W - 1)
|
||||
-- y 随机范围 [0, LED_H-1],即 0~21
|
||||
local y = math.random(0, LED_H - 1)
|
||||
-- 色相 h 随机范围 [0, 359],HSV 全色域取色
|
||||
local h = math.random(0, 359)
|
||||
-- 写入该坐标;多次命中同一坐标时后者覆盖前者,符合随机闪烁观感
|
||||
ws2812_set(x, y, ws2812_hsv2rgb(h, 255, WS2812_CFG.brightness))
|
||||
end
|
||||
-- 刷新显示
|
||||
ws2812_send()
|
||||
-- 帧间隔在基础速度上放慢一倍,让星点切换不至于过快刺眼
|
||||
sys.wait(WS2812_CFG.speed_ms * 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- 执行星点闪烁任务函数
|
||||
sys.taskInit(sparkle_task)
|
||||
Reference in New Issue
Block a user