Merge pull request 'Dev qing' (#12) from dev_qing into main
Reviewed-on: qing/micro_servers#12
This commit is contained in:
commit
bcd49f0e81
133 files changed
+43745
-523
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
+13452
File diff suppressed because it is too large.
Load diff
Binary file not shown.
+1026
File diff suppressed because it is too large.
Load diff
+2476
File diff suppressed because it is too large.
Load diff
BIN
Binary file not shown.
+2698
File diff suppressed because it is too large.
Load diff
BIN
Binary file not shown.
+2809
File diff suppressed because it is too large.
Load diff
+2834
File diff suppressed because it is too large.
Load diff
@@ -18,25 +18,24 @@
|
||||
│ waf │ │ gateway │ │ static │ │ dnfs │ │ db │ │ ai_review │
|
||||
│ WAF 网关 │ │ 应用层网关 │ │前端托管│ │ 树状菜单 │ │ 通用数据库 │ │ AI内容审核 │
|
||||
│ :443 (入口) │ │ :8443 (内部) │ │ :9200 │ │ :8006 │ │ :8005 │ │ :8007 │
|
||||
│ coraza+反代 │ │ 鉴权 + 反代 │ │静态/媒体│ │ 节点CRUD │ │ SQL+日志收集 │ │ DeepSeek+RAG │
|
||||
└──────┬───────┘ └──────┬───────┘ └────────┘ └──────┬───────┘ └──────────────┘ └──────────────┘
|
||||
│ HTTPS终止 │ 反向代理 │ 调用
|
||||
└──────────────────►│ ├──────────► db:8005
|
||||
├────────────────► static:9200
|
||||
└────────────────► dnfs:8006 ──► ai_review:8007
|
||||
|
||||
日志链路: waf / gateway / static / dnfs / ai_review / captcha ──(log_client)──► db:8005/log/ingest
|
||||
│ coraza+反代 │ │ 鉴权 + 反代 │ │静态/媒体│ │ 节点CRUD │ │ pymsg/TCP │ │ DeepSeek+RAG │
|
||||
└──────┬───────┘ └──────┬───────┘ └───┬────┘ └──────┬───────┘ └──────▲───────┘ └──────▲───────┘
|
||||
│ HTTPS终止 │ 反向代理 │ 代理笔记请求 │ pymsg │ pymsg │ HTTP
|
||||
└──────────────────►│ ├─────────────►│─────────────────┘ │
|
||||
├────────────►│ └─────── HTTP /review ──────────────┘
|
||||
└────────────►│
|
||||
日志链路: waf / gateway / static / dnfs / ai_review / captcha / smtp / aibox ──(log_client, pymsg)──► db:8005 (Logs 表)
|
||||
```
|
||||
|
||||
### 请求链路
|
||||
|
||||
1. 客户端所有请求先打到 `waf:443`(HTTPS),WAF 终止 TLS、运行 coraza 规则匹配,命中则直接返回 403。
|
||||
2. 未命中规则的请求由 WAF 以明文 HTTP 转发到 `gateway:8443`,并设置 `X-Forwarded-For` / `X-Forwarded-Proto` 头。
|
||||
3. `gateway` 对 `/login`、`/register` 做账号鉴权(SQLite + PBKDF2 密码哈希 + RSA 签名 Token + 可选 CAPTCHA + 速率限制)。
|
||||
3. `gateway` 对 `/login`、`/register` 做账号鉴权(PBKDF2 密码哈希 + RSA 签名 Token + 可选 CAPTCHA + 速率限制),用户数据通过 pymsg/TCP 调用 `db:8005` 存取。
|
||||
4. 其余请求携带 Token,经 `gateway` 校验后反向代理到 `Proxy.opened` 中放开的内部服务(`static:9200` / `dnfs:8006`)。
|
||||
5. `db:8005` 与 `ai_review:8007` 在 `Proxy.closed` 中,仅允许内部服务间调用,不对外暴露。
|
||||
6. `dnfs` 处理节点增删改查时,通过 HTTP 调用 `db` 服务完成数据持久化,新增节点前调用 `ai_review` 进行内容审核。
|
||||
7. 各服务通过 `log_client.py` SDK 异步批量推送运行日志到 `db:8005/log/ingest`,实现集中式日志收集(fail-safe,db 不可用时静默丢弃)。
|
||||
6. `dnfs` 处理节点增删改查时,通过 pymsg/TCP 调用 `db` 服务完成数据持久化,新增节点前调用 `ai_review` 进行内容审核。
|
||||
7. 各服务通过 `log_client.py` SDK 异步批量推送运行日志(pymsg/TCP 写入 db 的 `Logs` 表),实现集中式日志收集(fail-safe,db 不可用时静默丢弃)。
|
||||
|
||||
## 各服务说明
|
||||
|
||||
@@ -47,15 +46,18 @@
|
||||
| [gateway](gateway/) | C++20 | 8443 | 应用层网关(内部 HTTP),用户注册/登录鉴权、Token 签发与校验、反向代理到内部服务 |
|
||||
| [static](static/) | Python(Flask) | 9200 | 前端托管服务,社区首页、静态页面与文件浏览、视频/音频流媒体播放、路径遍历防护 |
|
||||
| [dnfs](dnfs/) | Python(Flask) | 8006 | 树状网络文件系统(Dendrachy Network File-System),基于父子 pid 的节点树增删改查,集成 AI 内容审核 |
|
||||
| [db](db/) | Python(Flask) | 8005 | 通用数据库服务,基于 SQLAlchemy Core 的 SQL-over-HTTP,支持 select/insert/update/delete,参数化查询防注入;内置集中式日志收集(/log/* 路由) |
|
||||
| [db](db/) | Python(pymsg) | 8005 | 通用数据库服务,基于 pymsg 协议(TCP 长度前缀帧 + JSON)的 SQL-over-TCP,SQLAlchemy Core 参数化查询防注入;内置集中式日志收集(Logs 表 + 自动清理) |
|
||||
| [ai_review](ai_review/) | Python(Flask) | 8007 | AI 内容审核服务,DeepSeek LLM + ChromaDB RAG,支持人工反馈纠偏 |
|
||||
| [captcha](captcha/) | Python(pymsg) | 8009 | 验证码服务(pymsg/TCP),生成图形验证码(5 分钟过期、一次性),经 gateway 的 `GET /captcha/image` 代理给浏览器 |
|
||||
| [smtp](smtp/) | Python(Flask) | 8008 | 邮件服务,发送/校验邮箱验证码(/send_email、/verify_email),注册流程的邮件验证码后端,SMTP 账号配置在 .env |
|
||||
| [aibox](aibox/) | Python | 8765 | 独立 AI 智能体系统(DeepSeek + WebSocket),自带技能系统/记忆/任务调度,由 core 拉起但与主干微服务无调用关系 |
|
||||
|
||||
### core(核心模块 · 进程托管服务)
|
||||
|
||||
- 可执行文件 `nidus.out`,作为 systemd 服务(`Nidus.service`)常驻运行。
|
||||
- 可执行文件 `nidus.out`,作为 systemd 服务(`Nidus.service`)常驻运行(崩溃守护由 systemd 负责,core 自身不含自动重启逻辑)。
|
||||
- 支持两种工作模式:命令行子系统调用(`--send/--exec/--list/--kill`)与 HTTP 伺服器模式(`--serve <port>`)。
|
||||
- 通过命名管道(Pipe)与消息队列(Mq)实现进程间通信。
|
||||
- 读取 `init.txt` 自启动列表,按行拉起各业务服务(ai_review / db / dnfs / static / waf / gateway / captcha)。
|
||||
- 读取 `init.txt` 自启动列表,按行拉起各业务服务(ai_review / db / dnfs / smtp / static / waf / gateway / captcha / aibox)。
|
||||
|
||||
### waf(WAF 网关)
|
||||
|
||||
@@ -66,13 +68,15 @@
|
||||
- 极客操作:`kill -HUP <pid>` 热加载规则;`waf -list` 列规则;`waf -test <file>` 校验语法;`waf -check "<input>"` 测试单条输入。
|
||||
- 配置见 [waf/config.yaml](waf/config.yaml),详见 [waf/README.md](waf/README.md)。
|
||||
|
||||
### gateway(应用层网关 · v0.3.0-security)
|
||||
### gateway(应用层网关 · v0.4.3)
|
||||
|
||||
- 内部 HTTP 服务,默认监听 127.0.0.1:8443,不再承载 TLS(TLS 已由 WAF 终止)。
|
||||
- 通过 `Server.tls = on/off` 配置切换 HTTPS/HTTP 模式([example.ini](gateway/example.ini)),独立部署时仍可启用 TLS。
|
||||
- **密码安全**:PBKDF2-HMAC-SHA256(10 万轮 + 16 字节随机盐),密码强度校验(8+ 字符、4 类中至少 3 类),登录时自动迁移旧 SHA256 哈希,常数时间比较防 timing attack。
|
||||
- **Token 机制**:`Base64(payload) + "_" + RSA-Sign(payload)`,默认 1 小时过期,验签通过才放行。
|
||||
- **CAPTCHA**:`/captcha` 接口生成验证码(5 分钟过期、一次性使用),登录与注册均可选启用。
|
||||
- **密码安全**:PBKDF2-HMAC-SHA256(10 万轮 + 16 字节随机盐),密码强度校验(8+ 字符、4 类中至少 3 类),常数时间比较防 timing attack。
|
||||
- **用户存储**:用户数据通过 pymsg/TCP 调用 `db:8005`(`DbC::call_msg`)存取,不再直连本地 SQLite。
|
||||
- **Token 机制**:`Base64(payload) + "_" + RSA-Sign(payload)`,默认 1 小时过期,验签通过才放行;RSA 密钥对启动时内存生成,重启后旧 Token 全部失效。
|
||||
- **Cookie 认证**:登录成功同时签发 `HttpOnly + Secure + SameSite=Lax` 的 `q4_token` Cookie;代理时无 `Authorization` 头则回退读 Cookie 验签,并向下游注入 `Authorization: Bearer`(下游零改动);`POST /logout` 清除 Cookie。公开目标(`Proxy.default`)不做凭证校验,避免旧 Cookie 影响公开页面。
|
||||
- **CAPTCHA**:`/captcha` 接口生成验证码(5 分钟过期、一次性使用),登录与注册均可选启用;`GET /captcha/image` 为 captcha 服务(pymsg/TCP :8009)的图形验证码代理路由。
|
||||
- **速率限制**:登录 5 次/5 分钟、注册 3 次/10 分钟,按 `X-Forwarded-For`(WAF 注入)客户端 IP 计数。
|
||||
- **反向代理**:`Target-Url` 必须在 `Proxy.opened` 白名单中(`closed` 仅记录用途,实际隔离靠"不在 opened 中即拒绝"),缺省走 `Proxy.default`(不需 Token,服务前端静态资源)。
|
||||
- **自动建表**:启动时若 `Users` 表不存在则自动创建,首位注册者即为管理员。
|
||||
@@ -81,44 +85,66 @@
|
||||
### static(前端托管服务)
|
||||
|
||||
- 托管 `public/` 下的前端页面:社区首页(`index.html`)、登录/注册、树状笔记(`dnfs.html`)、文件浏览(`__view.html`)等。
|
||||
- 支持文件浏览、视频/音频在线播放(HTTP Range 分段请求)、一键下载。
|
||||
- 路径遍历防护,仅允许访问 `directory.root` 之内。
|
||||
- 支持文件浏览、视频/音频在线播放(HTTP Range 分段请求,返回 206/416)、一键下载。
|
||||
- 路径遍历防护(`safe_path_join`:realpath 前缀校验),仅允许访问 `directory.root` 之内。
|
||||
- **dnfs 代理**:`/get_notes`、`/add_note`、`/delete_note`、`/update_note` 透传 Authorization 转发到 `dnfs:8006`(解决浏览器跨域)。
|
||||
- **账号旁路**:`/api/check_user`、`/api/reset_password` 等接口直接读写 `gateway/sqlite3.db` 的 Users 表;`/yhdet_callback`、`/api/yhdet_*` 对接 yhdet.top OAuth 登录。
|
||||
- 统一品牌"树状菜单",支持深色模式(`css/theme.css`)。
|
||||
- 可使用 Nuitka 编译为独立二进制(`fsys.bin`)。
|
||||
- 配置见 [static/config.ini](static/config.ini)。
|
||||
- 配置见 [static/config.ini](static/config.ini)(注:CORS 白名单硬编码于 `fsys.py`,config.ini 的 cors 段未生效)。
|
||||
|
||||
### dnfs(树状菜单服务)
|
||||
|
||||
- 基于 `pid`(父节点 id)组织的树状节点,节点含 `title` 与 `content`。
|
||||
- 提供 `/get_notes`、`/add_note`、`/delete_note`、`/update_note` 四个接口。
|
||||
- 数据持久化通过 HTTP 调用 `db` 服务(`http://127.0.0.1:8005/execute`),不再直接操作数据库。
|
||||
- 数据持久化通过 pymsg/TCP 调用 `db` 服务(`127.0.0.1:8005`,execute 路由),不再直接操作数据库。
|
||||
- 新增节点前调用 `ai_review` 服务(`http://127.0.0.1:8007/review`)进行内容审核,审核不通过则拒绝写入。
|
||||
|
||||
### db(通用数据库服务)
|
||||
|
||||
- 单一接口 `POST /execute`,接收结构化 JSON(action + table + where + values),翻译为 SQL 执行。
|
||||
- 基于自研 [pymsg](pymsg/) 协议(TCP 长度前缀帧 + JSON),单一路由 `_route: "execute"`,接收结构化 JSON(action + table + where + values),翻译为 SQL 执行。
|
||||
- 支持 select / insert / update / delete,运算符包括 eq、ne、gt、lt、ge、le、like、in,支持排序与分页。
|
||||
- 基于 SQLAlchemy Core 表反射,参数化查询,零 SQL 注入风险。
|
||||
- 仅监听 127.0.0.1,单线程,自动回滚,不暴露 DDL 操作。
|
||||
- 仅监听 127.0.0.1,自动提交/回滚,不暴露 DDL 操作。
|
||||
- 默认使用 SQLite(`database.db`),可通过 `DATABASE_URL` 环境变量切换至 PostgreSQL / MySQL。
|
||||
- **内置日志子模块**([log_module.py](db/log_module.py)):
|
||||
- `log_internal()` — db 进程内部直接写 Logs 表,无 HTTP 往返,防递归设计。
|
||||
- `/log/ingest` — 接收外部服务批量推送日志(单次上限 100 条,校验 service / level / message)。
|
||||
- `/log/query` — 结构化查询(service / level / trace_id / 时间区间 / 关键字模糊搜索 / 排序分页)。
|
||||
- `/log/stats` — 按 service / level 分组统计。
|
||||
- `/log/purge` — 手动清理指定时间之前的日志。
|
||||
- `/log/health` — 健康检查。
|
||||
- **内置日志子模块**:
|
||||
- 启动时自动建 `Logs` 表(service / level / message / trace_id / extra + 4 个索引)。
|
||||
- 外部服务通过 `log_client.py` 以 pymsg 调 execute 路由逐条 INSERT 写入日志。
|
||||
- `_log_internal()` — db 进程内部直接写 Logs 表,无网络往返,防递归设计。
|
||||
- 后台守护线程每小时自动清理:保留 30 天 / 上限 100 万条。
|
||||
- **统一日志客户端 SDK**([log_client.py](db/log_client.py)):各服务复制即用,纯标准库实现(urllib),异步非阻塞(内存队列 + 后台线程,50 条/2 秒批量 flush),fail-safe 设计(db 不可用时静默丢弃,不影响业务)。
|
||||
- 注:`db/log_module.py`(Flask `/log/*` Blueprint)为旧版 HTTP 接口的遗留文件,当前 dbv2 未挂载。
|
||||
- **统一日志客户端 SDK**(各服务目录下的 `log_client.py`,如 [dnfs/log_client.py](dnfs/log_client.py)):各服务复制即用,纯标准库实现,异步非阻塞(内存队列 + 后台线程,50 条/2 秒批量 flush),fail-safe 设计(db 不可用时静默丢弃,不影响业务)。
|
||||
|
||||
### ai_review(AI 内容审核服务)
|
||||
|
||||
- 基于 DeepSeek LLM + ChromaDB 向量检索的 RAG 审核架构。
|
||||
- 审核类别:色情、违禁药品、赌博、诈骗、仇恨/暴力/恐怖主义,灰色地带内容从严判定。
|
||||
- 接口:`POST /review`(提交审核)、`POST /rag/feedback`(人工纠偏)、`GET /rag/list`(知识库列表)、`GET /health`(健康检查)。
|
||||
- RAG 检索 top-3 相似已标注样本注入 prompt,MD5 去重,API 异常时 fail-open。
|
||||
- RAG 检索 top-3 相似已标注样本注入 prompt,MD5 去重。
|
||||
- **fail-closed 设计**:API 异常或响应解析失败时一律返回 `{"invalid": "true"}`(拦截),宁可误拦不可漏放。
|
||||
- 配置通过 `.env` 文件(已 gitignore),含 DeepSeek API 密钥、模型参数、监听地址。
|
||||
|
||||
### captcha(验证码服务)
|
||||
|
||||
- 生成图形验证码,Token 一次性使用、5 分钟过期;基于 [pymsg](pymsg/) 协议(TCP 长度前缀帧 + JSON),仅依赖 PIL。
|
||||
- pymsg 路由:`image`(生成,返回 token + base64 PNG)、`verify`(校验,一次性消费)、`health`。
|
||||
- 答案比较采用 XOR 防时序攻击;错误答案与无效 Token 分别返回 `{valid:false}` 与 `{valid:false, error:...}`。
|
||||
- **gateway 代理路由**:`GET /captcha/image` 经 pymsg 调 captcha 后解码 PNG,以 `image/png` + `X-Captcha-Token` 响应头返回浏览器(`Cache-Control: no-store`)。
|
||||
- 由 `gateway` 在 `/login`、`/register` 中可选启用 CAPTCHA 校验(字段 `captcha_token` / `captcha_answer`)。
|
||||
- 内存存储,不支持多实例水平扩展(需换 Redis);详见 [captcha/README.md](captcha/README.md)。
|
||||
|
||||
### smtp(邮件服务)
|
||||
|
||||
- 注册流程的邮箱验证码后端,基于 Flask + waitress,监听 127.0.0.1:8008。
|
||||
- 接口:`POST /send_email`(生成 6 位验证码并经 SMTP_SSL 发送至收件人,60 秒有效)、`POST /verify_email`(校验)。
|
||||
- 验证码存于进程内存,SMTP 账号通过 `.env` 配置(`SMTP_SERVER` / `SMTP_PORT` / `SMTP_USER` / `__PASSWORD`)。
|
||||
|
||||
### aibox(独立 AI 智能体系统)
|
||||
|
||||
- 一套相对独立的单智能体产品,基于 DeepSeek + WebSocket(:8765),由 `core` 拉起但与 waf / gateway / dnfs 等主干微服务无调用关系。
|
||||
- 自带技能系统(`skills/`)、任务调度器、记忆管理、前端页面与语音客户端(`voice_client.py`)。
|
||||
- 默认模型 `deepseek-v4-flash`,配置在 `config.json`;详见 [aibox/README.md](aibox/README.md)。
|
||||
|
||||
## 共享基础库
|
||||
|
||||
| 库 | 说明 |
|
||||
@@ -132,8 +158,9 @@
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| [log_client.py](db/log_client.py) | 统一日志客户端 SDK,各服务复制即用(纯标准库,异步批量推送) |
|
||||
| [security_middleware.py](db/security_middleware.py) | Flask 安全响应头中间件(CSP / X-Frame-Options / nosniff 等),各服务各持一份副本 |
|
||||
| [pymsg](pymsg/) | 自研 RPC 基础库(setuptools 包),协议为 `[4字节大端长度][UTF-8 JSON]`,提供 `send/recv/MsgClient/MsgServer`;db 服务端与各 log_client 的基础依赖,requirements 以 `../pymsg` 本地路径引用 |
|
||||
| log_client.py | 统一日志客户端 SDK,各服务目录各持一份副本(如 [dnfs/log_client.py](dnfs/log_client.py)),纯标准库 + pymsg,异步批量推送 |
|
||||
| security_middleware.py | Flask 安全响应头中间件(CSP / X-Frame-Options / nosniff 等),各服务各持一份副本 |
|
||||
|
||||
## 端口一览
|
||||
|
||||
@@ -144,9 +171,11 @@
|
||||
| 5200 | core | — | PMC HTTP 管理接口 |
|
||||
| 9200 | static | 127.0.0.1 | 前端托管(经 gateway 代理) |
|
||||
| 8006 | dnfs | 127.0.0.1 | 树状菜单(经 gateway 代理) |
|
||||
| 8005 | db | 127.0.0.1 | 数据库服务(仅内部调用) |
|
||||
| 8005 | db | 127.0.0.1 | 数据库服务(pymsg/TCP,仅内部调用) |
|
||||
| 8007 | ai_review | 127.0.0.1 | AI 审核(仅内部调用) |
|
||||
| 8009 | captcha | 127.0.0.1 | 验证码服务(独立组件) |
|
||||
| 8009 | captcha | 127.0.0.1 | 验证码服务(pymsg/TCP,经 gateway 代理) |
|
||||
| 8008 | smtp | 127.0.0.1 | 邮件服务(仅内部调用,注册验证码邮件后端) |
|
||||
| 8765 | aibox | 127.0.0.1 | AI 智能体(WebSocket,独立产品) |
|
||||
|
||||
## 依赖
|
||||
|
||||
@@ -189,10 +218,12 @@ bash _run.sh # 运行
|
||||
bash /opt/micro_servers/ai_review/_run.sh
|
||||
bash /opt/micro_servers/db/_run.sh
|
||||
bash /opt/micro_servers/dnfs/_run.sh
|
||||
bash /opt/micro_servers/smtp/_run.sh
|
||||
bash /opt/micro_servers/static/_run.sh
|
||||
bash /opt/micro_servers/waf/_run.sh
|
||||
bash /opt/micro_servers/gateway/_run.sh
|
||||
bash /opt/micro_servers/captcha/_run.sh
|
||||
bash /opt/micro_servers/aibox/_run.sh
|
||||
```
|
||||
|
||||
启动 `core`(需 root 或 systemd):
|
||||
@@ -223,10 +254,10 @@ gateway 改为内部 HTTP 模式([example.ini](gateway/example.ini) 中 `tls =
|
||||
- **服务按业务能力拆分**:WAF 网关、应用网关、前端托管、节点树、数据库、内容审核各自独立、独立端口、独立数据存储。
|
||||
- **API 网关模式**:`waf` 作为边缘网关统一入口,`gateway` 承担鉴权与反向代理,内部服务通过 `closed` 列表隔离。
|
||||
- **进程编排/守护**:`core` 作为服务管理器拉起并守护子进程,类似简易版 supervisor。
|
||||
- **多语言异构**:C++(core / gateway)、Python(static / dnfs / db / ai_review)、Go(waf)混合,通过 HTTP 通信。
|
||||
- **数据访问层解耦**:`db` 服务将数据库操作收敛为统一的 HTTP 接口,业务服务无需直连数据库。
|
||||
- **多语言异构**:C++(core / gateway)、Python(static / dnfs / db / ai_review)、Go(waf)混合,对外 HTTP、内部数据访问走 pymsg/TCP。
|
||||
- **数据访问层解耦**:`db` 服务将数据库操作收敛为统一的 pymsg/TCP 接口,业务服务无需直连数据库。
|
||||
- **AI 能力集成**:`ai_review` 以 RAG 增强的 LLM 审核为业务层提供内容安全能力,支持人工反馈闭环。
|
||||
- **集中式可观测性**:`db` 内置日志收集服务(/log/* 路由),各服务通过 `log_client.py` SDK 异步推送日志,实现全链路汇聚、查询与统计,无需引入额外中间件。
|
||||
- **集中式可观测性**:`db` 内置日志表与自动清理,各服务通过 `log_client.py` SDK 异步推送日志(pymsg),实现全链路汇聚,无需引入额外中间件。
|
||||
- **安全分层**:`waf` 在边缘做规则匹配(OWASP CRS / 自定义 SecLang 规则),`gateway` 在应用层做鉴权,`db` 仅监听本机,多层防护。
|
||||
|
||||
与云原生微服务的差异:未引入服务注册发现、容器编排、消息总线,服务地址在 `waf/config.yaml` 与 `gateway/example.ini` 中静态配置。对于个人/中小规模的 DIY 场景,这种取舍在简洁性与可控性上是合理的。
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
|
||||
# Debian
|
||||
sudo apt update
|
||||
sudo apt install libsqlite3-dev libboost-all-dev libcurl4-gnutls-dev python3 python3-venv python3-pip g++ patchelf golang-go
|
||||
sudo apt install libsqlite3-dev libboost-all-dev libcurl4-gnutls-dev python3 python3-venv python3-pip g++ patchelf golang-go libportaudio-ocaml-dev
|
||||
@@ -12,7 +12,7 @@ def apply_security_headers(app, csp_policy=None):
|
||||
@app.after_request
|
||||
def set_security_headers(response):
|
||||
# Prevent clickjacking
|
||||
response.headers['X-Frame-Options'] = 'DENY'
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
# XSS protection (legacy browsers)
|
||||
response.headers['X-XSS-Protection'] = '1; mode=block'
|
||||
# Prevent MIME type sniffing
|
||||
@@ -30,10 +30,10 @@ def apply_security_headers(app, csp_policy=None):
|
||||
"default-src 'self'; "
|
||||
"script-src 'self' 'unsafe-inline'; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"img-src 'self' data:; "
|
||||
"img-src 'self' data: blob:; "
|
||||
"font-src 'self'; "
|
||||
"connect-src 'self'; "
|
||||
"frame-ancestors 'none'"
|
||||
"frame-ancestors 'self'"
|
||||
)
|
||||
|
||||
# Remove server header info
|
||||
|
||||
+190
-203
@@ -1,21 +1,24 @@
|
||||
# 单AI智能体系统 - WebSocket版本(增强版)
|
||||
|
||||
一个功能强大的AI智能体系统,通过WebSocket与客户端交互,支持文件操作、网页访问、记忆管理、任务调度、扩展技能等功能。所有任务都通过AI对话执行,支持实时流式输出和任务状态广播。
|
||||
一个基于 DeepSeek API 的**单 AI 智能体**系统,通过 WebSocket 与客户端交互。所有任务(文件操作、网页访问、记忆管理、任务调度等)都由 AI 对话驱动执行,支持实时流式输出与任务状态广播。内置可热重载的技能系统,智能体可通过工具自我进化(更新角色定义、自主创建技能)。
|
||||
|
||||
> 本模块为 `micro_servers` 项目中的独立组件,由 `core`(`PMC`)通过 `init.txt` 中的 `bash /opt/micro_servers/aibox/_run.sh` 拉起。它与主干微服务(waf/gateway/static/dnfs/db/ai_review)解耦,是另一套 AI 助手产品。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
- 🤖 **单AI智能体**:基于DeepSeek API的智能对话系统,支持思考模式
|
||||
- 🔌 **WebSocket通信**:实时双向通信,支持流式输出和任务状态广播
|
||||
- 📁 **文件操作**:读写文件、目录列表(支持大文件分页)
|
||||
- 🌐 **网页访问**:获取网页内容、提取标题、链接、搜索信息
|
||||
- 🧠 **记忆管理**:自动保存和检索对话记忆,保持对话连贯性
|
||||
- ⏰ **任务系统**:支持一次性、重复(daily/weekly/monthly/custom)、即时任务,自动触发AI对话
|
||||
- 🔧 **扩展技能**:动态加载`~/.aibox/skills/`目录下的可执行脚本或Markdown技能文件
|
||||
- 📦 **对话压缩**:当消息数或Token数达到阈值时自动压缩,保持上下文质量
|
||||
- 🤖 **单 AI 智能体**:基于 DeepSeek API 的智能对话系统,支持思考模式(reasoning 流式输出)
|
||||
- 🔌 **WebSocket 通信**:实时双向通信,支持流式输出与任务状态广播
|
||||
- 📁 **文件操作**:读写文件、目录列表(支持大文件分页 head/tail)
|
||||
- 🌐 **网页访问**:获取网页内容、提取标题、链接与正文
|
||||
- 🧠 **记忆管理**:自动保存与检索对话记忆,保持对话连贯性
|
||||
- ⏰ **任务系统**:支持一次性、重复(daily/weekly/monthly/custom)、即时任务,自动触发 AI 对话
|
||||
- 🔧 **扩展技能**:动态加载 `~/.aibox/skills/` 下的可执行脚本或 Markdown 技能文件
|
||||
- 🌍 **多语言支持**:自动检测系统语言,支持中英文
|
||||
- 🧬 **智能体自我进化**:通过`update_soul`工具动态更新角色定位和性格定义
|
||||
- 🧬 **智能体自我进化**:通过 `update_soul` / `update_identity` 工具动态更新角色定位与用户画像
|
||||
- 🔄 **技能热重载**:无需重启即可添加、修改或删除扩展技能
|
||||
- 💾 **对话管理**:历史对话归档、开始新对话(重置AI记忆)
|
||||
- 🔐 **SOCKS5 代理**:通过 `set_proxy` / `get_proxy` 工具为网页访问与 API 调用配置代理,可持久化
|
||||
- 💾 **对话管理**:历史对话归档、开始新对话(重置 AI 记忆并重新加载提示词)
|
||||
- 📦 **对话压缩**:消息数或 Token 数达到阈值时自动压缩,保持上下文质量
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
@@ -23,46 +26,72 @@
|
||||
|
||||
- Python 3.8+
|
||||
- DeepSeek API Key
|
||||
- 操作系统:Linux/macOS/Windows
|
||||
- 操作系统:Linux / macOS / Windows
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
# 克隆或下载代码
|
||||
# 安装依赖
|
||||
# 克隆或下载代码后,安装依赖
|
||||
pip install websockets requests beautifulsoup4
|
||||
```
|
||||
|
||||
### 配置
|
||||
|
||||
1. **设置DeepSeek API Key**:
|
||||
1. **设置 DeepSeek API Key**:
|
||||
```bash
|
||||
export DEEPSEEK_API_KEY="your-api-key"
|
||||
```
|
||||
|
||||
2. **(可选)配置文件**:`~/.aibox/config.json`(系统会自动创建默认配置)
|
||||
2. **(可选)配置文件**:`~/.aibox/config.json`(首次启动时自动创建默认配置)
|
||||
|
||||
3. **启动服务器**:
|
||||
```bash
|
||||
python agent.py
|
||||
python main.py
|
||||
```
|
||||
|
||||
启动后,WebSocket服务器将运行在 `ws://localhost:8765`
|
||||
启动后,WebSocket 服务器将运行在 `ws://localhost:8765`(地址与端口可在 `config.json` 的 `system.websocket_host` / `system.websocket_port` 中修改)。
|
||||
|
||||
> 注意:本模块入口文件为 `main.py`(不是 `agent.py`)。`client.py` 为示例 WebSocket 客户端,`voice_client.py` 为语音客户端,`_run.sh` 为被 `core` 拉起的启动脚本。
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
aibox/
|
||||
├── main.py # 服务主程序(WebSocket 服务器 + AI 引擎 + 工具/技能系统)
|
||||
├── client.py # 示例 WebSocket 客户端
|
||||
├── voice_client.py # 语音客户端
|
||||
├── entry.html # 前端入口页
|
||||
├── entry1.html # 前端入口页(变体)
|
||||
├── frontend.html # 前端主页面
|
||||
├── skills/ # 内置扩展技能(*.py 可执行脚本 / *.md Markdown 技能)
|
||||
│ ├── hello-world.py
|
||||
│ ├── aes-crypto.py
|
||||
│ ├── bing_search.py
|
||||
│ ├── facerec.py
|
||||
│ ├── gitclient.py
|
||||
│ ├── ssh-exec.py
|
||||
│ ├── sftp-transfer.py
|
||||
│ └── ...
|
||||
├── SKILLS_FORMAT.md # 技能文件格式说明
|
||||
├── _run.sh # 启动脚本(被 core 守护时调用)
|
||||
├── _runcli.sh # 命令行启动脚本
|
||||
├── _set.sh # 安装脚本
|
||||
├── _setenv.sh # 环境设置脚本
|
||||
└── README.md
|
||||
```
|
||||
|
||||
运行期数据存放于 `~/.aibox/`:
|
||||
|
||||
```
|
||||
~/.aibox/
|
||||
├── config.json # 配置文件
|
||||
├── prompts/ # 系统提示词目录
|
||||
│ ├── 00_IDENTITY.md # 用户身份信息
|
||||
│ ├── 1_*.md # 基础技能提示词(文件操作、网页访问等)
|
||||
│ ├── 99_SOUL.md # AI灵魂定义(角色、性格、能力边界)
|
||||
│ └── 100_STATUS.md # AI状态记录
|
||||
├── skills/ # 扩展技能目录
|
||||
│ ├── my_skill # 可执行技能(无扩展名)
|
||||
│ └── guide.md # Markdown技能
|
||||
├── memories.db # 记忆和任务数据库
|
||||
│ ├── 99_SOUL.md # AI 灵魂定义(角色、性格、能力边界)
|
||||
│ └── 100_STATUS.md # AI 状态记录
|
||||
├── skills/ # 扩展技能目录(运行时从仓库 skills/ 复制或用户自建)
|
||||
├── memories.db # 记忆和任务数据库(SQLite)
|
||||
├── current_conversation.json # 当前对话
|
||||
├── conversation_history.json # 对话索引
|
||||
├── archive/ # 历史对话归档
|
||||
@@ -71,7 +100,7 @@ python agent.py
|
||||
|
||||
## 🔧 可用工具
|
||||
|
||||
AI智能体可以调用以下工具来完成任务:
|
||||
AI 智能体可以调用以下工具来完成任务(由 `Tool` 基类派生,在 `DeepSeekChat` 中注册):
|
||||
|
||||
| 工具名称 | 功能描述 |
|
||||
|---------|---------|
|
||||
@@ -82,15 +111,17 @@ AI智能体可以调用以下工具来完成任务:
|
||||
| `memory` | 保存、搜索(支持分页)、删除记忆 |
|
||||
| `memo` | 创建、查看、完成、搜索任务 |
|
||||
| `run_command` | 执行系统命令(实时输出,支持超时控制) |
|
||||
| `search_skills` | 搜索扩展技能(支持Markdown和可执行技能) |
|
||||
| `search_skills` | 搜索扩展技能(支持 Markdown 和可执行技能) |
|
||||
| `reload_skills` | 热重载扩展技能 |
|
||||
| `create_skill` | 创建新技能(AI自主编写Python脚本) |
|
||||
| `create_skill` | 创建新技能(AI 自主编写 Python 脚本) |
|
||||
| `install_skill` | 技能安装指引 |
|
||||
| `update_identity` | 更新用户身份信息 |
|
||||
| `update_soul` | 更新AI自我定义(智能体进化) |
|
||||
| `update_identity` | 更新用户身份信息(`00_IDENTITY.md`) |
|
||||
| `update_soul` | 更新 AI 自我定义(`99_SOUL.md`,智能体进化) |
|
||||
| `save_memory_and_end_conversation` | 保存记忆并结束对话 |
|
||||
| `get_current_time` | 获取当前时间 |
|
||||
| `calculator` | 数学计算 |
|
||||
| `set_proxy` | 设置 SOCKS5 代理(host/port/可选账号密码,可持久化,即时生效) |
|
||||
| `get_proxy` | 查看当前 SOCKS5 代理配置 |
|
||||
|
||||
## 💬 WebSocket API
|
||||
|
||||
@@ -140,14 +171,15 @@ const ws = new WebSocket('ws://localhost:8765');
|
||||
|
||||
### 支持的命令
|
||||
|
||||
> 注意:记忆搜索通过 `memory` 工具(在对话中由 AI 调用)完成,本系统**不存在** `/search` 斜杠命令。
|
||||
|
||||
| 命令 | 功能 |
|
||||
|-----|------|
|
||||
| `/exit` | 退出连接 |
|
||||
| `/new` | 开始新对话(保存当前,重置AI记忆,重新加载提示词) |
|
||||
| `/new` | 开始新对话(保存当前,重置 AI 记忆,重新加载提示词) |
|
||||
| `/list` | 列出所有历史对话 |
|
||||
| `/memories` | 查看最近记忆(支持分页) |
|
||||
| `/memos` | 查看待办任务 |
|
||||
| `/search <关键词>` | 搜索记忆 |
|
||||
| `/config` | 查看配置 |
|
||||
| `/reload` | 重载配置 |
|
||||
| `/reload_prompts` | 重载提示词 |
|
||||
@@ -159,17 +191,17 @@ const ws = new WebSocket('ws://localhost:8765');
|
||||
|
||||
| 类型 | 说明 |
|
||||
|-----|------|
|
||||
| `chunk` | AI响应的片段(流式输出) |
|
||||
| `complete` | AI响应完成 |
|
||||
| `reasoning` | AI思考过程(调试模式) |
|
||||
| `chunk` | AI 响应的片段(流式输出) |
|
||||
| `complete` | AI 响应完成 |
|
||||
| `reasoning` | AI 思考过程(思考模式) |
|
||||
| `error` | 错误信息 |
|
||||
| `command_result` | 命令执行结果 |
|
||||
| `task_status` | 任务状态更新(executing/complete/failed) |
|
||||
| `task_process` | 任务执行过程中的AI输出 |
|
||||
| `task_process` | 任务执行过程中的 AI 输出 |
|
||||
| `dialogue_ended` | 对话结束通知 |
|
||||
| `dialogue_reset` | 对话重置完成 |
|
||||
| `info` | 系统信息 |
|
||||
| `pong` | Ping响应 |
|
||||
| `pong` | Ping 响应 |
|
||||
|
||||
## ⏰ 任务系统
|
||||
|
||||
@@ -188,51 +220,38 @@ const ws = new WebSocket('ws://localhost:8765');
|
||||
```json
|
||||
// 每天提醒
|
||||
{
|
||||
"type": "task",
|
||||
"action": "add",
|
||||
"title": "每日健康提醒",
|
||||
"repeat_type": "daily"
|
||||
"type": "task", "action": "add", "title": "每日健康提醒", "repeat_type": "daily"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// 5分钟后执行
|
||||
{
|
||||
"type": "task",
|
||||
"action": "add",
|
||||
"title": "5分钟后提醒",
|
||||
"delay": 300
|
||||
"type": "task", "action": "add", "title": "5分钟后提醒", "delay": 300
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// 每30分钟重复
|
||||
{
|
||||
"type": "task",
|
||||
"action": "add",
|
||||
"title": "定时检查",
|
||||
"repeat_type": "custom",
|
||||
"repeat_interval": 30,
|
||||
"repeat_interval_unit": "minutes"
|
||||
"type": "task", "action": "add", "title": "定时检查", "repeat_type": "custom",
|
||||
"repeat_interval": 30, "repeat_interval_unit": "minutes"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// 即时任务
|
||||
{
|
||||
"type": "task",
|
||||
"action": "add",
|
||||
"title": "检查服务器状态",
|
||||
"is_immediate": true
|
||||
"type": "task", "action": "add", "title": "检查服务器状态", "is_immediate": true
|
||||
}
|
||||
```
|
||||
|
||||
### 任务执行流程
|
||||
|
||||
1. 任务到期后,系统自动标记为已触发
|
||||
2. 创建新的AI对话,将任务内容作为用户输入
|
||||
3. AI处理任务,使用工具执行操作
|
||||
4. 任务执行结果实时广播到所有WebSocket客户端
|
||||
2. 创建新的 AI 对话,将任务内容作为用户输入
|
||||
3. AI 处理任务,使用工具执行操作
|
||||
4. 任务执行结果实时广播到所有 WebSocket 客户端
|
||||
5. 重复任务自动计算下一次执行时间
|
||||
|
||||
## 🔧 扩展技能
|
||||
@@ -241,9 +260,8 @@ const ws = new WebSocket('ws://localhost:8765');
|
||||
|
||||
#### 1. 可执行技能
|
||||
- 放在 `~/.aibox/skills/` 目录
|
||||
- 文件必须可执行(`chmod +x`)
|
||||
- 支持 `--help` 选项
|
||||
- `--help` 第一行为一句话简介,其余为使用说明
|
||||
- 文件必须可执行(`chmod +x`),建议不带扩展名
|
||||
- 支持 `--help` 选项;`--help` 第一行为一句话简介,其余为使用说明
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
@@ -251,13 +269,9 @@ const ws = new WebSocket('ws://localhost:8765');
|
||||
import argparse
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="温度转换工具 - 摄氏度转华氏度"
|
||||
)
|
||||
parser.add_argument("-t", "--temp", type=float, required=True,
|
||||
help="摄氏温度值")
|
||||
parser = argparse.ArgumentParser(description="温度转换工具 - 摄氏度转华氏度")
|
||||
parser.add_argument("-t", "--temp", type=float, required=True, help="摄氏温度值")
|
||||
args = parser.parse_args()
|
||||
|
||||
fahrenheit = args.temp * 9/5 + 32
|
||||
print(f"{args.temp}°C = {fahrenheit:.1f}°F")
|
||||
|
||||
@@ -265,15 +279,14 @@ if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
#### 2. Markdown技能
|
||||
#### 2. Markdown 技能
|
||||
- 文件以 `.md` 结尾
|
||||
- 第一行为简介(`# 标题` 或纯文本)
|
||||
- 其余为完整的技能内容
|
||||
- 第一行为简介(`# 标题` 或纯文本),其余为完整技能内容
|
||||
|
||||
**示例**:
|
||||
```markdown
|
||||
# 代码审查指南
|
||||
提供代码审查的检查清单和最佳实践...
|
||||
提供代码审查的检查清单和最佳实践……
|
||||
|
||||
## 检查要点
|
||||
1. 代码风格一致性
|
||||
@@ -284,12 +297,12 @@ if __name__ == "__main__":
|
||||
|
||||
### 创建技能
|
||||
|
||||
#### 方式1:AI自主创建(推荐)
|
||||
直接告诉AI:
|
||||
#### 方式1:AI 自主创建(推荐)
|
||||
直接告诉 AI:
|
||||
```
|
||||
创建一个技能,功能是将摄氏度转换为华氏度,接受 -c 参数输入温度
|
||||
```
|
||||
AI会自动使用 `create_skill` 工具编写脚本并部署。
|
||||
AI 会自动使用 `create_skill` 工具编写脚本并部署到 `~/.aibox/skills/`。
|
||||
|
||||
#### 方式2:手动创建
|
||||
1. 编写脚本,确保支持 `--help`
|
||||
@@ -304,48 +317,40 @@ chmod +x ~/.aibox/skills/my_skill
|
||||
|
||||
#### 方式3:使用安装指引
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"content": "使用 install_skill 工具安装 /path/to/script.py"
|
||||
}
|
||||
{ "type": "message", "content": "使用 install_skill 工具安装 /path/to/script.py" }
|
||||
```
|
||||
|
||||
技能格式详见 [SKILLS_FORMAT.md](SKILLS_FORMAT.md)。
|
||||
|
||||
## 🧠 智能体自我进化
|
||||
|
||||
AI可以通过 `update_soul` 工具动态更新 `99_SOUL.md` 文件:
|
||||
AI 可以通过 `update_soul` 工具动态更新 `99_SOUL.md`,通过 `update_identity` 工具更新 `00_IDENTITY.md`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "message",
|
||||
"content": "使用 update_soul 更新我的角色,强调我更注重创造性思维"
|
||||
}
|
||||
{ "type": "message", "content": "使用 update_soul 更新我的角色,强调我更注重创造性思维" }
|
||||
```
|
||||
|
||||
这允许AI在对话中不断优化自己的:
|
||||
- 角色定位
|
||||
- 性格特点
|
||||
- 能力边界
|
||||
- 行为准则
|
||||
- 价值观
|
||||
这允许 AI 在对话中不断优化自己的:角色定位、性格特点、能力边界、行为准则、价值观。
|
||||
|
||||
## 📦 对话压缩
|
||||
|
||||
当对话达到阈值时自动压缩:
|
||||
|
||||
```json
|
||||
// 配置示例
|
||||
"context": {
|
||||
"max_history_messages": 500,
|
||||
"compress_enabled": true,
|
||||
"compress_message_threshold": 200, // 消息数阈值
|
||||
"compress_token_threshold": 120000, // Token数阈值
|
||||
"compress_keep_recent": 8 // 保留最近消息数
|
||||
// 配置示例(config.json -> context)
|
||||
{
|
||||
"max_history_messages": 500, // 历史消息上限
|
||||
"compress_enabled": true, // 是否启用压缩
|
||||
"compress_message_threshold": 200, // 消息数阈值
|
||||
"compress_token_threshold": 120000, // Token 数阈值
|
||||
"compress_ratio": 0.2, // 压缩保留比例(早期对话压缩为原长的该比例)
|
||||
"compress_keep_recent": 8 // 保留最近消息数
|
||||
}
|
||||
```
|
||||
|
||||
压缩过程:
|
||||
1. 检测到消息数或Token数超过阈值
|
||||
2. 调用AI压缩早期对话内容
|
||||
1. 检测到消息数或 Token 数超过阈值
|
||||
2. 调用 AI 压缩早期对话内容
|
||||
3. 保留最近的重要消息
|
||||
4. 将压缩摘要作为系统消息
|
||||
5. 压缩过程静默执行,不影响用户体验
|
||||
@@ -356,14 +361,14 @@ AI可以通过 `update_soul` 工具动态更新 `99_SOUL.md` 文件:
|
||||
|
||||
| 文件 | 用途 | 更新方式 |
|
||||
|-----|------|---------|
|
||||
| `00_IDENTITY.md` | 用户身份信息 | AI通过 `update_identity` 工具更新 |
|
||||
| `1_*.md` | 基础技能提示词 | 手动编辑,AI通过 `reload_prompts` 重载 |
|
||||
| `99_SOUL.md` | AI灵魂定义(角色、性格) | AI通过 `update_soul` 工具更新 |
|
||||
| `100_STATUS.md` | AI状态记录 | AI通过 `save_memory_and_end_conversation` 更新 |
|
||||
| `00_IDENTITY.md` | 用户身份信息 | AI 通过 `update_identity` 工具更新 |
|
||||
| `1_*.md` | 基础技能提示词 | 手动编辑,AI 通过 `reload_prompts` 重载 |
|
||||
| `99_SOUL.md` | AI 灵魂定义(角色、性格) | AI 通过 `update_soul` 工具更新 |
|
||||
| `100_STATUS.md` | AI 状态记录 | AI 通过 `save_memory_and_end_conversation` 更新 |
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
主要配置项(`~/.aibox/config.json`):
|
||||
主要配置项(`~/.aibox/config.json`,首次启动自动生成):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -372,13 +377,16 @@ AI可以通过 `update_soul` 工具动态更新 `99_SOUL.md` 文件:
|
||||
"websocket_host": "localhost",
|
||||
"websocket_port": 8765,
|
||||
"debug_level": 1,
|
||||
"log_file": "~/.aibox/ai.log"
|
||||
"log_file": "~/.aibox/ai.log",
|
||||
"skills_dir": "~/.aibox/skills",
|
||||
"prompts_dir": "~/.aibox/prompts"
|
||||
},
|
||||
"context": {
|
||||
"max_history_messages": 500,
|
||||
"compress_enabled": true,
|
||||
"compress_message_threshold": 200,
|
||||
"compress_token_threshold": 120000,
|
||||
"compress_ratio": 0.2,
|
||||
"compress_keep_recent": 8
|
||||
},
|
||||
"scheduler": {
|
||||
@@ -388,7 +396,7 @@ AI可以通过 `update_soul` 工具动态更新 `99_SOUL.md` 文件:
|
||||
},
|
||||
"api": {
|
||||
"deepseek_api_url": "https://api.deepseek.com/chat/completions",
|
||||
"deepseek_model": "deepseek-chat",
|
||||
"deepseek_model": "deepseek-v4-flash",
|
||||
"api_key_env": "DEEPSEEK_API_KEY"
|
||||
},
|
||||
"memory": {
|
||||
@@ -400,17 +408,19 @@ AI可以通过 `update_soul` 工具动态更新 `99_SOUL.md` 文件:
|
||||
}
|
||||
```
|
||||
|
||||
> 默认模型为 `deepseek-v4-flash`(通过 `api.deepseek_model` 修改)。API Key 从环境变量 `DEEPSEEK_API_KEY` 读取(亦支持 `~/.cert/deepseek.key` 文件)。
|
||||
|
||||
## 🛠️ 命令行参数
|
||||
|
||||
```bash
|
||||
python agent.py [选项]
|
||||
python main.py [选项]
|
||||
|
||||
选项:
|
||||
--verbose, -v 显示详细日志
|
||||
--debug LEVEL 调试级别 (0-3)
|
||||
--lang zh|en 强制语言
|
||||
--host HOST WebSocket主机
|
||||
--port PORT WebSocket端口
|
||||
--host HOST WebSocket 主机
|
||||
--port PORT WebSocket 端口
|
||||
--no-scheduler 禁用任务调度器
|
||||
--scheduler-interval 调度器检查间隔(秒)
|
||||
--config PATH 指定配置文件路径
|
||||
@@ -424,19 +434,19 @@ python agent.py [选项]
|
||||
|
||||
```bash
|
||||
# 启动服务器(详细日志)
|
||||
python agent.py --verbose
|
||||
python main.py --verbose
|
||||
|
||||
# 指定端口
|
||||
python agent.py --port 9000
|
||||
python main.py --port 9000
|
||||
|
||||
# 检查任务
|
||||
python agent.py --check-memo
|
||||
python main.py --check-memo
|
||||
|
||||
# 添加任务(命令行)
|
||||
python agent.py --memo-add "提醒开会|下午3点会议室A|+1h"
|
||||
python main.py --memo-add "提醒开会|下午3点会议室A|+1h"
|
||||
|
||||
# 查看所有任务
|
||||
python agent.py --memo-list
|
||||
python main.py --memo-list
|
||||
```
|
||||
|
||||
## 🔄 架构流程
|
||||
@@ -452,7 +462,7 @@ python agent.py --memo-list
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌───────────────┐ │
|
||||
│ │ 消息处理器 │ │ 命令处理器 │ │ 任务调度器 │ │
|
||||
│ └─────────────┘ └─────────────┘ └───────────────┘ │
|
||||
└─────────────────┬───────────────────────────────────────┘
|
||||
└─────────────────┬──────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────▼───────────────────────────────────────┐
|
||||
│ AI 对话引擎 (DeepSeek) │
|
||||
@@ -502,16 +512,54 @@ Server -> Client: {type: "command_result", content: "🔄 开始新对话..."}
|
||||
Server -> Client: {type: "dialogue_reset", content: "✅ 对话状态已完全重置"}
|
||||
```
|
||||
|
||||
## 📝 开发指南
|
||||
|
||||
### 添加新工具
|
||||
|
||||
1. 继承 `Tool` 基类(位于 `main.py`)
|
||||
2. 实现 `get_name()`、`get_description()`、`get_parameters()`、`execute()`
|
||||
3. 可选实现 `execute_streaming()` 支持流式输出
|
||||
4. 在 `DeepSeekChat` 的工具注册处注册
|
||||
|
||||
```python
|
||||
class MyTool(Tool):
|
||||
def get_name(self) -> str:
|
||||
return "my_tool"
|
||||
|
||||
def get_description(self) -> str:
|
||||
return "我的自定义工具"
|
||||
|
||||
def get_parameters(self) -> Dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {"param": {"type": "string"}},
|
||||
"required": ["param"]
|
||||
}
|
||||
|
||||
def execute(self, param: str, **kwargs) -> str:
|
||||
return f"执行结果: {param}"
|
||||
```
|
||||
|
||||
### 添加新命令
|
||||
|
||||
在 `WebSocketHandler` 的命令处理分支(`handle_command`)中添加:
|
||||
|
||||
```python
|
||||
elif command == "/mycommand":
|
||||
result = "命令执行结果"
|
||||
await self.send_message(websocket, {"type": "command_result", "content": result})
|
||||
```
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: API Key无效怎么办?
|
||||
**A:**
|
||||
### Q: API Key 无效怎么办?
|
||||
**A:**
|
||||
1. 确认环境变量已设置:`echo $DEEPSEEK_API_KEY`
|
||||
2. 检查配置文件中的 `api_key_env` 设置
|
||||
3. 创建 `~/.cert/deepseek.key` 文件并写入API Key
|
||||
3. 创建 `~/.cert/deepseek.key` 文件并写入 API Key
|
||||
|
||||
### Q: 技能不生效?
|
||||
**A:**
|
||||
**A:**
|
||||
1. 确认文件在 `~/.aibox/skills/` 目录
|
||||
2. 确认文件可执行(`chmod +x`)
|
||||
3. 调用 `reload_skills` 工具
|
||||
@@ -519,7 +567,7 @@ Server -> Client: {type: "dialogue_reset", content: "✅ 对话状态已完全
|
||||
5. 检查文件命名(不能有扩展名)
|
||||
|
||||
### Q: 任务没有执行?
|
||||
**A:**
|
||||
**A:**
|
||||
1. 检查调度器是否启用:`/tasks`
|
||||
2. 查看任务状态:`/memos`
|
||||
3. 检查系统时间是否正确
|
||||
@@ -527,118 +575,57 @@ Server -> Client: {type: "dialogue_reset", content: "✅ 对话状态已完全
|
||||
5. 确认任务有 `reminder_time`
|
||||
|
||||
### Q: 对话压缩丢失了重要信息?
|
||||
**A:** 调整配置:
|
||||
**A:** 调整 `config.json` 的 `context` 配置:
|
||||
```json
|
||||
"compress_message_threshold": 300, // 调大阈值
|
||||
"compress_keep_recent": 20 // 保留更多最近消息
|
||||
```
|
||||
|
||||
### Q: 如何备份数据?
|
||||
**A:**
|
||||
```bash
|
||||
# 备份整个目录
|
||||
tar -czf aibox_backup_$(date +%Y%m%d).tar.gz ~/.aibox/
|
||||
|
||||
# 恢复
|
||||
tar -xzf aibox_backup_20260101.tar.gz -C ~/
|
||||
```
|
||||
|
||||
### Q: WebSocket连接断开?
|
||||
**A:**
|
||||
1. 检查服务器是否运行:`ps aux | grep agent.py`
|
||||
2. 确认端口未被占用:`netstat -tlnp | grep 8765`
|
||||
3. 检查防火墙设置
|
||||
4. 查看服务器日志
|
||||
5. 考虑使用 `--host 0.0.0.0` 允许外部连接
|
||||
|
||||
### Q: 如何让AI使用特定技能?
|
||||
### Q: 如何让 AI 使用特定技能?
|
||||
**A:** 在对话中明确指示:
|
||||
```
|
||||
使用 search_skills 搜索天气相关技能,然后用 run_command 执行它
|
||||
```
|
||||
|
||||
### Q: 内存或日志占用过大?
|
||||
**A:**
|
||||
1. 调整日志配置:
|
||||
```json
|
||||
"log_max_size": 5242880, // 5MB
|
||||
"log_backup_count": 3
|
||||
```
|
||||
**A:**
|
||||
1. 调整日志配置:`log_max_size`、`log_backup_count`
|
||||
2. 清理旧对话归档
|
||||
3. 定期清理工具结果缓存
|
||||
|
||||
### Q: WebSocket 连接断开?
|
||||
**A:**
|
||||
1. 检查服务器是否运行:`ps aux | grep main.py`
|
||||
2. 确认端口未被占用:`netstat -tlnp | grep 8765`
|
||||
3. 检查防火墙设置
|
||||
4. 查看服务器日志:`tail -f ~/.aibox/ai.log`
|
||||
5. 考虑使用 `--host 0.0.0.0` 允许外部连接
|
||||
|
||||
## 📄 更新日志
|
||||
|
||||
### v2.0 (当前版本)
|
||||
- ✅ 新增 `update_soul` 工具(智能体自我进化)
|
||||
- ✅ 新增 `create_skill` 工具(AI自主创建技能)
|
||||
- ✅ 新增 `create_skill` 工具(AI 自主创建技能)
|
||||
- ✅ 新增 `install_skill` 工具(技能安装指引)
|
||||
- ✅ 新增 `set_proxy` / `get_proxy` 工具(SOCKS5 代理)
|
||||
- ✅ 新增 `task_process` 消息类型(任务执行过程广播)
|
||||
- ✅ 支持Markdown格式技能文件
|
||||
- ✅ 对话压缩支持Token数阈值判断
|
||||
- ✅ 开始新对话时完全重置AI状态并重新加载提示词
|
||||
- ✅ 智能体主动结束对话后重置状态
|
||||
- ✅ 任务执行时显示AI完整处理过程
|
||||
- ✅ 增强的流式输出支持(思考内容显示)
|
||||
- ✅ 技能热重载支持(`reload_skills`)
|
||||
- ✅ 记忆搜索支持分页
|
||||
- ✅ 任务调度器支持即时任务
|
||||
- ✅ 支持 Markdown 格式技能文件
|
||||
- ✅ 对话压缩支持 Token 数阈值判断
|
||||
- ✅ 技能热重载(`reload_skills`)
|
||||
- ✅ 记忆搜索支持分页、任务调度器支持即时任务
|
||||
- ✅ 命令执行支持超时控制
|
||||
|
||||
### v1.0
|
||||
- ✅ WebSocket服务器支持
|
||||
- ✅ 文件操作、网页访问、记忆管理
|
||||
- ✅ 任务调度系统
|
||||
- ✅ 扩展技能支持
|
||||
- ✅ 多语言支持
|
||||
- ✅ 对话管理
|
||||
- ✅ WebSocket 服务器、文件/网页/记忆操作、任务调度、扩展技能、多语言、对话管理
|
||||
|
||||
## 📝 开发指南
|
||||
## 📝 开发指南补充
|
||||
|
||||
### 添加新工具
|
||||
|
||||
1. 继承 `Tool` 基类
|
||||
2. 实现 `get_name()`, `get_description()`, `get_parameters()`, `execute()`
|
||||
3. 可选实现 `execute_streaming()` 支持流式输出
|
||||
4. 在 `DeepSeekChat._register_tools()` 中注册
|
||||
|
||||
```python
|
||||
class MyTool(Tool):
|
||||
def get_name(self) -> str:
|
||||
return "my_tool"
|
||||
|
||||
def get_description(self) -> str:
|
||||
return "我的自定义工具"
|
||||
|
||||
def get_parameters(self) -> Dict:
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"param": {"type": "string"}
|
||||
},
|
||||
"required": ["param"]
|
||||
}
|
||||
|
||||
def execute(self, param: str, **kwargs) -> str:
|
||||
return f"执行结果: {param}"
|
||||
```
|
||||
|
||||
### 添加新命令
|
||||
|
||||
在 `WebSocketHandler.handle_command()` 中添加:
|
||||
|
||||
```python
|
||||
elif command == "/mycommand":
|
||||
result = "命令执行结果"
|
||||
await self.send_message(websocket, {
|
||||
"type": "command_result",
|
||||
"content": result
|
||||
})
|
||||
```
|
||||
### 扩展技能格式
|
||||
技能文件命名与格式详见 [SKILLS_FORMAT.md](SKILLS_FORMAT.md):可执行脚本需支持 `--help`,Markdown 技能首行为简介。
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交Issue和Pull Request!
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
@@ -646,4 +633,4 @@ MIT License
|
||||
|
||||
---
|
||||
|
||||
**注意**:本项目需要DeepSeek API Key,请确保遵守API使用条款。
|
||||
**注意**:本项目需要 DeepSeek API Key,请确保遵守 API 使用条款。
|
||||
@@ -10,7 +10,7 @@ rich>=13.0.0
|
||||
colorama>=0.4.6
|
||||
|
||||
# voice
|
||||
SpeechRecognition>=3.10.0
|
||||
pyttsx3>=2.90
|
||||
pyaudio>=0.2.13
|
||||
#SpeechRecognition>=3.10.0
|
||||
#pyttsx3>=2.90
|
||||
#pyaudio>=0.2.13
|
||||
# sudo apt install libportaudio2 portaudio19-dev
|
||||
@@ -198,7 +198,6 @@ def fetch_emails(limit=10, folder="INBOX", mark_seen=False, since_days=None):
|
||||
|
||||
status, messages = imap.select(folder)
|
||||
if status != 'OK':
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return False, f"❌ 无法打开邮件夹 {folder}"
|
||||
|
||||
@@ -209,7 +208,6 @@ def fetch_emails(limit=10, folder="INBOX", mark_seen=False, since_days=None):
|
||||
|
||||
status, message_ids = imap.search(None, search_criteria)
|
||||
if status != 'OK':
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return False, "❌ 搜索邮件失败"
|
||||
|
||||
@@ -217,7 +215,6 @@ def fetch_emails(limit=10, folder="INBOX", mark_seen=False, since_days=None):
|
||||
total = len(msg_ids)
|
||||
|
||||
if total == 0:
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return True, f"📭 邮件夹 '{folder}' 中没有邮件"
|
||||
|
||||
@@ -275,7 +272,6 @@ def fetch_emails(limit=10, folder="INBOX", mark_seen=False, since_days=None):
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
imap.close()
|
||||
imap.logout()
|
||||
|
||||
result = f"📨 邮件夹 '{folder}' 中共有 {total} 封邮件,显示最新 {len(emails)} 封:\n\n"
|
||||
@@ -308,13 +304,11 @@ def read_email(msg_id, folder="INBOX", mark_seen=True):
|
||||
|
||||
status, messages = imap.select(folder)
|
||||
if status != 'OK':
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return False, f"❌ 无法打开邮件夹 {folder}"
|
||||
|
||||
status, msg_data = imap.fetch(str(msg_id).encode(), '(RFC822)')
|
||||
if status != 'OK':
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return False, f"❌ 无法获取邮件 ID: {msg_id}"
|
||||
|
||||
@@ -351,7 +345,6 @@ def read_email(msg_id, folder="INBOX", mark_seen=True):
|
||||
if not mark_seen:
|
||||
imap.store(str(msg_id).encode(), '-FLAGS', '\\Seen')
|
||||
|
||||
imap.close()
|
||||
imap.logout()
|
||||
|
||||
result = f"📧 邮件详情 (ID: {msg_id})\n"
|
||||
@@ -391,7 +384,6 @@ def list_folders():
|
||||
|
||||
status, folders = imap.list()
|
||||
if status != 'OK':
|
||||
imap.close()
|
||||
imap.logout()
|
||||
return False, "❌ 无法获取邮件夹列表"
|
||||
|
||||
@@ -403,7 +395,6 @@ def list_folders():
|
||||
folder_name = parts[1].strip('"')
|
||||
folder_list.append(folder_name)
|
||||
|
||||
imap.close()
|
||||
imap.logout()
|
||||
|
||||
result = "📂 邮件夹列表:\n"
|
||||
@@ -447,7 +438,6 @@ def get_email_stats():
|
||||
except:
|
||||
continue
|
||||
|
||||
imap.close()
|
||||
imap.logout()
|
||||
|
||||
result = f"📊 邮箱统计 ({username})\n"
|
||||
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
# ITIL - 定时任务
|
||||
你接到一份运维工作,现在创建一个定时任务,每 30分钟 执行一次,立即开始第一次执行。
|
||||
你接到一份运维工作,现在创建一个定时任务,每 30分钟 执行一次,半分钟后开始第一次执行。
|
||||
|
||||
# ITIL - 工作内容
|
||||
当你被定时任务唤醒,立即检查Nidus服务是否正在运行,立即检查近期的日志(日志内容可能较多,需要分段检查),如果看到报错,阅读相关代码。然后生成报告,如果可以的话,向管理员发送邮件警告。
|
||||
当你被定时任务唤醒,立即检查Nidus服务是否正在运行,立即检查近期的日志(日志内容可能较多,需要分段检查),如果看到报错,阅读相关代码。然后生成报告,如果可以的话,向管理员发送邮件警告,管理员的邮箱位于~/.cert/my_mail.txt。
|
||||
|
||||
注意:
|
||||
1. **不要试图修改文件内容**。
|
||||
2. **量力而行**,如果尝试了几次无果,就可以开始写总结了。
|
||||
3. **不要在定时任务中再次启动相同的定时任务**,通常它是循环进行的。
|
||||
|
||||
# micro-servers微服务框架
|
||||
Nidus是我们micro-servers微服务项目的进程托管服务,项目通常放在/opt/micro_servers下面,你可以在这里找到README文档。通常每个微服务模块,单独是一个文件夹,里面都是有README.md的,你可以在定位到报错模块时阅读相关内容。
|
||||
@@ -0,0 +1,38 @@
|
||||
# prjs
|
||||
nn/
|
||||
bot
|
||||
bot?
|
||||
test_*
|
||||
|
||||
.git/
|
||||
.vscode/
|
||||
.idea/
|
||||
.cache/
|
||||
|
||||
#
|
||||
*.so
|
||||
*.o
|
||||
*.cjo
|
||||
*.dll
|
||||
*.exe
|
||||
*.lib
|
||||
*.exp
|
||||
main
|
||||
|
||||
# cangjie
|
||||
build/
|
||||
target/
|
||||
output/
|
||||
module-resolve.json
|
||||
module-lock.json
|
||||
cjpm.lock
|
||||
.cjpm-history
|
||||
*.macrocall
|
||||
*.gcda
|
||||
*.gcno
|
||||
|
||||
|
||||
# 其他
|
||||
log
|
||||
report
|
||||
tmp
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 卿松
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,60 @@
|
||||
MODE ?= cpu
|
||||
VERSION := 0.0.2
|
||||
APP_NAME := bot
|
||||
CC:= gcc
|
||||
CXX:= g++
|
||||
NVCC:= nvcc
|
||||
LIBS := -lpthread
|
||||
FLAGS := -O3 -pipe #-Wall -Werror
|
||||
|
||||
#-----------
|
||||
all: libi2c4cj.so libsnd4cj.so libmt4cj.so libfft4cj.so #libcnn4cj.so
|
||||
|
||||
# I2C
|
||||
libi2c4cj.so: src/cpp/I2C.c
|
||||
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
|
||||
|
||||
# SOUND
|
||||
libsnd4cj.so: src/cpp/snd.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
|
||||
|
||||
# cpu nn
|
||||
ifeq ($(MODE), cpu)
|
||||
libmt4cj.so: src/cpp/Matrix.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
|
||||
endif
|
||||
|
||||
ifeq ($(MODE), cuda)# GPU矩阵
|
||||
libmt4cj.so: src/cuda/matrix.cu
|
||||
#$(NVCC) -arch=compute_61 -code=sm_61 -O3 -Xcompiler -fPIC -shared src/cuda/matrix.cu -o libmt4cj.so
|
||||
$(NVCC) -fPIC -shared src/cuda/matrix.cu -o libmt4cj.so
|
||||
|
||||
endif
|
||||
|
||||
# 快速傅里叶变换fft
|
||||
libfft4cj.so: src/cpp/fft1.c src/cpp/fft.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC $^ -lm -o $@
|
||||
|
||||
# 卷积神经网络cnn
|
||||
libcnn4cj.so: src/cpp/cnn/cnn_i.cpp conv.o pooling.o ga_pooling.o flatten.o imgl.o
|
||||
$(CC) $(FLAGS) -std=c++17 -shared -fPIC $^ -lm -o $@
|
||||
|
||||
conv.o: src/cpp/cnn/conv.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
|
||||
|
||||
pooling.o: src/cpp/cnn/pooling.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
|
||||
|
||||
ga_pooling.o: src/cpp/cnn/ga_pooling.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
|
||||
|
||||
flatten.o: src/cpp/cnn/flatten.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
|
||||
|
||||
imgl.o: src/cpp/cnn/imgl.cpp
|
||||
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
|
||||
|
||||
|
||||
clear:
|
||||
rm -f *.o
|
||||
rm -f *.so
|
||||
@@ -0,0 +1,134 @@
|
||||
# bot4cj
|
||||
|
||||
bot4cj是基于仓颉和 Linux I2C 接口的机器人硬件脚本解析器。
|
||||
|
||||
## 依赖库
|
||||
libc、libpthread、liblua-5.4
|
||||
|
||||
You may need this
|
||||
|
||||
``` bash
|
||||
sudo ln -s /usr/lib/aarch64-linux-gnu/liblua5.4.so /usr/lib/aarch64-linux-gnu/liblua.so
|
||||
sudo ln -s /usr/lib/x86_64-linux-gnu/liblua5.4.so /usr/lib/x86_64-linux-gnu/liblua.so
|
||||
```
|
||||
|
||||
## 项目介绍
|
||||
想要得到一个机器人软件通常比一个普通的软件更为困难,这是因为这个世界上的机器人五花八门,所以它们的软件往往都是独特的,所以我想写一种用于描述机器人硬件的通用的脚本。
|
||||
|
||||
`bot4cj` 基于仓颉编程语言和 Linux I2C 接口,提供了便捷的硬件控制能力,支持pca9685、mpu6050,我通常使用树莓派作为宿主机。
|
||||
|
||||
## 解析器
|
||||
|
||||
树莓派通过I2C接口连接pca9685后使用如下方法使用:
|
||||
|
||||
```bash
|
||||
cat ss/test.mt | cjpm run # 蜘蛛机器人脚本
|
||||
```
|
||||
|
||||
## 动作脚本
|
||||
|
||||
项目中包含的动作脚本在ss文件夹下面,这些动作脚本的适配机器人型号如下
|
||||
|
||||
```textplain
|
||||
# PCA9685
|
||||
1号舵机(s1) => 左下水平关节
|
||||
2号舵机(s2) => 左下垂直关节
|
||||
3号舵机(s3) => 右下水平关节
|
||||
4号舵机(s4) => 右下垂直关节
|
||||
5号舵机(s5) => 右上水平关节
|
||||
6号舵机(s6) => 右上垂直关节
|
||||
7号舵机(s7) => 左上水平关节
|
||||
8号舵机(s8) => 左上垂直关节
|
||||
9号舵机(s9) => 云台1
|
||||
10号舵机(s10) => 云台2
|
||||
扬声器(spk) => 仅负责延时
|
||||
```
|
||||
|
||||
适用的机器人建模文件[仓库链接](https://github.com/gopsc/Dashun)
|
||||
|
||||
### 机器人脚本语法(.mt)
|
||||
|
||||
**.bot文件是旧版动作脚本**
|
||||
|
||||
机器人脚本是行列式表格,每列之间以一个或多个空格、制表符分隔,每行以分号(;)结尾。
|
||||
|
||||
每个矩阵对象以@name 行开头,@ 行结束。
|
||||
|
||||
|
||||
示例:
|
||||
|
||||
```mt
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
|
||||
# 定义I2C硬件
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
|
||||
# 定义pca9685硬件
|
||||
@PCA9685
|
||||
name I2C path;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
|
||||
# 定义mpu6050硬件
|
||||
@MPU6050
|
||||
name I2C path;
|
||||
mpu6050-0 i2c-1 0x69;
|
||||
mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
# 定义扬声器硬件
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
# 定义舵机硬件
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵为硬件变量输入参数
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
90 - - - - - - - - - - 20; # left-behind
|
||||
|
||||
45 - - - - - - - - - 0 500; # stand
|
||||
- - 90 - - - 45 - - - - 20; # right-behind
|
||||
|
||||
- - 45 - - - - - - - 200 500; # stand
|
||||
- - - - 90 - - - - - 0 20; # right-front
|
||||
|
||||
- - - - 45 - - - - - - 500; # stand
|
||||
- - - - - - 90 - - - - 20; # left-front
|
||||
|
||||
- - - - - - 45 - - - - 500; # stand
|
||||
- - - - - - - - - - - 20; # stand
|
||||
@
|
||||
|
||||
```
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
export cjStackSize=64MB
|
||||
export cjHeapSize=64MB
|
||||
exec cjpm run --run-args="$@"
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
export cjHeapSize=16MB
|
||||
cjpm update
|
||||
exec cjpm build
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
[package]
|
||||
cjc-version = "1.1.3"
|
||||
name = "bot4cj"
|
||||
description = "nothing here"
|
||||
version = "0.0.3"
|
||||
target-dir = ""
|
||||
src-dir = ""
|
||||
output-type = "executable"
|
||||
compile-option = "-l c -l pthread -l lua" # Linux
|
||||
override-compile-option = ""
|
||||
link-option = ""
|
||||
package-configuration = {}
|
||||
|
||||
[dependencies]
|
||||
#ctype4cj = { git = "https://gitcode.com/qingss0/ctype4cj.git", branch="main" }
|
||||
ctype4cj = "1.0.3"
|
||||
#pthread4cj = { git = "https://gitcode.com/qingss0/pthread4cj.git", branch="main"}
|
||||
pthread4cj = "1.0.0"
|
||||
#spi4cj = { git = "https://gitcode.com/qingss0/spi4cj.git", branch="main"}
|
||||
spi4cj = "1.0.0"
|
||||
#lua4cj = { git = "https://gitcode.com/qingss0/lua4cj.git", branch="main" }
|
||||
lua4cj = "1.0.0"
|
||||
# 这种x86机器人开发板感觉不经常使用,我没用过,不知道里面的i2c调用是不是参数是一样的
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
[target.x86_64-unknown-linux-gnu.dependencies]
|
||||
[target.x86_64-unknown-linux-gnu.bin-dependencies]
|
||||
path-option = ["${CANGJIE_STDX_PATH}/linux_x86_64_cjnative/static/stdx"]
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
[target.aarch64-unknown-linux-gnu.dependencies]
|
||||
[target.aarch64-unknown-linux-gnu.bin-dependencies]
|
||||
path-option = ["${CANGJIE_STDX_PATH}/linux_aarch64_cjnative/static/stdx"]
|
||||
|
||||
# 为什么不支持Windows了?
|
||||
# 因为新版我们加载了Linux的glibc,通常Windows中没有这个
|
||||
#[target.x86_64-w64-mingw32]
|
||||
# [target.x86_64-w64-mingw32.bin-dependencies]
|
||||
# path-option = ["${CANGJIE_STDX_PATH}\\windows_x86_64_cjnative\\dynamic\\stdx"]
|
||||
@@ -0,0 +1,11 @@
|
||||
nvcc -shared -o libmt4cj.dll src/cuda/matrix.cu -allow-unsupported-compiler
|
||||
#nvcc -shared -o libmt4cj.dll src/cpp/Matrix.cpp -allow-unsupported-compiler
|
||||
nvcc -shared -o libi2c4cj.dll src/cpp/I2C.c -allow-unsupported-compiler
|
||||
nvcc -shared -o libfft4cj.dll src/cpp/fft.cpp src/cpp/Matrix.cpp src/cpp/fft1.c -allow-unsupported-compiler
|
||||
nvcc -shared -o conv.lib -c src/cpp/cnn/conv.cpp -allow-unsupported-compiler
|
||||
nvcc -shared -o imgl.lib -c src/cpp/cnn/imgl.cpp -allow-unsupported-compiler
|
||||
nvcc -shared -o pooling.lib -c src/cpp/cnn/pooling.cpp -allow-unsupported-compiler
|
||||
nvcc -shared -o ga_pooling.lib -c src/cpp/cnn/ga_pooling.cpp -allow-unsupported-compiler
|
||||
nvcc -shared -o flatten.lib -c src/cpp/cnn/flatten.cpp -allow-unsupported-compiler
|
||||
nvcc -shared --std=c++17 -o libcnn4cj.dll src/cpp/cnn/cnn_i.cpp conv.lib imgl.lib pooling.lib ga_pooling.lib flatten.lib -allow-unsupported-compiler
|
||||
nvcc -shared -o libsnd4cj.dll src/cpp/snd.cpp -allow-unsupported-compiler
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,325 @@
|
||||
-- ADS1115模数转换器 硬件脚本(测试)
|
||||
|
||||
|
||||
-- 这个函数是辅助用的,它能够打印一张表出来
|
||||
function print_table(t, indent)
|
||||
indent = indent or 0
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
print(string.rep(' ', indent) .. k .. ":")
|
||||
print_table(v, indent + 2)
|
||||
else
|
||||
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ADS1115_ADDR = 0x48 -- ADS1115的I2C地址
|
||||
ADS1115_REG_CONVERSION = 0x00 -- 转换结果(只读)
|
||||
ADS1115_REG_CONFIG = 0x01 -- 配置寄存器(读/写)
|
||||
ADS1115_REG_LO_THRESH = 0x02 -- 低阈值比较
|
||||
ADS1115_REG_HI_THRESH = 0x03 -- 高阈值比较
|
||||
-- ====================================
|
||||
-- ADS1115 配置寄存器 bit 定义(16位)
|
||||
-- ====================================
|
||||
-- BIT15: OS[0] => 单次转换启动位(1=开始)
|
||||
-- BIT14-12: MUX[2:0] => 输入多路选择
|
||||
-- BIT11-9: PGA[2:0] => 可编程增益放大器
|
||||
-- BIT8: MODE => 0=连续转换,1=单次(省电)
|
||||
-- BIT7-5: DR[2:0] => 数据速率
|
||||
-- BIT4: COMP_MODE => 比较模式(0=传统)
|
||||
-- BIT3: COMP_POL => 比较极性(0=低有效)
|
||||
-- BIT2: COMP_LAT => 比较锁存(0=不锁存)
|
||||
-- BIT1-0: COMP_QUE => 比较器队列(11=禁用)
|
||||
--
|
||||
-- ==== MUX 输入通道 ====
|
||||
ADS1115_MUX_AIN0_AIN1 = 0x0000 -- 差分:P=AIN0,N=AIN1(默认)
|
||||
ADS1115_MUX_AIN0_AIN3 = 0x1000 -- 差分:P=AIN0,N=AIN3
|
||||
ADS1115_MUX_AIN1_AIN3 = 0x2000 -- 差分:P=AIN1,N=AIN3
|
||||
ADS1115_MUX_AIN2_AIN3 = 0x3000 -- 差分:P=AIN2,N=AIN3
|
||||
ADS1115_MUX_AIN0_GND = 0x4000 -- 单端:AIN0
|
||||
ADS1115_MUX_AIN1_GND = 0x5000 -- 单端:AIN1
|
||||
ADS1115_MUX_AIN2_GND = 0x6000 -- 单端:AIN2
|
||||
ADS1115_MUX_AIN3_GND = 0x7000 -- 单端:AIN3
|
||||
|
||||
-- ==== PGA 增益(满量程范围)====
|
||||
ADS1115_PGA_6_144V = 0x0000 -- ±6.144V (1LSB=0.1875mV)
|
||||
ADS1115_PGA_4_096V = 0x0200 -- ±4.096V (1LSB=0.125mV)
|
||||
ADS1115_PGA_2_048V = 0x0400 -- ±2.048V (1LSB=0.0625mV)(默认)
|
||||
ADS1115_PGA_1_024V = 0x0600 -- ±1.024V (1LSB=0.03125mV)
|
||||
ADS1115_PGA_0_512V = 0x0800 -- ±0.512V (1LSB=0.015625mV)
|
||||
ADS1115_PGA_0_256V = 0x0A00 -- ±0.256V (1LSB=0.0078125mV)
|
||||
|
||||
-- ==== 数据速率 ====
|
||||
ADS1115_DR_8SPS = 0x0000 -- 8 SPS
|
||||
ADS1115_DR_16SPS = 0x0020 -- 16 SPS
|
||||
ADS1115_DR_32SPS = 0x0040 -- 32 SPS
|
||||
ADS1115_DR_64SPS = 0x0060 -- 64 SPS
|
||||
ADS1115_DR_128SPS = 0x0080 -- 128 SPS (默认)
|
||||
ADS1115_DR_250SPS = 0x00A0 -- 250 SPS
|
||||
ADS1115_DR_475SPS = 0x00C0 -- 475 SPS
|
||||
ADS1115_DR_860SPS = 0x00E0 -- 860 SPS
|
||||
|
||||
-- ==== 比较器队列(COMP_QUE)====
|
||||
ADS1115_COMP_QUE_1 = 0x0000 -- 1次后触发
|
||||
ADS1115_COMP_QUE_2 = 0x0001 -- 2次后触发
|
||||
ADS1115_COMP_QUE_4 = 0x0002 -- 4次后触发
|
||||
ADS1115_COMP_QUE_DISABLE = 0x0003 -- 禁用比较器(默认)
|
||||
|
||||
-- ==== 操作模式 ====
|
||||
ADS1115_MODE_CONTINUOUS = 0x0000 -- 连续转换
|
||||
ADS1115_MODE_SINGLE = 0x0100 -- 单次转换(默认,省电)
|
||||
|
||||
-- ==== 启动转换 ====
|
||||
ADS1115_OS_START = 0x8000 -- 启动单次转换
|
||||
|
||||
-- ADS1115 配置上下文
|
||||
ads1115_t = {
|
||||
addr = 0x00,
|
||||
config = 0x00, -- 16bits
|
||||
fs_valtage = 0.0 -- 满量程电压(取决于PGA)
|
||||
}
|
||||
|
||||
-- PGA对应的满量程电压(单位:V)
|
||||
pga_fs = {
|
||||
6.144, -- ADS1115_PGA_6_144V [0]
|
||||
4.096, -- ADS1115_PGA_4_096V [1]
|
||||
2.048, -- ADS1115_PGA_2_048V [2]
|
||||
1.024, -- ADS1115_PGA_1_024V [3]
|
||||
0.512, -- ADS1115_PGA_0_512V [4]
|
||||
0.256 -- ADS1115_PGA_0_256V [5]
|
||||
}
|
||||
|
||||
-- PGA 增益对应的满量程电压名
|
||||
pga_names = {
|
||||
"±6.144V", -- [0]
|
||||
"±4.096V", -- [1]
|
||||
"±2.048V", -- [2]
|
||||
"±1.024V", -- [3]
|
||||
"±0.512V", -- [4]
|
||||
"±0.256V" -- [5]
|
||||
}
|
||||
|
||||
-- 数据速率字符串
|
||||
dr_names = {
|
||||
"8 SPS", -- [0]
|
||||
"16 SPS", -- [1]
|
||||
"32 SPS", -- [2]
|
||||
"64 SPS", -- [3]
|
||||
"128 SPS", -- [4]
|
||||
"250 SPS", -- [5]
|
||||
"475 SPS", -- [6]
|
||||
"860 SPS" -- [7]
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- I2C写2字节(大端)
|
||||
function i2c_write_word(fd, reg, val)
|
||||
i2c_write_reg(fd, reg + 0, (val >> 8) | 0xFF)
|
||||
i2c_write_reg(fd, reg + 1, val & 0xFF)
|
||||
end
|
||||
|
||||
--
|
||||
-- 【配置脚本】
|
||||
-- FIXME(20260602-qing): 异常处理
|
||||
function build(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua builder func get a table --------")
|
||||
print_table(data)
|
||||
print("----------------------------------------------")
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查文件句柄参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查地址参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
-- 写进配置表
|
||||
ads1115.addr = addr
|
||||
|
||||
|
||||
-- 开始配置PCA9685
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 重启PCA9685
|
||||
i2c_write_reg(fd, 0x00, 0x80)
|
||||
|
||||
-- 等待振荡器启动
|
||||
milli_sleep(5)
|
||||
|
||||
i2c_write_reg(fd, 0x00, 0x10) -- 进入SLEEP模式(允许改频率)
|
||||
i2c_write_reg(fd, 0xFE, math.floor(prescale))
|
||||
i2c_write_reg(fd, 0x00, 0x20) -- 退出SLEEP模式
|
||||
|
||||
-- 等待振荡器启动
|
||||
milli_sleep(5)
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
-- 【销毁PCA9685的脚本】
|
||||
-- 重置PCA9685
|
||||
-- NOTE(20260612-qing): 其实可以不需要,因为我们只是清空设置
|
||||
function destroy(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua ender func get a table --------")
|
||||
print_table(data)
|
||||
print("--------------------------------------------")
|
||||
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查fd参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查address参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
-- 准备配置
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 重启PCA9685
|
||||
i2c_write_reg(fd, 0x00, 0x80)
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- 【调用PCA9685】
|
||||
-- 将一个舵机通道转动到一个角度
|
||||
-- NOTE(20260612-qing): 接收一个参数(表),返回一个结果(表)
|
||||
function call(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua caller func get a table --------")
|
||||
print_table(data)
|
||||
print("---------------------------------------------")
|
||||
|
||||
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查fd参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查address参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
local channel = data.channel
|
||||
|
||||
-- 检查channel参数是否存在
|
||||
if channel == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: channel"
|
||||
}
|
||||
end
|
||||
|
||||
local angle = data.angle
|
||||
|
||||
-- 检查angle参数是否存在
|
||||
if angle == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: angle"
|
||||
}
|
||||
end
|
||||
|
||||
print(string.format("Lua hardware drive set angle: %d", angle))
|
||||
|
||||
-- 设置I2C地址
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 计算需要的参数
|
||||
local min = 0.5 -- 0度对应0.5ms
|
||||
local max = 2.5 -- 180度对应2.5ms
|
||||
local width = min + (max - min) * (angle / 180.0)
|
||||
local offval = math.floor(width * 4096.0 / 20.0)
|
||||
local reg = 0x06 + 4 * channel
|
||||
|
||||
-- 写I2C寄存器
|
||||
i2c_write_reg(fd, reg + 0, (0 >> 0) & 0xFF) -- 1 * 8
|
||||
i2c_write_reg(fd, reg + 1, (0 >> 8) & 0xFF) -- 2 * 8
|
||||
i2c_write_reg(fd, reg + 2, (offval >> 0) & 0xFF) -- 3 * 8
|
||||
i2c_write_reg(fd, reg + 3, (offval >> 8) & 0xFF) -- 4 * 8
|
||||
|
||||
-- 返回字典
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
-- ADS1256 模数转换器 - SPI接口
|
||||
|
||||
function build(data)
|
||||
end
|
||||
|
||||
function destroy(data)
|
||||
end
|
||||
|
||||
function call(data)
|
||||
end
|
||||
@@ -0,0 +1,223 @@
|
||||
-- PCA9685 PWM控制板 硬件脚本(测试)
|
||||
|
||||
|
||||
-- 这个函数是辅助用的,它能够打印一张表出来
|
||||
function print_table(t, indent)
|
||||
indent = indent or 0
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
print(string.rep(' ', indent) .. k .. ":")
|
||||
print_table(v, indent + 2)
|
||||
else
|
||||
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-- 【配置脚本】
|
||||
-- 该脚本负责初始化PCA9685
|
||||
-- FIXME(20260602-qing): 异常处理
|
||||
function build(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua builder func get a table --------")
|
||||
print_table(data)
|
||||
print("----------------------------------------------")
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查文件句柄参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查地址参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
-- 设置PWM频率
|
||||
local freq = 50.0
|
||||
local prescale_val = 25000000.0 / (4096.0 * freq) - 1.0 -- 25MHz主频
|
||||
local prescale = prescale_val + 0.5
|
||||
|
||||
-- 开始配置PCA9685
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 重启PCA9685
|
||||
i2c_write_reg(fd, 0x00, 0x80)
|
||||
|
||||
-- 等待振荡器启动
|
||||
milli_sleep(5)
|
||||
|
||||
i2c_write_reg(fd, 0x00, 0x10) -- 进入SLEEP模式(允许改频率)
|
||||
i2c_write_reg(fd, 0xFE, math.floor(prescale))
|
||||
i2c_write_reg(fd, 0x00, 0x20) -- 退出SLEEP模式
|
||||
|
||||
-- 等待振荡器启动
|
||||
milli_sleep(5)
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
-- 【销毁PCA9685的脚本】
|
||||
-- 重置PCA9685
|
||||
-- NOTE(20260612-qing): 其实可以不需要,因为我们只是清空设置
|
||||
function destroy(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua ender func get a table --------")
|
||||
print_table(data)
|
||||
print("--------------------------------------------")
|
||||
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查fd参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查address参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
-- 准备配置
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 重启PCA9685
|
||||
i2c_write_reg(fd, 0x00, 0x80)
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- 【调用PCA9685】
|
||||
-- 将一个舵机通道转动到一个角度
|
||||
-- NOTE(20260612-qing): 接收一个参数(表),返回一个结果(表)
|
||||
function call(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua caller func get a table --------")
|
||||
print_table(data)
|
||||
print("---------------------------------------------")
|
||||
|
||||
|
||||
local fd = data.fd
|
||||
|
||||
-- 检查fd参数是否存在
|
||||
if fd == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: fd"
|
||||
}
|
||||
end
|
||||
|
||||
local addr = data.address
|
||||
|
||||
-- 检查address参数是否存在
|
||||
if addr == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: addr"
|
||||
}
|
||||
end
|
||||
|
||||
addr = tonumber(addr)
|
||||
|
||||
local channel = data.channel
|
||||
|
||||
-- 检查channel参数是否存在
|
||||
if channel == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: channel"
|
||||
}
|
||||
end
|
||||
|
||||
local angle = data.angle
|
||||
|
||||
-- 检查angle参数是否存在
|
||||
if angle == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Argument: angle"
|
||||
}
|
||||
end
|
||||
|
||||
print(string.format("Lua hardware drive set angle: %d", angle))
|
||||
|
||||
-- 设置I2C地址
|
||||
i2c_set_addr(fd, addr)
|
||||
|
||||
-- 计算需要的参数
|
||||
local min = 0.5 -- 0度对应0.5ms
|
||||
local max = 2.5 -- 180度对应2.5ms
|
||||
local width = min + (max - min) * (angle / 180.0)
|
||||
local offval = math.floor(width * 4096.0 / 20.0)
|
||||
local reg = 0x06 + 4 * channel
|
||||
|
||||
-- 写I2C寄存器
|
||||
i2c_write_reg(fd, reg + 0, (0 >> 0) & 0xFF) -- 1 * 8
|
||||
i2c_write_reg(fd, reg + 1, (0 >> 8) & 0xFF) -- 2 * 8
|
||||
i2c_write_reg(fd, reg + 2, (offval >> 0) & 0xFF) -- 3 * 8
|
||||
i2c_write_reg(fd, reg + 3, (offval >> 8) & 0xFF) -- 4 * 8
|
||||
|
||||
-- 返回字典
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
-- SPI 硬件
|
||||
|
||||
--
|
||||
SPI_SUCCESS = 0
|
||||
SPI_FAILED = 1
|
||||
|
||||
-- 全局文件句柄
|
||||
fd = 0
|
||||
|
||||
-- 这个函数是辅助用的,它能够打印一张表出来
|
||||
function print_table(t, indent)
|
||||
indent = indent or 0
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
print(string.rep(' ', indent) .. k .. ":")
|
||||
print_table(v, indent + 2)
|
||||
else
|
||||
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function build(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- SPI.Lua builder func get a table --------")
|
||||
print_table(data)
|
||||
print("--------------------------------------------------")
|
||||
local path = data.path
|
||||
|
||||
-- 检查文件句柄参数是否存在
|
||||
if path == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: path"
|
||||
}
|
||||
end
|
||||
|
||||
-- 模式
|
||||
local spi_mode = data.spi_mode
|
||||
if spi_mode == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: spi_mode"
|
||||
}
|
||||
end
|
||||
print(spi_mode)
|
||||
spi_mode = tonumber(spi_mode)
|
||||
|
||||
-- 比特
|
||||
local bits = data.bits
|
||||
if bits == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: bits"
|
||||
}
|
||||
end
|
||||
bits = tonumber(bits)
|
||||
|
||||
|
||||
-- 速率
|
||||
local speed = data.speed
|
||||
if speed == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: speed"
|
||||
}
|
||||
end
|
||||
speed = tonumber(speed)
|
||||
|
||||
--
|
||||
local delay = data.delay
|
||||
if delay == nil then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: delay"
|
||||
}
|
||||
end
|
||||
delay = tonumber(delay)
|
||||
|
||||
-- 开始执行
|
||||
fd = spi_open(path)
|
||||
if fd <= 0 then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Failed to open SPI file"
|
||||
}
|
||||
end
|
||||
|
||||
local res = spi_setup(fd, spi_mode, bits, speed, delay)
|
||||
if res ~= 0 then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Failed to setup SPI device"
|
||||
}
|
||||
end
|
||||
|
||||
return {
|
||||
result = "OK",
|
||||
fd = fd
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
function destroy(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- SPI.Lua ender func get a table --------")
|
||||
print_table(data)
|
||||
print(string.format("now fd: %i", fd))
|
||||
print("------------------------------------------------")
|
||||
|
||||
spi_close(fd)
|
||||
fd = -1
|
||||
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function call(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- SPI.Lua caller func get a table --------")
|
||||
print_table(data)
|
||||
print("-------------------------------------------------")
|
||||
|
||||
return {
|
||||
fd = fd
|
||||
}
|
||||
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
-- PCA9685 PWM控制板 硬件脚本(测试)
|
||||
|
||||
-- 这个函数是辅助用的,它能够打印一张表出来
|
||||
function print_table(t, indent)
|
||||
indent = indent or 0
|
||||
for k, v in pairs(t) do
|
||||
if type(v) == 'table' then
|
||||
print(string.rep(' ', indent) .. k .. ":")
|
||||
print_table(v, indent + 2)
|
||||
else
|
||||
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
-- 【配置脚本】
|
||||
function build(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua builder func get a table --------")
|
||||
print_table(data)
|
||||
print("----------------------------------------------")
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
-- 【销毁用的脚本】
|
||||
function destroy(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua ender func get a table --------")
|
||||
print_table(data)
|
||||
print("--------------------------------------------")
|
||||
|
||||
|
||||
-- 返回成功信号
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
|
||||
-- 【调用的脚本】
|
||||
function call(data)
|
||||
|
||||
-- 检查该参数是否是一张表
|
||||
if type(data) ~= "table" then
|
||||
return {
|
||||
result = "ERR",
|
||||
message = "Invalid Arguments: table"
|
||||
}
|
||||
end
|
||||
|
||||
print("-------- Lua caller func get a table --------")
|
||||
print_table(data)
|
||||
print("---------------------------------------------")
|
||||
|
||||
-- 返回字典
|
||||
return {
|
||||
result = "OK"
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
cjpm run --run-args="$($args -join ' ')"
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 10 - 10 - 10 - 10 - - - 20;
|
||||
- 20 - 20 - 20 - 20 - - - 20;
|
||||
- 30 - 30 - 30 - 30 - - - 20;
|
||||
45 0 45 0 45 0 45 0 90 90 10 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- - - - - - - - - - 400 200;
|
||||
0 - - - - - - 150 - - 1000 1000;
|
||||
45 - - - - - - 0 - - 10 20;
|
||||
@
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 45 - - - 45 - - - - - 100;
|
||||
90 - - - 90 - - - - - - 50; # stand
|
||||
#
|
||||
- 0 - - - 0 - - - - - 50;
|
||||
- - - 45 - - - 45 - - - 100;
|
||||
45 - - - 45 - - - - - - 10;
|
||||
- - 0 - - - 0 - - - - 50;
|
||||
#
|
||||
- - - 0 - - - 0 - - - 50;
|
||||
- 45 - - - 45 - - - - - 100;
|
||||
- - 45 - - - 45 - - - - 10;
|
||||
- 0 - - - 0 - - - - - 50;
|
||||
@
|
||||
@@ -0,0 +1,53 @@
|
||||
# ONE STEP
|
||||
#
|
||||
# legs | neck | free | speaker | delay
|
||||
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
|
||||
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
|
||||
!- - - - - - - - - 90 - - - - - - - -; # 4-legs spider robot
|
||||
!- - - - - - - - - - - - - - - - - -; # offset
|
||||
45 - 45 - 45 - 45 - 90 90 - - - - - - 1000 100; # stand
|
||||
- - - - - - - - - - - - - - - - 1000 -; # stand
|
||||
|
||||
- - - 30 - - - 30 - - - - - - - - 0 50; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
0 - 90 - 90 - 0 - - - - - - - - - - -; # right front forward & left behind forward
|
||||
- 0 - - - 0 - - - - - - - - - - - -; # right front down & left behind down
|
||||
|
||||
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
0 - 90 - 90 - 0 - - - - - - - - - - -; #
|
||||
- 0 - - - 0 - - - - - - - - - - - -; #
|
||||
|
||||
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
0 - 90 - 90 - 0 - - - - - - - - - - -; #
|
||||
- 0 - - - 0 - - - - - - - - - - - -; #
|
||||
|
||||
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
0 - 90 - 90 - 0 - - - - - - - - - - -; #
|
||||
- 0 - - - 0 - - - - - - - - - - - -; #
|
||||
|
||||
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
0 - 90 - 90 - 0 - - - - - - - - - - -; #
|
||||
- 0 - - - 0 - - - - - - - - - - - -; #
|
||||
|
||||
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
|
||||
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
|
||||
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
|
||||
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
|
||||
45 - 45 - 45 - 45 - - - - - - - - - - -; # right front forward & left behind forward
|
||||
- 0 - - - 0 - - - - - - - - - - - -; # right front down & left behind down
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 10 - 10 - 10 - 10 - - - 20;
|
||||
- 15 - 15 - 15 - 15 - - - 20;
|
||||
- 20 - 20 - 20 - 20 - - - 20;
|
||||
- 25 - 25 - 25 - 25 - - - 20;
|
||||
- 30 - 30 - 30 - 30 - - - 20;
|
||||
@
|
||||
@@ -0,0 +1,26 @@
|
||||
# SNNEAK
|
||||
# legs | neck | free | delay
|
||||
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
|
||||
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -10; # offset
|
||||
# 俯卧撑
|
||||
#
|
||||
90 0 90 0 90 0 90 0 90 90 - - - - - - 1000 200;
|
||||
- 5 - 5 - 5 - 5 - - - - - - - - 0 50;
|
||||
- 10 - 10 - 10 - 10 - - - - - - - - - -;
|
||||
- 15 - 15 - 15 - 15 - - - - - - - - - -;
|
||||
- 20 - 20 - 20 - 20 - - - - - - - - - -;
|
||||
- 25 - 25 - 25 - 25 - - - - - - - - - -;
|
||||
- 30 - 30 - 30 - 30 - - - - - - - - - -;
|
||||
- 35 - 35 - 35 - 35 - - - - - - - - - -;
|
||||
|
||||
#r
|
||||
- 35 - 35 - 35 - 35 - - - - - - - - 1000 200;
|
||||
- 30 - 30 - 30 - 30 - - - - - - - - 0 50;
|
||||
- 25 - 25 - 25 - 25 - - - - - - - - - -;
|
||||
- 20 - 20 - 20 - 20 - - - - - - - - - -;
|
||||
- 15 - 15 - 15 - 15 - - - - - - - - - -;
|
||||
- 10 - 10 - 10 - 10 - - - - - - - - - -;
|
||||
- 5 - 5 - 5 - 5 - - - - - - - - - -;
|
||||
- 0 - 0 - 0 - 0 - - - - - - - - - -;
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- - - - - 0 - - - - 400 200;
|
||||
- - 0 - - 150 - - - - 1000 1000;
|
||||
- - 45 - - 0 - - - - 10 20;
|
||||
@
|
||||
@@ -0,0 +1,56 @@
|
||||
# ONE STEP
|
||||
# legs | neck | free | delay
|
||||
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
|
||||
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -; # offset
|
||||
|
||||
|
||||
45 - 45 - 45 - 45 - 90 90 - - - - - - 1000 100; # stand
|
||||
- - - - - - - - - - - - - - - - - -; # stand
|
||||
|
||||
#----------------------------------------------------------------
|
||||
- 30 - - - 25 40 - - - - - - - - - 0 50;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
50 - 0 - 0 - 90 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
|
||||
#----------------------------------------------------------------
|
||||
|
||||
- 30 90 - - 25 40 - - - - - - - - - - -;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
50 - 0 - 0 - 90 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
|
||||
- 30 90 - - 25 40 - - - - - - - - - - -;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
50 - 0 - 0 - 90 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
|
||||
- 30 90 - - 25 40 - - - - - - - - - - -;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
50 - 0 - 0 - 90 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
|
||||
- 30 90 - - 25 40 - - - - - - - - - - -;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
50 - 0 - 0 - 90 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
|
||||
#----------------------------------------------------------------
|
||||
- 30 90 - - 25 40 - - - - - - - - - - -;
|
||||
0 - - - 90 - - - - - - - - - - - - -;
|
||||
- 0 - - - 0 - - - - - - - - - - - -;
|
||||
- - - 25 - - - 30 - - - - - - - - - -;
|
||||
45 - 45 - 45 - 45 - - - - - - - - - - -;
|
||||
- - - 0 - - - 0 - - - - - - - - - -;
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 10 - 10 - 10 - 10 - - - 50;
|
||||
- 20 - 20 - 20 - 20 - - - -;
|
||||
- 30 - 30 - 30 - 30 - - - -;
|
||||
- 20 - 20 - 20 - 20 - - - -;
|
||||
- 10 - 10 - 10 - 10 - - - -;
|
||||
- 0 - 0 - 0 - 0 - - - 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,56 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
#i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
90 - - - - - - - - - - 20; # left-behind
|
||||
|
||||
45 - - - - - - - - - 0 500; # stand
|
||||
- - 90 - - - 45 - - - - 20; # right-behind
|
||||
|
||||
- - 45 - - - - - - 45 200 500; # stand
|
||||
- - - - 90 - - - - - 0 20; # right-front
|
||||
|
||||
- - - - 45 - - - - - - 500; # stand
|
||||
- - - - - - 90 - - - - 20; # left-front
|
||||
|
||||
- - - - - - 45 - - - - 500; # stand
|
||||
- - - - - - - - - 90 - 20; # stand
|
||||
@
|
||||
@@ -0,0 +1,21 @@
|
||||
# 无硬件情况下的测试脚本
|
||||
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@TEST
|
||||
name parameter0 patameter1;
|
||||
t0 p0 p1;
|
||||
t1 p2 p3;
|
||||
@
|
||||
|
||||
|
||||
@Action
|
||||
t0.A t1.F;
|
||||
10 20;
|
||||
@
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 10 - 10 - - - - - - - 50;
|
||||
- 20 - 20 - - - - - - - -;
|
||||
- 30 - 30 - - - - - - - -;
|
||||
- 20 - 20 - - - - - - - -;
|
||||
- 10 - 10 - - - - - - - -;
|
||||
- 0 - 0 - - - - - - - 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- - - - - 10 - 10 - - - 50;
|
||||
- - - - - 20 - 20 - - - -;
|
||||
- - - - - 30 - 30 - - - -;
|
||||
- - - - - 20 - 20 - - - -;
|
||||
- - - - - 10 - 10 - - - -;
|
||||
- - - - - 0 - 0 - - - 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- 10 - - - - - 10 - - - 50;
|
||||
- 20 - - - - - 20 - - - -;
|
||||
- 30 - - - - - 30 - - - -;
|
||||
- 20 - - - - - 20 - - - -;
|
||||
- 10 - - - - - 10 - - - -;
|
||||
- 0 - - - - - 0 - - - 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,57 @@
|
||||
|
||||
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
|
||||
@Alias
|
||||
name alias;
|
||||
A angle;
|
||||
F frequency;
|
||||
@
|
||||
|
||||
@I2C
|
||||
name path;
|
||||
i2c-0 /dev/i2c-0;
|
||||
i2c-1 /dev/i2c-1;
|
||||
@
|
||||
|
||||
@PCA9685
|
||||
name I2C address;
|
||||
pca9685-0 i2c-1 0x40;
|
||||
@
|
||||
|
||||
@MPU6050_BIND
|
||||
name I2C addr;
|
||||
#mpu6050-0 i2c-1 0x69;
|
||||
#mpu6050-1 i2c-1 0x68;
|
||||
#mpu6050-2 i2c-0 0x68;
|
||||
#mpu6050-3 i2c-0 0x69;
|
||||
@
|
||||
|
||||
@Speaker
|
||||
name channels sample_rate volumn max_f min_f;
|
||||
spk 2 48000 0.6 5000 -;
|
||||
@
|
||||
|
||||
@Servo
|
||||
name max min offset controller channel;
|
||||
s1 90 - - pca9685-0 0; # rb0
|
||||
s2 180 - - - 1; # rb1
|
||||
s3 -90 - - - 2; # lb0
|
||||
s4 -180 - - - 3; # lb1
|
||||
s5 90 - - - 4; # lf0
|
||||
s6 180 - - - 5; # lf1
|
||||
s7 -90 - - - 6; # rf0
|
||||
s8 -180 - - - 7; # rf1
|
||||
s9 180 - - - 8; # cam0
|
||||
s10 180 - - - 9; # cam1
|
||||
@
|
||||
|
||||
# 行动矩阵
|
||||
@Action
|
||||
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
|
||||
45 - 45 - 45 - 45 - 90 90 10 200; # stand
|
||||
- - - 10 - 10 - - - - - 50;
|
||||
- - - 20 - 20 - - - - - -;
|
||||
- - - 30 - 30 - - - - - -;
|
||||
- - - 20 - 20 - - - - - -;
|
||||
- - - 10 - 10 - - - - - -;
|
||||
- - - 0 - 0 - - - - - 200; # stand
|
||||
@
|
||||
@@ -0,0 +1,29 @@
|
||||
# SNNEAK
|
||||
# legs | neck | free | delay
|
||||
#lb rb rf lf
|
||||
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
|
||||
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
|
||||
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
|
||||
|
||||
# 俯卧撑
|
||||
#
|
||||
- 0 - 0 - 0 - 0 90 90 - - - - - - 1000 200;
|
||||
- 5 - 5 - 5 - 5 - - - - - - - - 0 50;
|
||||
- 10 - 10 - 10 - 10 - - - - - - - - - -;
|
||||
- 15 - 15 - 15 - 15 - - - - - - - - - -;
|
||||
- 20 - 20 - 20 - 20 - - - - - - - - - -;
|
||||
- 25 - 25 - 25 - 25 - - - - - - - - - -;
|
||||
- 30 - 30 - 30 - 30 - - - - - - - - - -;
|
||||
- 35 - 35 - 35 - 35 - - - - - - - - - -;
|
||||
|
||||
#r
|
||||
- 35 - 35 - 35 - 35 - - - - - - - - 1000 200;
|
||||
- 30 - 30 - 30 - 30 - - - - - - - - 0 50;
|
||||
- 25 - 25 - 25 - 25 - - - - - - - - - -;
|
||||
- 20 - 20 - 20 - 20 - - - - - - - - - -;
|
||||
- 15 - 15 - 15 - 15 - - - - - - - - - -;
|
||||
- 10 - 10 - 10 - 10 - - - - - - - - - -;
|
||||
- 5 - 5 - 5 - 5 - - - - - - - - - -;
|
||||
- 0 - 0 - 0 - 0 - - - - - - - - - -;
|
||||
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
package bot4cj
|
||||
import std.io.*
|
||||
import std.math.*
|
||||
import std.convert.*
|
||||
import std.collection.*
|
||||
import bot4cj.math.*
|
||||
|
||||
/* -------- 矩阵编程语言 - 基本对象 --------
|
||||
* TODO:增加储存到文件系统的功能
|
||||
* TODO:神经网络层转换为矩阵对象
|
||||
-------- */
|
||||
class MatrixObject {
|
||||
private var name: String = "" /* -------- 名字 -------- */
|
||||
private var tmp = ArrayList<Rune>() /* ---- rune buf 字符缓冲区 ---- */
|
||||
private var grp = ArrayList<String>() /* ---- one line 整行缓冲区 ---- */
|
||||
private var values = ArrayList<ArrayList<String>>() /* ---- 原始矩阵 ---- */
|
||||
|
||||
/* -------- 是否已命名 -------- */
|
||||
public func hasName(): Bool { !(name == "") }
|
||||
|
||||
/* --------- 获得名字 --------- */
|
||||
public func getName(): String { return name }
|
||||
|
||||
/* ------- 取动作的长度 ------- */
|
||||
public mut prop size: Int64 {
|
||||
get() { values.size }
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
/* -------- 取动作的列数 -------- */
|
||||
public mut prop col: Int64 {
|
||||
get() {
|
||||
if (this.size > 0) {
|
||||
return values[0].size }
|
||||
else { return 0 }
|
||||
}
|
||||
|
||||
|
||||
set(_) {}
|
||||
}
|
||||
|
||||
/* ------- 获取矩阵元素,只能取,不能修改 ------- */
|
||||
public operator func [](row: Int64, col: Int64): String {
|
||||
return values[row][col]
|
||||
}
|
||||
|
||||
/** -- 在某一行中寻找某一个字符串的列号 -- */
|
||||
public func indexOfCol(row: Int64, key: String): Int64 {
|
||||
for (col in 0..values[row].size) {
|
||||
if (values[row][col] == key) {
|
||||
return col }
|
||||
}
|
||||
|
||||
/* ---- 没找到 ---- */
|
||||
throw IllegalArgumentException("The key does not found.")
|
||||
}
|
||||
|
||||
/* ----- 储存对象 ----- */
|
||||
public func save(output: StringWriter<OutputStream>) {
|
||||
output.writeln("@${getName()}")
|
||||
for (vec in values) {
|
||||
for (item in vec) {
|
||||
output.write(item)
|
||||
output.write(" ")
|
||||
}
|
||||
output.writeln(";")
|
||||
}
|
||||
output.writeln("@")
|
||||
}
|
||||
|
||||
/* -------- 添加字符 -------- */
|
||||
public func addRune(c: Rune) { tmp.add(c) }
|
||||
|
||||
/* -------- 提交名字 -------- */
|
||||
public func submitName() {
|
||||
if (tmp.size == 0) { return }
|
||||
let s = String(tmp)
|
||||
tmp.clear()
|
||||
name = s
|
||||
}
|
||||
|
||||
/* -------- 提交元素 -------- */
|
||||
public func submitItem() {
|
||||
if (tmp.size == 0) { return }
|
||||
let s = String(tmp)
|
||||
tmp = ArrayList<Rune>()
|
||||
grp.add(s)
|
||||
}
|
||||
|
||||
/* -------- 提交行 -------- */
|
||||
public func submitLine(count: Int64) {
|
||||
if (grp.size == 0) { return }
|
||||
if (!values.size == 0 && !grp.size == values[values.size - 1].size)
|
||||
{ throw DoesNotSync(count) }
|
||||
values.add(grp)
|
||||
grp = ArrayList<String>()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------- 对Nn1进行扩展 ---------------- */
|
||||
extend Nn1 {
|
||||
/* -------- 转换为MatrixObject对象 -------- */
|
||||
public func toMto(): MatrixObject {
|
||||
let mto = MatrixObject()
|
||||
return mto
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 矩阵式解析器 --------
|
||||
* TODO(20260514-qing): 这个类输入流数据,输出MatrixObject对象
|
||||
* 它是与其深度绑定的,我倾向于将其与MatrixObject融合
|
||||
* 即持有HashMap<String, ArrayList<ArrayList<String>>> */
|
||||
class MatrixParser {
|
||||
|
||||
/* ---- 静态判断Option<Rune>是否有值 ----
|
||||
- 该方法用于文本文件遍历时判空 */
|
||||
private static func hasValue(co: Option<Rune>): Bool {
|
||||
match(co) {
|
||||
case Some(_) => true
|
||||
case None => false
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 主初始化器(构造函数) --------
|
||||
- 设置目标输入流 */
|
||||
private var input: StringReader<InputStream>
|
||||
public init(input: StringReader<InputStream>) {
|
||||
this.input = input }
|
||||
|
||||
/* -------- 切换目标输入流 -------- */
|
||||
public func reset(input: StringReader<InputStream>) {
|
||||
this.input = input }
|
||||
|
||||
/* -------- 解析器 - 循环不变式 ----------------------
|
||||
* -------- 初始化: ss是一个动作脚本的输入流 --------
|
||||
* -------- 保持: ss是动作脚本剩余部分的输入流 ------
|
||||
* -------- 终止: 当输入流为空时,循环终止 ---------- */
|
||||
private var obj = MatrixObject()
|
||||
public func parse(): Option<MatrixObject> {
|
||||
var count = 1i64 /* 行计数器始于1 */
|
||||
flag = false /* 重置结束标志 */
|
||||
this.obj = MatrixObject() /* 创建一个新的对象,一次只解析一个对象 */
|
||||
var so = input.read() /* 获取初始状态 */
|
||||
while (!flag && hasValue(so)) {
|
||||
try {
|
||||
let s = so.getOrThrow()
|
||||
print(s) /* debug */
|
||||
count = _next(s, count) /* 进行解析解析 */
|
||||
so = input.read() /* 为下一次循环提供条件 */
|
||||
}
|
||||
catch (exp: BufOverTheLine) { throw exp }
|
||||
catch (exp: IllegalArgumentException) { throw IllegalArgumentException("Parse failed at line ${count}") }
|
||||
catch (exp: Exception) { throw UnknowExcept(count) } /* FIXME: 还是暴露出原来的错误位置来更好? */
|
||||
}
|
||||
|
||||
/* 如果没有新的矩阵式对象(意外终止)则返回空 */
|
||||
if (!obj.hasName()) { return None }
|
||||
return obj /* 否则为解析成功正常退出,返回解析到的矩阵式对象 */
|
||||
}
|
||||
|
||||
private var flag = false /* 结束标志 */
|
||||
private var nl: Bool = true /* new line */
|
||||
private var cm: Bool = false /* comment */
|
||||
private var nm: Bool = false /* name-line */
|
||||
private func _next(c: Rune, ct: Int64): Int64 {
|
||||
var count = ct
|
||||
/* ------------ COMMENTS ------------ */
|
||||
if (!cm && c == r'#') { cm = true } /* 注释开始时 */
|
||||
else if (cm && c == r'\n') { /* 注释结束时 */
|
||||
nl = true
|
||||
cm = false
|
||||
count += 1
|
||||
}
|
||||
else if (cm && c != r'\n') { ; } /* 注释进行时 */
|
||||
/* FIXME: EOF情况 */
|
||||
/* ------------ new line ------------ */
|
||||
else if (nl && (c == r' ' || c == r'\t' || c == r'\r')) { ; } /* 新行空字符,维持现状 */
|
||||
else if (nl && c == r'\n') { /* 新行空行 */
|
||||
count += 1
|
||||
}
|
||||
else if (nl && c == r'@' && !obj.hasName()) {
|
||||
nl = false
|
||||
nm = true
|
||||
}
|
||||
else if (nl && c == r'@' && obj.hasName()) {
|
||||
flag =true
|
||||
nl = false
|
||||
nm = true
|
||||
}
|
||||
else if (nl && c != r'@') { /* 新行内容 */
|
||||
nl = false
|
||||
obj.addRune(c)
|
||||
}
|
||||
/* ------------- not nl ------------- */
|
||||
else if (!nl && nm && (c == r' ' || c == r'\t' || c == r'\n')) { /* 添加名字 */
|
||||
obj.submitName()
|
||||
nl = true
|
||||
nm = false
|
||||
}
|
||||
else if (!nl && !nm && (c == r' ' || c == r'\t' || c == r"\n")) { /* 添加命名项 */
|
||||
obj.submitItem()
|
||||
if (c == r"\n") { count += 1 }
|
||||
}
|
||||
else if (!nl && c == r';') { /* 普通行结束 */
|
||||
obj.submitItem()
|
||||
obj.submitLine(count)
|
||||
nl = true
|
||||
}
|
||||
else if (!nl) { // 非空格、非制表符、非分号
|
||||
obj.addRune(c)
|
||||
}
|
||||
|
||||
/* -------- 返回行号 -------- */
|
||||
return count
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* -------- 矩阵式储存器 --------
|
||||
* - 接收矩阵式的哈希表(HashMap<String, MatrixObject>)
|
||||
* - 以及一个输出流(在构造函数)。 */
|
||||
class MatrixSaver {
|
||||
private let stringWriter: StringWriter<OutputStream>
|
||||
public init(output: OutputStream) {
|
||||
stringWriter = StringWriter(output) }
|
||||
public func save(C: LinkedList<MatrixObject>) {
|
||||
for (item in C) {
|
||||
item.save(stringWriter)
|
||||
stringWriter.writeln() /* 对象之间空一行 */
|
||||
stringWriter.flush() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
/* --------------------------------------------------------- */
|
||||
/* TODO(20260516-qing): 继续完善异常种类 */
|
||||
|
||||
/* -------- 解析异常 -------- */
|
||||
open class ParseExcept <: Exception {
|
||||
public ParseExcept(msg: String, line: Int64) {
|
||||
super('${msg}. we got line: ${line}') }
|
||||
}
|
||||
|
||||
/* -------- 缓冲区过长 -------- */
|
||||
class BufOverTheLine <: ParseExcept {
|
||||
BufOverTheLine(count: Int64) {
|
||||
super('Buffer length over the line', count) }
|
||||
}
|
||||
|
||||
/* -------- 行没对齐 -------- */
|
||||
class DoesNotSync <: ParseExcept {
|
||||
DoesNotSync(count: Int64) {
|
||||
super("Matrix Line Does Not Sync", count) }
|
||||
}
|
||||
|
||||
/* -------- 未知异常 -------- */
|
||||
class UnknowExcept <: ParseExcept {
|
||||
UnknowExcept(count: Int64) {
|
||||
super("I don't understand what happened", count) }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* i2c - 用户界面
|
||||
*
|
||||
* 这里已经提供了I2C通用接口,可以将上层代码移动到仓颉下面
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#ifdef __linux__
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h> /* 其中宏的取值可能具有平台特异性 */
|
||||
#include <i2c/smbus.h>
|
||||
#endif
|
||||
|
||||
#include "bot4cj.h" /* 有没有可能在仓颉代码中解析C头文件取得宏 */
|
||||
|
||||
|
||||
/*
|
||||
* 初始化i2c设备
|
||||
*
|
||||
* 动态指定设备文件
|
||||
*
|
||||
* 返回打开的文件句柄或者错误码
|
||||
*/
|
||||
#ifdef __linux__
|
||||
int16_t I2C_init(const char* filepath) {
|
||||
int16_t fd;
|
||||
if ((fd = open(filepath, O_RDWR)) < 0) {
|
||||
perror("Failed to open I2C bus");
|
||||
return BOT4CJ_I2C_OPENED_FAILED;
|
||||
}
|
||||
else {
|
||||
return fd;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int16_t I2C_init(const char* filepath) {
|
||||
return BOT4CJ_I2C_OPENED_FAILED;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 释放I2C设备文件 */
|
||||
#ifdef __linux__
|
||||
void I2C_free(int16_t fd) {
|
||||
close(fd);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
void I2C_free(int16_t fd) {
|
||||
/* Windows平台下无需操作 */
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* 设置I2C地址 */
|
||||
#ifdef __linux__
|
||||
int I2C_setAddr(int16_t fd, uint8_t addr) {
|
||||
if (ioctl(fd, I2C_SLAVE, addr) < 0) {
|
||||
perror("Failed to set I2C address");
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
else {
|
||||
return BOT4CJ_I2C_OK;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int I2C_setAddr(int16_t fd, uint8_t addr) {
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 写寄存器 */
|
||||
#ifdef __linux__
|
||||
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
|
||||
/*uint8_t buf[] = {reg, val};
|
||||
if (write(fd, buf, 2) != 2) {
|
||||
perror("I2C write error");
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
else {
|
||||
return BOT4CJ_I2C_OK;
|
||||
}*/
|
||||
|
||||
return i2c_smbus_write_byte_data(fd, reg, val);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
|
||||
return BOT4CJ_I2C_ERR;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 读寄存器 失败返回0 */
|
||||
#ifdef __linux__
|
||||
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
|
||||
|
||||
uint8_t value;
|
||||
|
||||
/*if (write(fd, ®, 1) != 1) {
|
||||
perror("I2C read error");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (read(fd, &value, 1)!= 1) {
|
||||
perror("I2C read error");
|
||||
return 0;
|
||||
}*/
|
||||
|
||||
//return value;
|
||||
|
||||
int8_t a = i2c_smbus_read_byte_data(fd, reg);
|
||||
return *(uint8_t *)& a;
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 读寄存器 - 有符号
|
||||
* 失败返回0 */
|
||||
#ifdef __linux__
|
||||
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
|
||||
|
||||
int8_t value;
|
||||
|
||||
//if (write(fd, ®, 1) != 1) {
|
||||
//perror("I2C read error");
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//if (read(fd, &value, 1)!= 1) {
|
||||
//perror("I2C read error");
|
||||
//return 0;
|
||||
//}
|
||||
|
||||
//return value;
|
||||
|
||||
return i2c_smbus_read_byte_data(fd, reg);
|
||||
}
|
||||
#endif
|
||||
#ifdef _WIN64
|
||||
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,516 @@
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include "Matrix.hh"
|
||||
|
||||
namespace qing {
|
||||
|
||||
template <typename T>
|
||||
Matrx<T>::Matrx(): row(0), col(0) {}
|
||||
|
||||
template <typename T>
|
||||
Matrx<T>::Matrx(const uint64_t row, const uint64_t col)
|
||||
: row(row), col(col) { /* 该构造函数用于从头开始构造 */
|
||||
arr = std::vector<T>(col * row);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T>::Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src)
|
||||
: row(row), col(col) { /* 该构造函数用于通过已有的vector进行构造 */
|
||||
arr = src;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& Matrx<T>::get(uint64_t row, uint64_t col) const { /* 访问器 - 访问矩阵元素 */
|
||||
return arr[col * this->row + row];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T& Matrx<T>::witch(uint64_t row, uint64_t col) { /* 访问器 - 访问矩阵元素 */
|
||||
return arr[col * this->row + row];
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::set(uint64_t row, uint64_t col, T v) { /* 设置矩阵元素 */
|
||||
arr[col * this->row + row] = v;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const std::vector<T>& Matrx<T>::get_data() const{ /* 访问器 - 用于获取原始数据 */
|
||||
return arr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const uint64_t Matrx<T>::get_col() const { /* 获取列数 */
|
||||
return col;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const uint64_t Matrx<T>::get_row() const { /* 获取行数 */
|
||||
return row;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Matrx<T>::has_same_shape(const Matrx<T>& other) { /* 两个矩阵是否具有相同形状 */
|
||||
return other.get_col() == this->get_col() && other.get_row() == this->get_row();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Matrx<T>::has_dot_shape(const Matrx<T>& other) { /* 具有矩阵乘法的形状 */
|
||||
return this->get_col() == other.get_row();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::assert_same_shape(const Matrx<T>& other) { /* 断言两个矩阵具有相同形状 */
|
||||
if (!has_same_shape(other))
|
||||
throw std::invalid_argument("The shapes of matrix are not same.");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Matrx<T>::assert_dot_shape(const Matrx<T>& other) { /* 断言两个矩阵具有点积前置形状 */
|
||||
if (!has_dot_shape(other)) {
|
||||
throw std::invalid_argument("The shapes of matrix can not be dot.");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator+(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it += *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator+(T& item) { /* 矩阵加法广播 */
|
||||
auto len = this->get_data().size();
|
||||
auto v0 = std::vector<T> (len, item);
|
||||
auto matrix = (*this) + Matrx(get_row(), get_col(), v0);
|
||||
|
||||
//auto matrix = *this;
|
||||
//for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
// *it += item;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator-(const Matrx<T>& other) { /* 操作符重载 - 矩阵减法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector <T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it -= *it_or;
|
||||
return Matrx{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator-(T& item) { /* 操作符重载 - 矩阵减法广播 */
|
||||
auto len = this->get_data().size();
|
||||
auto v0 = std::vector<T> (len, item);
|
||||
auto matrix = (*this) - Matrx(get_row(), get_col(), v0);
|
||||
|
||||
//Matrx<T> matrix = *this;
|
||||
//for (auto it = arr.begin(); it != arr.end(); ++it)
|
||||
// *it -= item;
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::dot(const Matrx<T>& other) { /* 操作符重载 - 矩阵乘法 */
|
||||
//assert_dot_shape(other); /* 它们的形状是否能够点积 */
|
||||
auto matrix = Matrx<T>(this->get_row(), other.get_col());
|
||||
for (uint64_t i=0; i<this->get_row(); ++i) /* 点积运算 */
|
||||
for (uint64_t j=0; j<other.get_col(); ++j) {
|
||||
T sum{0};
|
||||
for (uint64_t k=0; k<this->get_col(); ++k)
|
||||
sum += this->get(i, k) * other.get(k, j);
|
||||
matrix.witch(i, j) = sum;
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator*(T& item) { /* 操作符重载 - 矩阵乘法广播 */
|
||||
std::vector<T> vec = this->get_data();
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it)
|
||||
*it *= item;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), vec};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator*(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it *= *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator/(T& item) { /* 操作符重载 - 矩阵除法广播 */
|
||||
std::vector<T> vec = this->get_data();
|
||||
for (auto it = vec.begin(); it != vec.end(); ++it)
|
||||
*it /= item;
|
||||
return Matrx<T>(this->get_row(), this->get_col(), vec);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Matrx<T> Matrx<T>::operator/(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
|
||||
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
|
||||
std::vector<T> v0 = this->get_data();
|
||||
std::vector<T> vec = other.get_data();
|
||||
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
|
||||
*it /= *it_or;
|
||||
return Matrx<T>{this->get_row(), this->get_col(), v0};
|
||||
}
|
||||
|
||||
template class Matrx<float>; /* 显式实例化 */
|
||||
template class Matrx<double>;
|
||||
|
||||
}
|
||||
|
||||
/* FIXME: 形状不合法时返回空指针 */
|
||||
using namespace qing;
|
||||
extern "C" {
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* createMatrix(uint64_t row, uint64_t col) {
|
||||
return (void *) new Matrx<float>(row, col);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void freeMatrix(void* M) {
|
||||
delete (Matrx<float>*) M;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
float* witchMatrix(void *m, uint64_t row, uint64_t col) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return &(M->witch(row, col));
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
float getMatrix(void *m, uint64_t row, uint64_t col) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get(row, col);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void setMatrix(void *m, uint64_t row, uint64_t col, float v) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
M->set(row, col, v);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
uint64_t getrowMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get_row();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
uint64_t getcolMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
return M->get_col();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
bool sameShapeMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
return M->has_same_shape(*S);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
bool dotShapeMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
return M->has_dot_shape(*S);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* plusMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) + (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* plusBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
auto a = new Matrx<float> ((*M) + t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* subMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) - (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void * subBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float> ((*M) - t);
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* multiMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) * (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* multiBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float>((*M) * t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* divMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>((*M) / (*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* divBroadcastMatrix(void *m, float t) {
|
||||
Matrx<float> *M = (Matrx<float> *) m;
|
||||
auto a = new Matrx<float>((*M) / t);
|
||||
return (void*) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* dotMatrix(void *m, void *s) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
Matrx<float> *S = (Matrx<float>*) s;
|
||||
auto a = new Matrx<float>(M->dot(*S));
|
||||
return (void *) a;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* transposeMatrix(void *m) {
|
||||
Matrx<float> *M = (Matrx<float> *)m;
|
||||
auto N= new Matrx<float>(M->get_col(), M->get_row());
|
||||
for (uint64_t row = 0; row< N->get_row(); row++) {
|
||||
for (uint64_t col = 0; col < N->get_col(); col++) {
|
||||
N->witch(row, col) = M->get(col, row);
|
||||
}
|
||||
}
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
/* 正态分布Xavier初始化 */
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
{
|
||||
/* 构建空数组 */
|
||||
std::vector<float> weights(fan_in * fan_out);
|
||||
|
||||
/* 高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算标准差 */
|
||||
float sigma = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建正态分布对象,均值为0,标准差为sigma */
|
||||
std::normal_distribution<float> distribution(0.0f, sigma);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
weights[k++] = distribution(generator);
|
||||
|
||||
/* 构建矩阵 */
|
||||
auto matrix = new Matrx<float>(fan_in, fan_out, weights);
|
||||
return (void *)matrix;
|
||||
}
|
||||
|
||||
/* 均匀分布Xavier初始化函数 */
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
{
|
||||
/* 构建空的数组 */
|
||||
auto weights = std::vector<float>(fan_in * fan_out);
|
||||
|
||||
/* 使用高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
|
||||
float scale = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建均匀分布对象,范围为[-scale, scale] */
|
||||
std::uniform_real_distribution<float> distribution(-scale, scale);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
weights[k++] = distribution(generator);
|
||||
|
||||
/* 构建矩阵对象 */
|
||||
auto matrix = new Matrx<float> (fan_in, fan_out, weights);
|
||||
return (void *)matrix;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Sigmoid_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = 1.0 / (1.0 + std::exp(-x));
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Sigmoid_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& o: v) {
|
||||
o = o * (1.0 - o);
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* ReLU_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? x : 0;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* ReLU_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? 1.0f : 0.0f;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Leaky_ReLU_do(void *m, float alpha) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? x : alpha * x;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Leaky_ReLU_dot(void *m, float alpha) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = x > 0 ? 1.0 : alpha;
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Tanh_do(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
x = std::tanh(x);
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
_declspec(dllexport)
|
||||
#endif
|
||||
void* Tanh_dot(void *m) {
|
||||
Matrx<float> *M = (Matrx<float>*) m;
|
||||
std::vector<float> v = M->get_data();
|
||||
for (float& x: v) {
|
||||
if (x > 20.0) x = 0.0; // tanh(x) ≈ 1, 导数≈0
|
||||
else if (x < -20.0) x = 0.0; // tanh(x) ≈ -1, 导数≈0
|
||||
else {
|
||||
float t = tanh(x);
|
||||
x = 1.0 - t * t;
|
||||
}
|
||||
}
|
||||
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
|
||||
return (void *)N;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
|
||||
___col___
|
||||
/ \
|
||||
+---------+ \
|
||||
| + + + + | | row - 第一维度
|
||||
| + + + + | |
|
||||
+---------+ /
|
||||
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <cstdint>
|
||||
namespace qing {
|
||||
template <class T>
|
||||
class Matrx {
|
||||
public:
|
||||
Matrx();
|
||||
Matrx(const uint64_t row, const uint64_t col);
|
||||
Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src);
|
||||
const T& get(uint64_t row, uint64_t col) const;
|
||||
//-------------旧接口----------------
|
||||
T& witch(uint64_t row, uint64_t col);
|
||||
//-------------新接口----------------
|
||||
void set(uint64_t row, uint64_t col, T v);
|
||||
//-----------------------------------
|
||||
const std::vector<T>& get_data() const;
|
||||
const uint64_t get_col() const;
|
||||
const uint64_t get_row() const;
|
||||
bool has_same_shape(const Matrx<T>& other);
|
||||
bool has_dot_shape(const Matrx<T>& other);
|
||||
void assert_same_shape(const Matrx<T>& other);
|
||||
void assert_dot_shape(const Matrx<T>& other);
|
||||
Matrx<T> operator+(const Matrx<T>& other);
|
||||
Matrx<T> operator+(T& item);
|
||||
Matrx<T> operator-(const Matrx<T>& other);
|
||||
Matrx<T> operator-(T& item);
|
||||
Matrx<T> operator*(const Matrx<T>& other);
|
||||
Matrx<T> operator*(T& item);
|
||||
Matrx<T> operator/(T& item);
|
||||
Matrx<T> operator/(const Matrx<T>& other);
|
||||
Matrx<T> dot(const Matrx<T>& other);
|
||||
|
||||
void save(std::ostream& out) {
|
||||
out << row << " " << col;
|
||||
for (const auto& item : arr) {
|
||||
out << " ";
|
||||
out << item;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t row, col;
|
||||
std::vector<T> arr;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* I2C操作库 - PCA9685 - MPU6050 */
|
||||
#ifndef QING_I2C
|
||||
#define QING_I2C
|
||||
/* I2C */
|
||||
#define BOT4CJ_I2C_OK 0
|
||||
#define BOT4CJ_I2C_ERR 1
|
||||
#define BOT4CJ_I2C_OPENED_FAILED -1
|
||||
|
||||
#ifdef _WIN64
|
||||
#define DLLEXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#endif
|
||||
|
||||
DLLEXPORT int16_t I2C_init(const char* filepath);
|
||||
DLLEXPORT void I2C_free(int16_t fd);
|
||||
DLLEXPORT int I2C_setAddr(int16_t fd, uint8_t addr);
|
||||
DLLEXPORT int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val);
|
||||
DLLEXPORT uint8_t I2C_readReg(int16_t fd, uint8_t reg);
|
||||
DLLEXPORT int8_t I2C_readRegFlag(int16_t fd, uint8_t reg);
|
||||
#endif
|
||||
@@ -0,0 +1,80 @@
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include "fft.h"
|
||||
#include "Matrix.hh"
|
||||
using namespace qing;
|
||||
|
||||
|
||||
|
||||
void functf1d(float A[DN])
|
||||
{
|
||||
unsigned long int i;
|
||||
float dt, tt;
|
||||
float p = 0.1;
|
||||
float freq = 10.0; // 10Hz
|
||||
float fs = DN; // 采样率65536Hz
|
||||
|
||||
dt = 1.0 / fs;
|
||||
|
||||
for(i = 0; i < DN; i++)
|
||||
{
|
||||
tt = (float)i * dt;
|
||||
A[i] = sin(2.0 * M_PI * freq * tt) * p; // 正弦波
|
||||
//A[1][i] = 0.0; // 虚部为0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 初始化FFT数组
|
||||
static void fft_init(float A[2][DN], Matrx<float>& M)
|
||||
{
|
||||
for (int i = 0; i < DN; i++) {
|
||||
float a = M.witch(i, 0);
|
||||
A[0][i] = a;
|
||||
A[1][i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算FFT结果
|
||||
static void fft_result(std::vector<float>& R, float A[2][DN])
|
||||
{
|
||||
for (int i = 0; i < DN/2; i++) {
|
||||
float a = pow(A[0][i], 2) + pow(A[1][i], 2);
|
||||
R[i] = sqrt(a) / (DN/2);
|
||||
//std::cout << i << ":\t" << R[i] << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// 生成正弦波
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* gen_wave()
|
||||
#else
|
||||
void* gen_wave()
|
||||
#endif
|
||||
{
|
||||
std::vector<float> A(DN);
|
||||
functf1d(A.data());
|
||||
return (void *)new Matrx<float>(DN, 1, A);
|
||||
}
|
||||
|
||||
// 开始FFT
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* start_fft(void* M)
|
||||
#else
|
||||
void* start_fft(void* M)
|
||||
#endif
|
||||
{
|
||||
float A[2][DN];
|
||||
std::vector<float> R(N);
|
||||
fft_init(A, *(Matrx<float> *)M);
|
||||
//fft1(A, 1); // 复序列基2 FFT
|
||||
fft1r1(A, 1); // 单实序列
|
||||
fft_result(R, A);
|
||||
return (void*) new Matrx<float>(N/2, 1, R);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#ifndef FFT_H
|
||||
#define FFT_H
|
||||
|
||||
#ifndef N
|
||||
#define N 32768
|
||||
#endif
|
||||
|
||||
#ifndef DN
|
||||
#define DN (N*2)
|
||||
#endif
|
||||
|
||||
#define eps 1.0e-9
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void initial(void);
|
||||
void fft1(float A[2][N], int ifft);
|
||||
unsigned long int inverseq(unsigned long int i1,
|
||||
unsigned long int imax);
|
||||
void fft1r2(float A[2][N], float B[2][N], int ifft);
|
||||
void fft1r1(float A[2][DN], int ifft);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,231 @@
|
||||
/* fft1.c: fast Fourier transform base on 2 for one dimensional
|
||||
complex array, direct and inverse,
|
||||
This program is contributed by Jiang Yong, 2003. 1. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "fft.h"
|
||||
static int ifirst=0, sign=-1;
|
||||
static unsigned M, jup[64], kup[64];
|
||||
static float pi, wr[N], wi[N];
|
||||
/********* initial **************/
|
||||
void initial(void)
|
||||
{
|
||||
unsigned long int i, j, ni;
|
||||
float unit, tt;
|
||||
ni = N;
|
||||
M = 0;
|
||||
while (ni > 1) { ni=ni/2; M=M+1; }
|
||||
ni = 1;
|
||||
for(j=0; j<M; j++) ni = ni * 2;
|
||||
if ((ni-N) != 0)
|
||||
{
|
||||
printf("N isn't compatible! (N => %i, ni -> %lu)\n", N, ni);
|
||||
exit(1);
|
||||
}
|
||||
kup[0] = N/2; /* kup[l] = 2 ^ (M - l - 1) */
|
||||
jup[0] =1;
|
||||
for (j=1;j<M;j++)
|
||||
{
|
||||
kup[j]=kup[j-1]/2;
|
||||
jup[j]=jup[j-1]*2;
|
||||
}
|
||||
pi=4.0*atan((float)1.0);
|
||||
unit=2.0*pi/((float)N);
|
||||
tt=0.0;
|
||||
for(i=0;i<N;i++)
|
||||
{
|
||||
wr[i] = cos(tt);
|
||||
wi[i] = sign * sin(tt);
|
||||
tt = tt + unit;
|
||||
}
|
||||
}
|
||||
/** ifft=1<-->FFT ** ifft=-1<-->IFFT **/
|
||||
void fft1(float A[2][N], int ifft)
|
||||
{
|
||||
unsigned long int l, k, i, j, jl0, jl0k, jl1k, reseq, ij;
|
||||
float wrc, wis, ar, ai, x[2][N];
|
||||
if (ifirst == 0) { initial(), ifirst=1; }
|
||||
if (ifft == -1)
|
||||
for(i=0;i<N;i++) A[1][i] = -A[1][i];
|
||||
for (l=0; l<M; l++)
|
||||
{
|
||||
for (j=0; j<jup[l]; j++)
|
||||
{
|
||||
reseq=inverseq(j, l);
|
||||
ij=kup[l]*reseq;
|
||||
jl0=2*kup[l]*j;
|
||||
wrc = wr[ij];
|
||||
wis = wi[ij];
|
||||
for (k=0; k<kup[l]; k++)
|
||||
{
|
||||
jl0k = jl0 + k;
|
||||
jl1k = jl0 + kup[l] + k;
|
||||
ar = A[0][jl1k] * wrc - A[1][jl1k] * wis;
|
||||
ai = A[0][jl1k] * wis + A[1][jl1k] * wrc;
|
||||
A[0][jl1k] = A[0][jl0k] - ar;
|
||||
A[1][jl1k] = A[1][jl0k] - ai;
|
||||
A[0][jl0k] = A[0][jl0k] + ar;
|
||||
A[1][jl0k] = A[1][jl0k] + ai;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
j=inverseq(i, M);
|
||||
x[0][i] = A[0][j];
|
||||
x[1][i] = A[1][j];
|
||||
}
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
A[0][i] = x[0][i];
|
||||
A[1][i] = x[1][i];
|
||||
}
|
||||
if (ifft == -1)
|
||||
for (i=0; i<N; i++)
|
||||
{
|
||||
A[0][i] = A[0][i]/((float)N);
|
||||
A[1][i] = -A[1][i]/((float)N);
|
||||
}
|
||||
}
|
||||
/*********************************/
|
||||
unsigned long int inverseq(unsigned long int i1,
|
||||
unsigned long int imax)
|
||||
{
|
||||
unsigned long int j, ii, i0, inv;
|
||||
i0 = i1;
|
||||
inv = 0;
|
||||
for (j=0; j < imax; j++)
|
||||
{
|
||||
ii=i0%2;
|
||||
inv=2*inv+ii;
|
||||
i0=(i0-ii)/2;
|
||||
}
|
||||
return (inv);
|
||||
}
|
||||
/* ifft=1 <--> FFT ** ifft=-1 <--> IFFT */
|
||||
void fft1r2(float A[2][N], float B[2][N], int ifft)
|
||||
{
|
||||
unsigned long int k, j;
|
||||
float norm, c[2][N];
|
||||
if (ifft != -1)
|
||||
{
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
c[0][k]=A[0][k];
|
||||
c[1][k]=B[0][k];
|
||||
}
|
||||
fft1(c, 1);
|
||||
A[0][0] = c[0][0];
|
||||
A[1][0] = 0.0;
|
||||
B[0][0] = c[1][0];
|
||||
B[1][0] = 0.0;
|
||||
for (j=1; j<N; j++)
|
||||
{
|
||||
A[0][j] = 0.5 * (c[0][j] + c[0][N-j]);
|
||||
A[1][j] = 0.5 * (c[1][j] - c[1][N-j]);
|
||||
B[0][j] = 0.5 * (c[1][j] + c[1][N-j]);
|
||||
B[1][j] = -0.5 * (c[0][j] - c[0][N-j]);
|
||||
}
|
||||
} /* end if*/
|
||||
else
|
||||
{
|
||||
for (j=0; j<N/2; j++)
|
||||
/* check {xj}, {yj} is to real {Ak}, {Bk} */
|
||||
{
|
||||
if (j == 0) norm = 2.0 * fabs(A[1][0])
|
||||
+ 2.0 * fabs(B[1][0]);
|
||||
else norm = fabs(A[1][j]+A[1][N-j])
|
||||
+ fabs(A[0][j]-A[0][N-j])
|
||||
+ fabs(B[1][j]+B[1][N-j])
|
||||
+ fabs(B[0][j]-B[0][N-j]);
|
||||
if (norm > eps)
|
||||
{
|
||||
printf("The IDFT of the series isn't real!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
for (j=0; j<N; j++)
|
||||
{
|
||||
c[0][j]=A[0][j] -B[1][j];
|
||||
c[1][j]=A[1][j] +B[0][j];
|
||||
}
|
||||
fft1(c, -1);
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
A[0][k] = c[0][k];
|
||||
B[0][k] = c[1][k];
|
||||
A[1][k] = 0.0;
|
||||
B[1][k] = 0.0;
|
||||
}
|
||||
} /* end else */
|
||||
}
|
||||
/* ifft = 1 <--> FFT ** ifft = -1 <--> IFFT */
|
||||
void fft1r1(float A[2][DN], int ifft)
|
||||
{
|
||||
unsigned long int j, k, dk;
|
||||
float norm, c[2][N], d[2][N], tt, hunit, cosj, sinj;
|
||||
hunit = 4.0 * atan(1.0) / (float)N;
|
||||
if ( ifft != -1)
|
||||
{
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
dk = k + k;
|
||||
c[0][k] = A[0][dk];
|
||||
d[0][k] = A[0][dk+1];
|
||||
c[1][k] = 0.0;
|
||||
d[1][k] = 0.0;
|
||||
}
|
||||
fft1r2(c, d, 1);
|
||||
for (j = 0; j < N; j++)
|
||||
{
|
||||
tt = (float)j * hunit;
|
||||
cosj = cos(tt);
|
||||
sinj = sign * sin(tt);
|
||||
A[0][j] = c[0][j] + cosj * d[0][j] - sinj * d[1][j];
|
||||
A[1][j] = c[1][j] + cosj * d[1][j] + sinj * d[0][j];
|
||||
}
|
||||
A[0][N] = c[0][0] - d[0][0];
|
||||
A[1][N] = c[1][0] - d[1][0];
|
||||
for(j=1; j<N; j++)
|
||||
{
|
||||
A[0][DN-j] = A[0][j];
|
||||
A[1][DN-j] = -A[1][j];
|
||||
}
|
||||
} // end if
|
||||
|
||||
else
|
||||
{
|
||||
for (j = 0; j <N; j++) // check {xj} is to real {Ak}
|
||||
{
|
||||
if (j ==0) norm = 2.0 * fabs(A[1][0]);
|
||||
else norm = fabs(A[1][j]+A[1][DN-j])+
|
||||
fabs(A[0][j]-A[0][DN-j]);
|
||||
if (norm > eps)
|
||||
{
|
||||
printf("The IDFT of the series {xj} isn't real!\n");
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
for (j = 0; j <N; j++)
|
||||
{
|
||||
c[0][j]= 0.5 * (A[0][j]+A[0][N+j]);
|
||||
c[1][j]= 0.5 * (A[1][j]+A[1][N+j]);
|
||||
tt = (float) j * hunit;
|
||||
cosj = cos(tt);
|
||||
sinj = -sign*sin(tt);
|
||||
d[0][j]= 0.5 * (cosj * (A[0][j]-A[0][N+j])
|
||||
-sinj*(A[1][j] - A[1][N+j]));
|
||||
}
|
||||
fft1r2(c, d, -1);
|
||||
for (k=0; k<N; k++)
|
||||
{
|
||||
dk = k+k;
|
||||
A[0][dk]=c[0][k];
|
||||
A[0][dk+1]=d[0][k];
|
||||
A[1][dk]=0.0;
|
||||
A[1][dk+1]=0.0;
|
||||
}
|
||||
} // end else
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 一维卡尔曼滤波器
|
||||
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
|
||||
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
|
||||
* 使用示例
|
||||
* extKalman_t p; //定义一个卡尔曼滤波器结构体
|
||||
* float SersorData; //需要进行滤波的数据
|
||||
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
|
||||
* while(1)
|
||||
* {
|
||||
* SersorData = sersor(); //获取数据
|
||||
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
|
||||
* }
|
||||
*/
|
||||
|
||||
#include "kalman.h"
|
||||
|
||||
/**
|
||||
* @name kalmanCreate
|
||||
* @brief 创建一个卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* T_Q:系统噪声协方差
|
||||
* T_R:测量噪声协方差
|
||||
*
|
||||
* @retval none
|
||||
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
|
||||
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
|
||||
*/
|
||||
void KalmanCreate(extKalman_t* p, float T_Q, float T_R)
|
||||
{
|
||||
p->X_last = (float)0;
|
||||
p->P_last = 0;
|
||||
p->Q = T_Q;
|
||||
p->R = T_R;
|
||||
p->A = 1;
|
||||
p->B = 0;
|
||||
p->H = 1;
|
||||
p->X_mid = p->X_last;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KalmanFilter
|
||||
* @brief 卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* dat:待滤波数据
|
||||
* @retval 滤波后的数据
|
||||
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
|
||||
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
|
||||
* 以下是卡尔曼的5个核心公式
|
||||
* 一阶H'即为它本身,否则为转置矩阵
|
||||
*/
|
||||
|
||||
float KalmanFilter(extKalman_t* p, float dat)
|
||||
{
|
||||
p->X_mid = p->A * p->X_last; //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
|
||||
p->P_mid = p->A * p->P_last + p->Q; //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
|
||||
p->kg = p->P_mid / (p->P_mid + p->R); //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
|
||||
p->X_now = p->X_mid + p->kg * (dat - p->X_mid); //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
|
||||
p->P_now = (1 - p->kg) * p->P_mid; //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
|
||||
p->P_last = p->P_now; //状态更新
|
||||
p->X_last = p->X_now;
|
||||
return p->X_now; //输出预测结果x(k|k)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
|
||||
#ifndef _KALMAN_H
|
||||
#define _KALMAN_H
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float X_last; //上一时刻的最优结果 X(k-|k-1)
|
||||
float X_mid; //当前时刻的预测结果 X(k|k-1)
|
||||
float X_now; //当前时刻的最优结果 X(k|k)
|
||||
float P_mid; //当前时刻预测结果的协方差 P(k|k-1)
|
||||
float P_now; //当前时刻最优结果的协方差 P(k|k)
|
||||
float P_last; //上一时刻最优结果的协方差 P(k-1|k-1)
|
||||
float kg; //kalman增益
|
||||
float A; //系统参数
|
||||
float B;
|
||||
float Q;
|
||||
float R;
|
||||
float H;
|
||||
} extKalman_t;
|
||||
|
||||
void KalmanCreate(extKalman_t* p, float T_Q, float T_R);
|
||||
float KalmanFilter(extKalman_t* p, float dat);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,354 @@
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
#pragma comment(lib, "user32.lib")
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#else
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <cmath>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#ifndef M_LN2
|
||||
#define M_LN2 0.693147180559945309417
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows 回调结构
|
||||
struct PlaybackContext {
|
||||
volatile bool done;
|
||||
WAVEHDR* header;
|
||||
};
|
||||
|
||||
// Windows 播放完成回调函数
|
||||
void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
|
||||
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||
if (uMsg == WOM_DONE) {
|
||||
PlaybackContext* ctx = reinterpret_cast<PlaybackContext*>(dwInstance);
|
||||
ctx->done = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
|
||||
#ifdef _WIN32
|
||||
#define SND_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define SND_EXPORT
|
||||
#endif
|
||||
|
||||
SND_EXPORT bool play_frequency(float frequency, int32_t duration_ms,
|
||||
double volume, int32_t channels,
|
||||
int32_t sample_rate) {
|
||||
// 参数验证
|
||||
if (frequency <= 0.0f || duration_ms <= 0 ||
|
||||
volume <= 0.0 || volume > 1.0 ||
|
||||
channels <= 0 || channels > 8 ||
|
||||
sample_rate <= 0) {
|
||||
std::cerr << "Invalid parameters" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 精确计算帧数:使用 round 而非截断
|
||||
// duration_ms / 1000.0 得到秒数,乘以采样率得到帧数
|
||||
int64_t num_frames = static_cast<int64_t>(
|
||||
std::round(static_cast<double>(sample_rate) * duration_ms / 1000.0)
|
||||
);
|
||||
|
||||
if (num_frames <= 0) {
|
||||
std::cerr << "Duration too short to generate any frames" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 计算实际播放时长(用于调试)
|
||||
double actual_duration_ms = static_cast<double>(num_frames) / sample_rate * 1000.0;
|
||||
std::cout << "Requested duration: " << duration_ms << " ms, "
|
||||
<< "Actual duration: " << actual_duration_ms << " ms, "
|
||||
<< "Frames: " << num_frames << std::endl;
|
||||
|
||||
// 计算总采样点数
|
||||
int32_t total_samples = static_cast<int32_t>(num_frames * channels);
|
||||
std::vector<int16_t> buffer(total_samples);
|
||||
|
||||
// 计算振幅(应用对数音量缩放以获得更自然的音量控制)
|
||||
int32_t amplitude = static_cast<int32_t>(32767.0 * volume);
|
||||
|
||||
// 生成正弦波数据
|
||||
for (int64_t i = 0; i < num_frames; i++) {
|
||||
// 使用 double 计算以提高精度
|
||||
double t = static_cast<double>(i) / sample_rate;
|
||||
double value = sin(2.0 * M_PI * frequency * t);
|
||||
|
||||
// 应用淡入淡出以避免咔嗒声
|
||||
int64_t fade_samples = static_cast<int64_t>(sample_rate * 0.005); // 5ms 淡入淡出
|
||||
double fade_factor = 1.0;
|
||||
|
||||
if (i < fade_samples) {
|
||||
// 淡入:从 0 到 1
|
||||
fade_factor = static_cast<double>(i) / fade_samples;
|
||||
} else if (i >= num_frames - fade_samples) {
|
||||
// 淡出:从 1 到 0
|
||||
fade_factor = static_cast<double>(num_frames - 1 - i) / fade_samples;
|
||||
}
|
||||
|
||||
int16_t sample = static_cast<int16_t>(
|
||||
amplitude * value * fade_factor
|
||||
);
|
||||
|
||||
// 填充所有声道
|
||||
for (int32_t ch = 0; ch < channels; ch++) {
|
||||
buffer[i * channels + ch] = sample;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// ===== Windows 实现(带回调,精确计时) =====
|
||||
WAVEFORMATEX wfx;
|
||||
wfx.wFormatTag = WAVE_FORMAT_PCM;
|
||||
wfx.nChannels = channels;
|
||||
wfx.nSamplesPerSec = sample_rate;
|
||||
wfx.wBitsPerSample = 16;
|
||||
wfx.nBlockAlign = channels * 2; // 16-bit = 2 bytes per sample
|
||||
wfx.nAvgBytesPerSec = sample_rate * wfx.nBlockAlign;
|
||||
wfx.cbSize = 0;
|
||||
|
||||
PlaybackContext context;
|
||||
context.done = false;
|
||||
context.header = new WAVEHDR();
|
||||
|
||||
// 打开波形设备,使用回调函数
|
||||
HWAVEOUT hWaveOut;
|
||||
MMRESULT result = waveOutOpen(
|
||||
&hWaveOut,
|
||||
WAVE_MAPPER,
|
||||
&wfx,
|
||||
reinterpret_cast<DWORD_PTR>(waveOutProc), // 回调函数
|
||||
reinterpret_cast<DWORD_PTR>(&context), // 传递给回调的数据
|
||||
CALLBACK_FUNCTION
|
||||
);
|
||||
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Cannot open audio device. Error code: " << result << std::endl;
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 准备 WAVEHDR
|
||||
WAVEHDR& header = *context.header;
|
||||
ZeroMemory(&header, sizeof(WAVEHDR));
|
||||
header.lpData = reinterpret_cast<char*>(buffer.data());
|
||||
header.dwBufferLength = static_cast<DWORD>(total_samples * sizeof(int16_t));
|
||||
header.dwFlags = 0;
|
||||
header.dwLoops = 0;
|
||||
|
||||
result = waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Failed to prepare audio header. Error code: " << result << std::endl;
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 写入音频数据
|
||||
result = waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
if (result != MMSYSERR_NOERROR) {
|
||||
std::cerr << "Failed to write audio data. Error code: " << result << std::endl;
|
||||
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待播放完成(使用事件等待而非轮询)
|
||||
auto start_time = timeGetTime();
|
||||
while (!context.done) {
|
||||
// 使用 MsgWaitForMultipleObjects 等待消息或超时
|
||||
// 这样可以在等待的同时处理 Windows 消息
|
||||
DWORD wait_result = MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_ALLINPUT);
|
||||
|
||||
if (wait_result == WAIT_OBJECT_0) {
|
||||
// 处理 Windows 消息
|
||||
MSG msg;
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
// 超时检查(防止无限等待)
|
||||
DWORD elapsed = timeGetTime() - start_time;
|
||||
if (elapsed > static_cast<DWORD>(duration_ms + 1000)) {
|
||||
std::cerr << "Playback timeout" << std::endl;
|
||||
waveOutReset(hWaveOut);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto end_time = timeGetTime();
|
||||
DWORD actual_play_time = end_time - start_time;
|
||||
std::cout << "Actual playback time: " << actual_play_time << " ms" << std::endl;
|
||||
|
||||
// 清理
|
||||
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
|
||||
waveOutClose(hWaveOut);
|
||||
delete context.header;
|
||||
|
||||
#else
|
||||
// ===== Linux ALSA 实现 =====
|
||||
snd_pcm_t *pcm_handle = nullptr;
|
||||
const char* device = "default";
|
||||
|
||||
int rc = snd_pcm_open(&pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot open device '" << device << "': "
|
||||
<< snd_strerror(rc) << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置硬件参数
|
||||
snd_pcm_hw_params_t *hw_params;
|
||||
snd_pcm_hw_params_alloca(&hw_params);
|
||||
|
||||
rc = snd_pcm_hw_params_any(pcm_handle, hw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot get hardware parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_access(pcm_handle, hw_params,
|
||||
SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set access type: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_format(pcm_handle, hw_params,
|
||||
SND_PCM_FORMAT_S16_LE);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set sample format: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, channels);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set channel count: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int hw_sample_rate = static_cast<unsigned int>(sample_rate);
|
||||
rc = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params,
|
||||
&hw_sample_rate, 0);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set sample rate: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hw_sample_rate != static_cast<unsigned int>(sample_rate)) {
|
||||
std::cout << "Requested sample rate " << sample_rate
|
||||
<< " Hz not supported, using " << hw_sample_rate << " Hz" << std::endl;
|
||||
}
|
||||
|
||||
// 设置缓冲区大小和周期
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
rc = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
|
||||
if (buffer_size > static_cast<snd_pcm_uframes_t>(num_frames)) {
|
||||
snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params,
|
||||
&reinterpret_cast<snd_pcm_uframes_t&>(num_frames));
|
||||
}
|
||||
|
||||
rc = snd_pcm_hw_params(pcm_handle, hw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set hardware parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置软件参数
|
||||
snd_pcm_sw_params_t *sw_params;
|
||||
snd_pcm_sw_params_alloca(&sw_params);
|
||||
|
||||
rc = snd_pcm_sw_params_current(pcm_handle, sw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot get software parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params,
|
||||
static_cast<snd_pcm_uframes_t>(num_frames));
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set start threshold: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = snd_pcm_sw_params(pcm_handle, sw_params);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Cannot set software parameters: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 播放音频(写入数据)
|
||||
snd_pcm_sframes_t frames_written = 0;
|
||||
snd_pcm_uframes_t frames_to_write = static_cast<snd_pcm_uframes_t>(num_frames);
|
||||
|
||||
// 循环写入直到所有数据都播放完毕
|
||||
while (frames_written < static_cast<snd_pcm_sframes_t>(num_frames)) {
|
||||
snd_pcm_uframes_t remaining = static_cast<snd_pcm_uframes_t>(num_frames - frames_written);
|
||||
|
||||
rc = snd_pcm_writei(pcm_handle,
|
||||
buffer.data() + (frames_written * channels),
|
||||
remaining);
|
||||
|
||||
if (rc == -EPIPE) {
|
||||
// 缓冲区下溢,需要恢复
|
||||
std::cerr << "Buffer underrun occurred" << std::endl;
|
||||
snd_pcm_prepare(pcm_handle);
|
||||
rc = snd_pcm_writei(pcm_handle,
|
||||
buffer.data() + (frames_written * channels),
|
||||
remaining);
|
||||
}
|
||||
|
||||
if (rc < 0) {
|
||||
std::cerr << "Error writing to PCM device: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rc > 0) {
|
||||
frames_written += rc;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有数据播放完成
|
||||
rc = snd_pcm_drain(pcm_handle);
|
||||
if (rc < 0) {
|
||||
std::cerr << "Error draining PCM device: " << snd_strerror(rc) << std::endl;
|
||||
snd_pcm_close(pcm_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
snd_pcm_close(pcm_handle);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <cuda_runtime.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CHECK(call) do {\
|
||||
cudaError_t err = call;\
|
||||
if (err != cudaSuccess) {\
|
||||
fprintf(stderr, "CUDA error at %s:%d\n", __FILE__, __LINE__);\
|
||||
fprintf(stderr, "%s\n", cudaGetErrorString(err));\
|
||||
exit(EXIT_FAILURE);\
|
||||
}\
|
||||
} while (0)
|
||||
@@ -0,0 +1,872 @@
|
||||
#include <cstdint>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <random>
|
||||
#include "error.cuh"
|
||||
extern "C" {
|
||||
/* 创建一个设备数组 */
|
||||
static void* _crtDevArr(const size_t m)
|
||||
{
|
||||
void *d;
|
||||
CHECK(cudaMalloc(&d, m));
|
||||
return d;
|
||||
}
|
||||
|
||||
/* 释放一个设备数组 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Free(void* d)
|
||||
#else
|
||||
void Cu4cj_Free(void* d)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaFree(d));
|
||||
}
|
||||
|
||||
|
||||
/* 创建一个单精度浮点数组 输入数组的长度(不初始化) */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Cu4cj_Orig(uint64_t n)
|
||||
#else
|
||||
void* Cu4cj_Orig(int64_t n)
|
||||
#endif
|
||||
{
|
||||
return _crtDevArr(n * sizeof(float));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 从主机内存拷贝数据到设备内存
|
||||
* FIXME: 是否需要做成输入目的地址
|
||||
*/
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_ToDev(float* h, uint64_t n)
|
||||
#else
|
||||
float* Cu4cj_ToDev(float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
/* 从设备内存拷贝数据到主机内存 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_ToHost(float* d, float* h, const uint64_t n)
|
||||
#else
|
||||
void Cu4cj_ToHost(float* d, float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Copy(float* d, float* h, const uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Copy(float* d, float* h, int64_t n)
|
||||
#endif
|
||||
{
|
||||
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
|
||||
}
|
||||
|
||||
/* 创建一个单精度浮点数组并将其初始化为零
|
||||
* FIXME: 当n == 10000 时进程被杀死 */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Zero(const uint64_t n)
|
||||
#else
|
||||
float* Cu4cj_Zero(int64_t n)
|
||||
#endif
|
||||
{
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
CHECK(cudaMemset(d, 0, n*sizeof(float)));
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
/* 正态分布初始化(适用于神经网络权重) */
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Normal(uint64_t n, uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
float* Cu4cj_Normal(int64_t n, int64_t fan_in, int64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
// 计算 Xavier/Glorot 初始化的标准差
|
||||
float std = sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
// 先在主机上分配内存并生成正态分布随机数
|
||||
float* h = (float*)malloc(n * sizeof(float));
|
||||
|
||||
/* 初始化随机数种子 */
|
||||
srand(time(NULL));
|
||||
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
// 使用 Box-Muller 变换生成正态分布随机数
|
||||
float u1 = (float)rand() / RAND_MAX;
|
||||
float u2 = (float)rand() / RAND_MAX;
|
||||
float z = sqrt(-2.0f * log(u1)) * cos(2.0f * 3.1415926535f * u2);
|
||||
h[i] = z * std;
|
||||
}
|
||||
|
||||
// 在设备上分配内存
|
||||
float* d = (float*)Cu4cj_Orig(n);
|
||||
|
||||
// 将主机内存中的数据拷贝到设备内存
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
|
||||
// 释放主机内存
|
||||
free(h);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 向量加法
|
||||
static void __global__ _devAdd(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] + d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量加法
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Add(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Add(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devAdd<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量减法
|
||||
static void __global__ _devSub(float* d1, float* d2, float* r, int64_t n)
|
||||
{
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] - d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量减法
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Sub(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Sub(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devSub<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量依次相乘
|
||||
static void __global__ _devMul(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量依次相乘
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Mul(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Mul(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devMul<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量依次相除
|
||||
static void __global__ _devDiv(float* d1, float* d2, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] / d2[i];
|
||||
}
|
||||
}
|
||||
|
||||
// 向量依次相除
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_Div(float* d1, float* d2, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_Div(float* d1, float* d2, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devDiv<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 向量加法广播
|
||||
static void __global__ _devAddBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] + a;
|
||||
}
|
||||
}
|
||||
|
||||
// 加法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devAddBroadcast<<<(n + 255) / 256, 255>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 减法广播
|
||||
static void __global__ _devSubBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] - a;
|
||||
}
|
||||
}
|
||||
|
||||
// 减法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devSubBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 乘法广播
|
||||
static void __global__ _devMulBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * a;
|
||||
}
|
||||
}
|
||||
|
||||
// 乘法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devMulBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 除法广播
|
||||
static void __global__ _devDivBroadcast(float* d1, float a, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] / a;
|
||||
}
|
||||
}
|
||||
|
||||
// 除法广播
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, uint64_t n)
|
||||
#else
|
||||
void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, int64_t n)
|
||||
#endif
|
||||
{
|
||||
_devDivBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 矩阵乘法
|
||||
static void __global__ _devMatDot(float* d1, float* d2, float* r, int64_t m, int64_t n, int64_t k) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int j = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
if (i < m && j < k) {
|
||||
float sum = 0.0f;
|
||||
for (int p = 0; p < n; p++) {
|
||||
sum += d1[i * n + p] * d2[p * k + j];
|
||||
}
|
||||
r[i * k + j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
// 矩阵乘法
|
||||
// m、n、k 分别为矩阵 A、B、C 的行数、列数
|
||||
// 其中 n 为矩阵 A 的列数,也为矩阵 B 的行数
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void Cu4cj_MatDot(float* d1, float* d2, float* d_r, uint64_t m, uint64_t n, uint64_t k)
|
||||
#else
|
||||
void Cu4cj_MatDot(float* d1, float* d2, float* d_r, int64_t m, int64_t n, int64_t k)
|
||||
#endif
|
||||
{
|
||||
dim3 blockDim(16, 16);
|
||||
dim3 gridDim((m + blockDim.x - 1) / blockDim.x, (k + blockDim.y - 1) / blockDim.y);
|
||||
_devMatDot<<<gridDim, blockDim>>>(d1, d2, d_r, m, n, k);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
}
|
||||
|
||||
// 矩阵转置
|
||||
static void __global__ _devTransport(float* d1, float* d2, int64_t m, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int j = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
if (i < m && j < n) {
|
||||
d2[j * m + i] = d1[i * n + j];
|
||||
}
|
||||
}
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
|
||||
#else
|
||||
float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
|
||||
#endif
|
||||
{
|
||||
float *d_r = (float*)Cu4cj_Zero(c * r * sizeof(float));
|
||||
_devTransport<<<1, c * r>>>(m, d_r, c, r);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return d_r;
|
||||
}
|
||||
|
||||
|
||||
// Sigmoid 激活函数,输入为矩阵,输出为矩阵
|
||||
static void __global__ _devSigmoid(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = 1.0f / (1.0f + exp(-d1[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Sigmoid 导数
|
||||
static void __global__ _devSigmoidDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] * (1.0f - d1[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ReLU
|
||||
static void __global__ _devReLU(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? d1[i] : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// ReLU 导数
|
||||
static void __global__ _devReLUDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? 1.0f : 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Leaky_ReLU
|
||||
static void __global__ _devLeakyReLU(float* d1, float* r, int64_t n, float a) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? d1[i] : a * d1[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Leaky_ReLU 导数
|
||||
static void __global__ _devLeakyReLU_Derivative(float* d1, float* r, int64_t n, float a) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = d1[i] > 0.0f ? 1.0f : a;
|
||||
}
|
||||
}
|
||||
|
||||
// Tanh 激活函数,输入为矩阵,输出为矩阵
|
||||
static void __global__ _devTanh(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = (exp(d1[i]) - exp(-d1[i])) / (exp(d1[i]) + exp(-d1[i]));
|
||||
}
|
||||
}
|
||||
|
||||
// Tanh 导数
|
||||
static void __global__ _devTanhDerivative(float* d1, float* r, int64_t n) {
|
||||
int i = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (i < n) {
|
||||
r[i] = 1.0f - d1[i] * d1[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace qing {
|
||||
class Matrix {
|
||||
public:
|
||||
Matrix(uint64_t r, uint64_t c) {
|
||||
this->r = r;
|
||||
this->c = c;
|
||||
this->d = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
}
|
||||
Matrix(uint64_t r, uint64_t c, float* d) {
|
||||
this->r = r;
|
||||
this->c = c;
|
||||
this->d = d;
|
||||
}
|
||||
Matrix(const Matrix& m) {
|
||||
this->r = m.getR();
|
||||
this->c = m.getC();
|
||||
this->d = (float*)Cu4cj_Zero(m.getR() * m.getC() * sizeof(float));
|
||||
Cu4cj_Copy(m.getD(), d, m.getR() * m.getC() * sizeof(float));
|
||||
}
|
||||
~Matrix() {
|
||||
Cu4cj_Free(d);
|
||||
}
|
||||
uint64_t getR() const {
|
||||
return r;
|
||||
}
|
||||
uint64_t getC() const {
|
||||
return c;
|
||||
}
|
||||
float* getD() const {
|
||||
return d;
|
||||
}
|
||||
float get(uint64_t r, uint64_t c) const {
|
||||
float h;
|
||||
CHECK(cudaMemcpy(&h, &d[c * this->r + r], sizeof(float), cudaMemcpyDeviceToHost));
|
||||
return h;
|
||||
}
|
||||
void set(uint64_t r, uint64_t c, float v) {
|
||||
CHECK(cudaMemcpy(&d[c * this->r + r], &v, sizeof(float), cudaMemcpyHostToDevice));
|
||||
}
|
||||
bool hasSameShape(const Matrix& m) {
|
||||
return r == m.getR() && c == m.getC();
|
||||
}
|
||||
bool hasDotShape(const Matrix& m) {
|
||||
return c == m.getR();
|
||||
}
|
||||
Matrix operator+(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Add(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator+(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_AddBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator-(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Sub(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator-(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_SubBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator*(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Mul(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator*(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_MulBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator/(const Matrix& m) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_Div(d, m.getD(), d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix operator/(float a) {
|
||||
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
|
||||
Cu4cj_DivBroadcast(d, a, d_r, r * c);
|
||||
return Matrix(r, c, d_r);
|
||||
}
|
||||
Matrix dot(const Matrix& m) {
|
||||
//if (!hasDotShape(m)) {
|
||||
// throw std::invalid_argument("Dot product: matrices must have compatible shapes");
|
||||
//}
|
||||
float *d_r = (float*)Cu4cj_Zero(r * m.getC() * sizeof(float));
|
||||
Cu4cj_MatDot(d, m.getD(), d_r, r, c, m.getC());
|
||||
return Matrix(r, m.getC(), d_r);
|
||||
}
|
||||
Matrix T() {
|
||||
float *d_r = (float*)Cu4cj_Transport(d, r, c);
|
||||
return Matrix(c, r, d_r);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t r, c;
|
||||
float* d;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace qing;
|
||||
extern "C" {
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* createMatrix(const uint64_t r, const uint64_t c)
|
||||
#else
|
||||
void* createMatrix(int64_t r, int64_t c)
|
||||
#endif
|
||||
{
|
||||
return(void*) new Matrix(r, c);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void freeMatrix(void* m)
|
||||
#else
|
||||
void freeMatrix(void* m)
|
||||
#endif
|
||||
{
|
||||
delete (Matrix*)m;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
float* d = Cu4cj_Normal(fan_in * fan_out, fan_in, fan_out);
|
||||
return (void *)new Matrix(fan_in, fan_out, d);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#else
|
||||
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
|
||||
#endif
|
||||
{
|
||||
uint64_t n = fan_in * fan_out;
|
||||
|
||||
// 先在主机上分配内存并生成正态分布随机数
|
||||
float* h = (float*)malloc(n * sizeof(float));
|
||||
|
||||
/* 使用高质量的随机数生成器 */
|
||||
static std::mt19937 generator(std::random_device{}());
|
||||
|
||||
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
|
||||
float scale = std::sqrt(2.0f / (fan_in + fan_out));
|
||||
|
||||
/* 创建均匀分布对象,范围为[-scale, scale] */
|
||||
std::uniform_real_distribution<float> distribution(-scale, scale);
|
||||
|
||||
/* 遍历权重矩阵,进行赋值 */
|
||||
uint64_t k=0;
|
||||
for (uint64_t i=0; i<fan_in; ++i)
|
||||
for (uint64_t j=0; j<fan_out; ++j)
|
||||
h[k++] = distribution(generator);
|
||||
|
||||
|
||||
|
||||
// 在设备上分配内存
|
||||
float* d = (float*)Cu4cj_Orig(n*sizeof(float));
|
||||
|
||||
// 将主机内存中的数据拷贝到设备内存
|
||||
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
|
||||
|
||||
// 释放主机内存
|
||||
free(h);
|
||||
|
||||
return (void *)new Matrix(fan_in, fan_out, d);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) uint64_t getrowMatrix(void *m)
|
||||
#else
|
||||
uint64_t getrowMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->getR();
|
||||
}
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) uint64_t getcolMatrix(void *m)
|
||||
#else
|
||||
uint64_t getcolMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->getC();
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) float getMatrix(void *m, uint64_t r, uint64_t c)
|
||||
#else
|
||||
float getMatrix(void *m, uint64_t r, uint64_t c)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
return m1->get(r, c);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void setMatrix(void *m, uint64_t r, uint64_t c, float v)
|
||||
#else
|
||||
void setMatrix(void *m, uint64_t r, uint64_t c, float v)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
m1->set(r, c, v);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) bool sameShapeMatrix(void *m, void *s)
|
||||
#else
|
||||
bool sameShapeMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
return (*m1).hasSameShape(*s1);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) bool dotShapeMatrix(void *m, void *s)
|
||||
#else
|
||||
bool dotShapeMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
return (*m1).hasDotShape(*s1);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* plusMatrix(void *m, void *s)
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
void* plusMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = *m1 + *s1;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* plusBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* plusBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = *m1 + a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* subMatrix(void *m, void *s)
|
||||
#else
|
||||
void* subMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = *m1 - *s1;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* subBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* subBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = *m1 - a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* multiMatrix(void *m, void *s)
|
||||
#else
|
||||
void* multiMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1) * (*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* multiBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* multiBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = (*m1) * a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* divMatrix(void *m, void *s)
|
||||
#else
|
||||
void* divMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1) / (*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* divBroadcastMatrix(void *m, float a)
|
||||
#else
|
||||
void* divBroadcastMatrix(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = (*m1) / a;
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* dotMatrix(void *m, void *s)
|
||||
#else
|
||||
void* dotMatrix(void *m, void *s)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix* s1 = (Matrix*)s;
|
||||
Matrix r = (*m1).dot(*s1);
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* transposeMatrix(void *m)
|
||||
#endif
|
||||
#ifdef __linux__
|
||||
void* transposeMatrix(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix r = m1->T();
|
||||
return (void *)new Matrix(r);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Sigmoid_do(void *m)
|
||||
#else
|
||||
void* Sigmoid_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devSigmoid<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Sigmoid_dot(void *m)
|
||||
#else
|
||||
void* Sigmoid_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devSigmoidDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* ReLU_do(void *m)
|
||||
#else
|
||||
void* ReLU_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* ReLU_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devReLUDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Leaky_ReLU_do(void *m, float a)
|
||||
#else
|
||||
void* LeakyReLU_do(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devLeakyReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Leaky_ReLU_dot(void *m, float a)
|
||||
#else
|
||||
void* LeakyReLU_dot(void *m, float a)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devLeakyReLU_Derivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
// Tanh
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Tanh_do(void *m)
|
||||
#else
|
||||
void* Tanh_do(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devTanh<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
__declspec(dllexport) void* Tanh_dot(void *m)
|
||||
#else
|
||||
void* Tanh_dot(void *m)
|
||||
#endif
|
||||
{
|
||||
Matrix* m1 = (Matrix*)m;
|
||||
Matrix s = Matrix(*m1);
|
||||
_devTanhDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
|
||||
CHECK(cudaGetLastError());
|
||||
CHECK(cudaDeviceSynchronize());
|
||||
return (void *)new Matrix(s);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
|
||||
/* TODO(20260518-qing): 稍后我们专门做一个i2c代码仓库,这样就能够区分不同架构上面的int的大小
|
||||
* TODO(20260615-qing): 有没有可能只做一个简单的封装不引入异常机制 */
|
||||
|
||||
package bot4cj.hd
|
||||
import pthread4cj.*
|
||||
import ctype4cj.*
|
||||
|
||||
/* -------- 我们将直接调用glibc中的一些函数 -------- */
|
||||
foreign func ioctl(fd: C_Int, addr: C_ULong, ...): C_Int /* -------- 成功返回非负整数,失败返回-1 -------- */
|
||||
|
||||
const I2C_FAILED = -1i32 /* -------- I2C关闭文件失败 -------- */
|
||||
const I2C_SUCCESS = 0i32 /* -------- I2C关闭文件成功 -------- */
|
||||
const I2C_SLAVE = 0x0703u64 /* -------- at least on arm64 -------- */
|
||||
|
||||
/* -------- OPEN I2C DEVICE 打开I2C设备 -------- */
|
||||
@C
|
||||
public func openI2cDev(path: CString): Int32 {
|
||||
unsafe {
|
||||
let fd = libc_open(path, O_RDWR)
|
||||
if (fd > 0) {
|
||||
I2cLock_create(fd) /* -------- 为I2C设备创建锁 -------- */
|
||||
return fd
|
||||
}
|
||||
else {
|
||||
throw I2cOpenedFailed("?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 简化版,判断在外部实现 ---- */
|
||||
@C
|
||||
public func openI2cDev1(path: CString): C_Int {
|
||||
unsafe {
|
||||
let fd = libc_open(path, O_RDWR)
|
||||
I2cLock_create(fd)
|
||||
return fd
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- CLOSE I2C DEVICE 关闭I2C设备 -------- */
|
||||
@C
|
||||
public func closeI2cDev(fd: Int32): Unit {
|
||||
unsafe {
|
||||
I2cLock_destroy(fd) /* -------- 销毁I2C设备的锁 -------- */
|
||||
let ret = libc_close(fd)
|
||||
if (ret == I2C_FAILED) {
|
||||
throw I2cClosedFailed("?") }
|
||||
else if (ret == I2C_SUCCESS) { ; }
|
||||
else {
|
||||
throw I2cUnknowExp(
|
||||
"?", "$function returned: {ret.toString()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
public func closeI2cDev1(fd: C_Int): Unit {
|
||||
unsafe {
|
||||
I2cLock_destroy(fd)
|
||||
libc_close(fd)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- SET I2C ADDR 设置I2C地址 -------- */
|
||||
@C
|
||||
public func setI2cAddr(fd: Int32, addr: UInt8): Unit {
|
||||
unsafe {
|
||||
let ret = ioctl(fd, I2C_SLAVE, addr)
|
||||
if (ret < 0) {
|
||||
throw I2cSetAddressFailed("?") }
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
public func setI2cAddr1(fd: C_Int, addr: UInt8): C_Int {
|
||||
unsafe {
|
||||
return ioctl(fd, I2C_SLAVE, addr)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- WRITE I2C REG 写I2C寄存器 -------- */
|
||||
@C
|
||||
public func writeI2cReg(fd: Int32, reg: UInt8, val:UInt8): Unit {
|
||||
var a: VArray<UInt8, $2> = [reg, val]
|
||||
var p: CPointer<Unit> = unsafe { CPointer<Unit>(inout a) }
|
||||
if (2 != unsafe { libc_write(fd, p, 2) }) {
|
||||
throw I2cWriteRegFailed("?")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- READ I2C REG 读I2C寄存器 --------
|
||||
* INFO(20260522-qing): 读取寄存器之前要先写寄存器地址
|
||||
* INFO(20260616-qing): 这个函数只读取一个字节的数据
|
||||
* -------- */
|
||||
@C
|
||||
public func readI2cReg(fd: Int32, reg: UInt8): Int8 {
|
||||
var ptr = reg
|
||||
if (1 != unsafe { libc_write(fd, CPointer<Unit>(inout ptr), 1) }) {
|
||||
throw I2cReadRegFailed("?")
|
||||
}
|
||||
var value: Int8 = 0
|
||||
var p = unsafe { CPointer<Unit>(inout value) }
|
||||
let ret = unsafe { libc_read(fd, p, 1) }
|
||||
if (ret != 1) { throw I2cReadRegFailed("?") }
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
/* -------- I2C锁具 --------
|
||||
* INFO(20260521-qing): 因为每个I2C接口在通信时具有原子化的需求
|
||||
* - 需要一个互斥锁集合,在打开第一个I2C设备时被初始化,
|
||||
* - 释放最后一个I2C设备时,将锁具释放
|
||||
* - **每次进行I2C通信时,进行上锁操作** */
|
||||
@C
|
||||
struct I2C_Lock {
|
||||
var i2c_fd: Int32 = 0 /* -------- 这个锁绑定的I2C设备文件句柄 -------- */
|
||||
var lock = CPointer<UInt8>() /* -------- phread_mutex_t 锁对象 -------- */
|
||||
var is_active = false /* -------- 该锁具是否被激活 --------*/
|
||||
}
|
||||
|
||||
|
||||
var MAIN_LOCK = CPointer<UInt8>() /* -------- 主锁,操作锁具时使用 -------- */
|
||||
var LOCKS = CPointer<I2C_Lock>() /* -------- 指向锁具内存的指针 -------- */
|
||||
const LOCKS_SIZE: Int64 = 20 /* -------- 锁具的容量 --------*/
|
||||
|
||||
/* -------- 为一个I2C设备注册锁具 -------- */
|
||||
@C
|
||||
func I2cLock_create(fd: Int32): Unit {
|
||||
unsafe {
|
||||
/* -------- 初始化主要的锁 -------- */
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
MAIN_LOCK = _mutex_create()
|
||||
LOCKS = LibC.malloc<I2C_Lock>(count: LOCKS_SIZE)
|
||||
for (i in 0..20) {
|
||||
let tmp = I2C_Lock()
|
||||
(LOCKS+i).write(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 获取主锁 --------*/
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
var flag = false
|
||||
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == false) {
|
||||
lock.is_active = true
|
||||
lock.i2c_fd = fd
|
||||
lock.lock = _mutex_create()
|
||||
_mutex_init(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag ) { return } /* -------- 出口 -------- */
|
||||
throw OverflowException("LOCKS is over.")
|
||||
}
|
||||
}
|
||||
/* -------- 为一个I2C设备注销锁具 -------- */
|
||||
@C
|
||||
func I2cLock_destroy(fd: Int32): Unit {
|
||||
unsafe {
|
||||
/* -------- 检查锁具有没有被初始化 --------*/
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag_any = false
|
||||
var flag_target = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
lock.is_active = false
|
||||
_mutex_destroy(lock.lock)
|
||||
_mutex_free(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag_target = true
|
||||
/* -------- NOTE: 扫描整个锁具不终止 -------- */
|
||||
}
|
||||
else if (lock.is_active == true && lock.i2c_fd != fd) {
|
||||
flag_any = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
|
||||
/* -------- 如果锁具里已经没有I2C锁, 就释放整个锁具 -------- */
|
||||
if (!flag_any) {
|
||||
LibC.free(LOCKS)
|
||||
_mutex_free(MAIN_LOCK)
|
||||
MAIN_LOCK = CPointer<UInt8>()
|
||||
}
|
||||
|
||||
/* -------- 出口 -------- */
|
||||
if (flag_target) {
|
||||
return }
|
||||
|
||||
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 为一个I2C锁具加锁 -------- */
|
||||
@C
|
||||
func I2cLock_lock(fd: Int32): Unit {
|
||||
unsafe {
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
_mutex_lock(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag) { return }
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 为一个I2C锁具解锁 -------- */
|
||||
@C
|
||||
func I2cLock_unlock(fd: Int32): Unit {
|
||||
unsafe {
|
||||
if (MAIN_LOCK.isNull()) {
|
||||
throw NoneValueException("no i2c lock")
|
||||
}
|
||||
_mutex_lock(MAIN_LOCK)
|
||||
|
||||
var flag = false
|
||||
for (idx in 0..LOCKS_SIZE) {
|
||||
var lock = (LOCKS+idx).read()
|
||||
if (lock.is_active == true && lock.i2c_fd == fd) {
|
||||
_mutex_unlock(lock.lock)
|
||||
(LOCKS+idx).write(lock)
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
_mutex_unlock(MAIN_LOCK)
|
||||
if (flag) {return}
|
||||
throw NoneValueException("i2c lock nor found")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------- 原生I2C通信类--------
|
||||
* INFO(20260521-qing): 直接调用glibc的接口 */
|
||||
public class I2C_Orig {
|
||||
private var fd: Int32 = -1
|
||||
private var path: String
|
||||
public init (path: String) {
|
||||
this.path = path
|
||||
let s1 = unsafe {
|
||||
LibC.mallocCString(path) }
|
||||
fd = unsafe { openI2cDev(s1) }
|
||||
}
|
||||
|
||||
public func _close(): Unit {
|
||||
unsafe { closeI2cDev(fd) }
|
||||
}
|
||||
|
||||
public func setAddr(addr: UInt8) {
|
||||
unsafe { setI2cAddr(fd, addr) }
|
||||
}
|
||||
|
||||
public func writeReg(reg: UInt8, val:UInt8): Unit {
|
||||
unsafe { writeI2cReg(fd, reg, val) }
|
||||
}
|
||||
|
||||
public func readReg(reg: UInt8): Int8 {
|
||||
var value = unsafe { readI2cReg(fd, reg) }
|
||||
return value
|
||||
}
|
||||
|
||||
/*--------获取文件句柄--------*/
|
||||
public func getFd(): Int32 {
|
||||
return fd
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* -------- I2C设备异常 -------- */
|
||||
open private class I2cExcept <: Exception {
|
||||
public I2cExcept(dev: String, msg: String) {
|
||||
super('${dev}: ${msg}') }
|
||||
}
|
||||
|
||||
/* -------- 打开I2C设备失败 -------- */
|
||||
private class I2cOpenedFailed <: I2cExcept {
|
||||
public I2cOpenedFailed(dev: String) {
|
||||
super(dev, "failed to open i2c device") }
|
||||
}
|
||||
|
||||
/* -------- 关闭I2C设备失败 -------- */
|
||||
private class I2cClosedFailed <: I2cExcept {
|
||||
public I2cClosedFailed(dev: String) {
|
||||
super(dev, "failed to close i2c device") }
|
||||
}
|
||||
|
||||
/* -------- I2C设置地址失败 -------- */
|
||||
private class I2cSetAddressFailed <: I2cExcept {
|
||||
public I2cSetAddressFailed(dev: String) {
|
||||
super(dev, "failed to open i2c device") }
|
||||
}
|
||||
|
||||
/* -------- I2C写从机I2C失败 -------- */
|
||||
private class I2cWriteRegFailed <: I2cExcept {
|
||||
public I2cWriteRegFailed(dev: String) {
|
||||
super(dev, "failed to write i2c reg") }
|
||||
}
|
||||
|
||||
/* -------- I2C读从机I2C失败 -------- */
|
||||
private class I2cReadRegFailed <: I2cExcept {
|
||||
public I2cReadRegFailed(dev: String) {
|
||||
super(dev, "failed to read i2c reg") }
|
||||
}
|
||||
|
||||
/* -------- 未知I2C异常 -------- */
|
||||
private class I2cUnknowExp <: I2cExcept {
|
||||
public I2cUnknowExp(dev: String, msg: String) {
|
||||
super(dev, msg) }
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/* ----------------
|
||||
* MPU6050陀螺仪与加速度计模块
|
||||
* -
|
||||
* TODO(20260516-qing): 陀螺仪需要卡尔曼滤波、通过四元数等方式获取姿态角
|
||||
* - 所以应该使用一个C/C++线程来持续地采样,并且留一个接口进行获取
|
||||
*/
|
||||
|
||||
package bot4cj.hd
|
||||
import std.time.*
|
||||
import std.math.*
|
||||
import pthread4cj.*
|
||||
import bot4cj.math.*
|
||||
|
||||
const ADDR_0: UInt8 = 0x68 /* 默认地址 */
|
||||
const ADDR_1: UInt8 = 0x68 /* 地址2 */
|
||||
const PWR_MGMT_1: UInt8 = 0x6B /* 电源管理寄存器1 */
|
||||
const PWR_NOSLEEP: UInt8 = 0x00 /* 电源管理 - 非睡眠模式 */
|
||||
const GYRO_CONF_REG: UInt8 = 0x1B /* 陀螺仪配置寄存器 */
|
||||
const GYRO_FS_500DPS: UInt8 = 0x08 /* 陀螺仪量程 - 正负500度/秒 */
|
||||
const GYRO_REG_X_HIGH: UInt8 = 0x43 /* x - 陀螺仪横轴 高字节 */
|
||||
const GYRO_REG_X_LOW: UInt8 = 0x44
|
||||
const GYRO_REG_Y_HIGH: UInt8 = 0x45 /* y */
|
||||
const GYRO_REG_Y_LOW: UInt8 = 0x46
|
||||
const GYRO_REG_Z_HIGH: UInt8 = 0x47 /* z */
|
||||
const GYRO_REG_Z_LOW: UInt8 = 0x48
|
||||
const ACCEL_CONF_REG: UInt8 = 0x1C /* 加速度计配置寄存器 */
|
||||
const ACCEL_FS_8g: UInt8 = 0x10 /* 加速度计量程 正负8g */
|
||||
const ACCEL_REG_X_HIGH: UInt8 = 0x3B
|
||||
const ACCEL_REG_X_LOW: UInt8 = 0x3C
|
||||
const ACCEL_REG_Y_HIGH: UInt8 = 0x3D
|
||||
const ACCEL_REG_Y_LOW: UInt8 = 0x3E
|
||||
const ACCEL_REG_Z_HIGH: UInt8 = 0x3F
|
||||
const ACCEL_REG_Z_LOW: UInt8 = 0x40
|
||||
|
||||
/* -------- MPU6050传感器数据 -------- */
|
||||
@C
|
||||
public struct MPU6050_sensorData {
|
||||
public var accel_x: Int16 = 0 /* 加速度计 */
|
||||
public var accel_y: Int16 = 0
|
||||
public var accel_z: Int16 = 0
|
||||
public var gyro_x: Int16 = 0 /* 陀螺仪 */
|
||||
public var gyro_y: Int16 = 0
|
||||
public var gyro_z: Int16 = 0
|
||||
public func print() { /* 格式化打印 */
|
||||
println("[accel x, y, z] ${accel_x}, ${accel_y}, ${accel_z}")
|
||||
println("[gyro x, y, z] ${gyro_x}, ${gyro_y}, ${gyro_z}")
|
||||
println("--------------------")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* -------- 设置MPU6050 -------- */
|
||||
@C
|
||||
func mpu6050_setup(i2c_fd: Int32, addr: UInt8) {
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr);
|
||||
sleep(Duration.millisecond * 1)
|
||||
writeI2cReg(i2c_fd, PWR_MGMT_1, PWR_NOSLEEP); /* 退出睡眠 */
|
||||
writeI2cReg(i2c_fd, GYRO_CONF_REG, GYRO_FS_500DPS); /* ±500°/s */
|
||||
writeI2cReg(i2c_fd, ACCEL_CONF_REG, ACCEL_FS_8g); /* ±8g */
|
||||
I2cLock_unlock(i2c_fd)
|
||||
sleep(Duration.millisecond * 100); /* 等待稳定 */
|
||||
}
|
||||
}
|
||||
|
||||
@C
|
||||
/* -------- 读取 16 位传感器数据(带符号)--------
|
||||
* FIXME: 是否能够正确处理符号位 */
|
||||
func read16BitReg(i2c_fd: Int32, high: UInt8, low: UInt8): Int16 {
|
||||
unsafe {
|
||||
let hh: Int16 = Int16(readI2cReg(i2c_fd, high))
|
||||
let ll: Int16 = Int16(readI2cReg(i2c_fd, low))
|
||||
return (hh << 8) | ll
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 读取传感器数据 -------- */
|
||||
@C
|
||||
func mpu6050_readSensor(i2c_fd: Int32, addr: UInt8) {
|
||||
var data = MPU6050_sensorData()
|
||||
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr)
|
||||
data.accel_x = read16BitReg(i2c_fd, ACCEL_REG_X_HIGH, ACCEL_REG_X_LOW);
|
||||
data.accel_y = read16BitReg(i2c_fd, ACCEL_REG_Y_HIGH, ACCEL_REG_Y_LOW);
|
||||
data.accel_z = read16BitReg(i2c_fd, ACCEL_REG_Z_HIGH, ACCEL_REG_Z_LOW);
|
||||
data.gyro_x = read16BitReg(i2c_fd, GYRO_REG_X_HIGH, GYRO_REG_X_LOW);
|
||||
data.gyro_y = read16BitReg(i2c_fd, GYRO_REG_Y_HIGH, GYRO_REG_Y_LOW);
|
||||
data.gyro_z = read16BitReg(i2c_fd, GYRO_REG_Z_HIGH, GYRO_REG_Z_LOW);
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
/* -------- 计算PITCH角 -------- */
|
||||
//@C
|
||||
//func mpu6050_calcPitch()
|
||||
|
||||
/* -------- mpu6050主线程 --------
|
||||
* TODO(20260525-qing): 使用互补滤波就够用了 */
|
||||
const RAD_TO_DEG = 57.2956f32 /* ---- 弧度转换为角度的系数(180/π ≈ 57.2956) ---- */
|
||||
//foreign func pthread_testcancel(): Unit
|
||||
@C
|
||||
func mpu6050_main(args: CPointer<Unit>): CPointer<Unit> {
|
||||
|
||||
/* -------- 获取传入线程的参数 -------- */
|
||||
let param = unsafe{ CPointer<mpu6050_th_param>(args).read() }
|
||||
|
||||
/* -------- 为加速度计准备滤波器组 -------- */
|
||||
var kalman_accel_x = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_accel_y = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_accel_z = extKalman(20.0f32, 200.0f32)
|
||||
|
||||
/* -------- 为陀螺仪准备滤波器组 -------- */
|
||||
var kalman_gyro_x = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_gyro_y = extKalman(20.0f32, 200.0f32)
|
||||
var kalman_gyro_z = extKalman(20.0f32, 200.0f32)
|
||||
|
||||
|
||||
while(true) {
|
||||
|
||||
println("I am running. i2c_fd: ${param.i2c_fd}, addr: ${param.addr}")
|
||||
//unsafe { _mutex_lock(mutex) }
|
||||
|
||||
/* -------- 获取传感器数据 -------- */
|
||||
var data = unsafe { mpu6050_readSensor(param.i2c_fd, param.addr) }
|
||||
|
||||
/* -------- 为加速度计执行滤波操作 -------- */
|
||||
let filtered_accel_x = kalman_accel_x.filter(Float32(data.accel_x))
|
||||
let filtered_accel_y = kalman_accel_y.filter(Float32(data.accel_y))
|
||||
let filtered_accel_z = kalman_accel_z.filter(Float32(data.accel_z))
|
||||
|
||||
/* -------- 为陀螺仪执行滤波操作 -------- */
|
||||
let filtered_gyro_x = kalman_gyro_x.filter(Float32(data.gyro_x))
|
||||
let filtered_gyro_y = kalman_gyro_y.filter(Float32(data.gyro_y))
|
||||
let filtered_gyro_z = kalman_gyro_z.filter(Float32(data.gyro_z))
|
||||
|
||||
|
||||
/* -------- 计算Pitch(俯仰角)和Roll(翻滚角) -------- */
|
||||
let roll = atan2(filtered_accel_y, filtered_accel_z) * RAD_TO_DEG
|
||||
let pitch = atan(-filtered_accel_x / sqrt(filtered_accel_y * filtered_accel_y + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
|
||||
|
||||
let roll1 = atan(filtered_accel_y / sqrt(filtered_accel_x * filtered_accel_x + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
|
||||
let pitch1 = atan2(-filtered_accel_x, filtered_accel_z) * RAD_TO_DEG
|
||||
|
||||
print("roll: ${roll}\t")
|
||||
println("pitch: ${pitch}")
|
||||
print("roll1: ${roll1}\t")
|
||||
println("pitch1: ${pitch1}")
|
||||
|
||||
//unsafe { _mutex_unlock(mutex) }
|
||||
sleep(Duration.millisecond * 10)
|
||||
|
||||
}
|
||||
|
||||
/* -------- 返回NULL指针以占位 -------- */
|
||||
return CPointer<Unit>()
|
||||
}
|
||||
|
||||
/* -------- mpu6050线程参数 --------
|
||||
* INFO(20260522-qing): mutex字段是一个锁的指针,需要在MPU6050的构造函数中获取资源
|
||||
* - 用于辅助别的线程获取值。 */
|
||||
@C
|
||||
struct mpu6050_th_param {
|
||||
public var i2c_fd: Int32 = 0 /* i2c文件句柄 */
|
||||
public var addr: UInt8 = 0 /* i2c设备地址 */
|
||||
public var mutex = CPointer<UInt8>() /* 用于辅助别的线程获取值的锁 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -------- MPU6050陀螺仪、加速度计 --------
|
||||
* - 输入并持有一个I2C文件句柄 */
|
||||
public class MPU6050 {
|
||||
|
||||
/* -------- 主初始化器 --------
|
||||
* FIXME(20260517-qing): 由于gc的原因,我们可能会手动构造RAII,
|
||||
* - 将一部分初始化移动到别的函数 */
|
||||
public init(i2c_fd: Int32, addr: UInt8) {
|
||||
this.i2c_fd = i2c_fd
|
||||
this.addr = addr
|
||||
unsafe {
|
||||
param = LibC.malloc<mpu6050_th_param>()
|
||||
var tmp = mpu6050_th_param()
|
||||
tmp.i2c_fd = i2c_fd
|
||||
tmp.addr = addr
|
||||
tmp.mutex = LibC.malloc<UInt8>()
|
||||
param.write(tmp)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 终结器 -------- */
|
||||
~init() {
|
||||
/* -------- 清除构造时申请的线程参数和锁 -------- */
|
||||
unsafe {
|
||||
var tmp = param.read()
|
||||
LibC.free(tmp.mutex)
|
||||
LibC.free(param)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 初始化该模块 -------- */
|
||||
public func setup() {
|
||||
unsafe { mpu6050_setup(i2c_fd, addr) }
|
||||
}
|
||||
|
||||
/*----------------*/
|
||||
private var i2c_fd: Int32 /* -------- I2C设备文件句柄 -------- */
|
||||
private var addr: UInt8 /* -------- I2C设备地址 -------- */
|
||||
|
||||
|
||||
/* -------- 获取原始MPU6050传感器数据 -------- */
|
||||
public func readSensor(): MPU6050_sensorData {
|
||||
return unsafe { mpu6050_readSensor(i2c_fd, addr) }
|
||||
}
|
||||
|
||||
/* -------- mpu6050启动守护 --------
|
||||
* TODO(20260520-qing): 放进类中
|
||||
* --------------------------------- */
|
||||
var param: CPointer<mpu6050_th_param> /* -------- 线程参数必须能够持久保存 -------- */
|
||||
var pth: Option<Pth> = None /* -------- 线程对象 -------- */
|
||||
public func startDaemon() { /* -------- 启动线程 -------- */
|
||||
pth = Pth(mpu6050_main, CPointer<Unit>(param))
|
||||
}
|
||||
|
||||
/* -------- 停止线程 -------- */
|
||||
public func stopDaemon() {
|
||||
if (let Some(P) <- pth) {
|
||||
P.close() }
|
||||
}
|
||||
|
||||
/* -------- 等待线程终止 -------- */
|
||||
public func waitDaemon() {
|
||||
if (let Some(P) <- pth) {
|
||||
var ptr: CPointer<Unit> = CPointer<Unit>()
|
||||
P.join(CPointer<CPointer<Unit>>(CPointer<CPointer<Unit>>(ptr))) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package bot4cj.hd
|
||||
import pthread4cj.*
|
||||
|
||||
/* pca9685舵机控制版 - bot4cj
|
||||
* TODO(20260515-qing): 由于对统一接口的需求,设计基于回调的虚拟化硬件
|
||||
* TODO(20260516-qing): 获取pca9685的中文或者英文参考资料
|
||||
* NOTE(20260618-qing): 该代码已经被Lua脚本所取代,留着备用说不定以后会删除。 */
|
||||
public class PCA9685 {
|
||||
private static const ADDR_0: UInt8 = 0x40 /* PCA9685的i2c地址 */
|
||||
private let i2c_fd: Int32
|
||||
private let addr: UInt8
|
||||
public init( i2c_fd: Int32,
|
||||
addr!: UInt8 = ADDR_0) { /* PCA9685硬件地址 */
|
||||
this.i2c_fd = i2c_fd
|
||||
this.addr = addr
|
||||
/* -------- 初始化PCA9685 --------
|
||||
* FIXME(20260517-qing): 在旧版机器人代码移除以后,移除这一段 */
|
||||
|
||||
}
|
||||
|
||||
/* -------- 设置PWM频率为50Hz(舵机标准周期20ms0) --------
|
||||
* TODO(20260516-qing): 不将数值写死 */
|
||||
public func setup() {
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, addr);
|
||||
writeI2cReg(i2c_fd, 0x00, 0x20); /* 开启AI(地址自增)和关闭SLEEP */
|
||||
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
|
||||
/* FIXME: 这一条是不是重复了 */
|
||||
var freq: Float32 = 50.0;
|
||||
var prescale_val: Float32 = 25000000.0 / (4096.0 * freq) - 1.0; /* 25MHz主频 */
|
||||
var prescale: UInt8 = UInt8(prescale_val + 0.5);
|
||||
|
||||
writeI2cReg(i2c_fd, 0x00, 0x10); /* 进入SLEEP模式(允许改频率) */
|
||||
writeI2cReg(i2c_fd, 0xFE, prescale); /* 写入预分频值 */
|
||||
writeI2cReg(i2c_fd, 0x00, 0x20); /* 退出SLEEP */
|
||||
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
|
||||
}
|
||||
|
||||
|
||||
/* -------- 设置指定通道的PWM信号(0-4095)-------- */
|
||||
public func setPWM(channel: UInt8, on: UInt16, off: UInt16) {
|
||||
var reg: UInt8 = 0x06 + 4 * channel; /* LED0 寄存器起始地址 */
|
||||
unsafe {
|
||||
writeI2cReg(i2c_fd, reg, UInt8(on & 0xFF));
|
||||
writeI2cReg(i2c_fd, reg + 1, UInt8(on >> 8));
|
||||
writeI2cReg(i2c_fd, reg + 2, UInt8(off & 0xFF));
|
||||
writeI2cReg(i2c_fd, reg + 3, UInt8(off >> 8));
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* -------- 角度转PWM值(0°~180° → 0.5ms~2.5ms脉宽) -------- */
|
||||
public func setAngle(channel: UInt8, angle: Float32) {
|
||||
const min: Float32 = 0.5; /* 0°对应0.5ms */
|
||||
const max: Float32 = 2.5; /* 180°对应2.5ms */
|
||||
let width: Float32 = min + (max - min) * (angle / 180.0);
|
||||
let off_val: UInt16 = UInt16(width * 4096.0 / 20.0); /* 20ms 周期 */
|
||||
unsafe {
|
||||
I2cLock_lock(i2c_fd)
|
||||
setI2cAddr(i2c_fd, this.addr);
|
||||
this.setPWM(channel, 0, off_val); /* ON=0, OFF=计算值 */
|
||||
I2cLock_unlock(i2c_fd)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package bot4cj.hd
|
||||
import std.math.*
|
||||
/* -------- 舵机类 --------
|
||||
* 含最大值、最小值、漂移值三个调整项
|
||||
* 以及一个设置角度回调函数
|
||||
*
|
||||
* FIXME(20260618-qing): 在构造函数中设置角度最大值 */
|
||||
public class Servo {
|
||||
private var max: Float32
|
||||
private var min: Float32
|
||||
private var offset: Float32
|
||||
private var callback_setAngle: (Float32) -> Unit
|
||||
private static let MAX_ANGLE: Float32 = 180.0f32
|
||||
public init(max!: Float32, min!: Float32, offset!: Float32,
|
||||
callback_setAngle!: (Float32) -> Unit) {
|
||||
this.max =max
|
||||
this.min = min
|
||||
this.offset = offset
|
||||
this.callback_setAngle = callback_setAngle
|
||||
}
|
||||
|
||||
public func setAngle(angle: Float32) {
|
||||
var a = angle
|
||||
a += offset /* ---- 加上漂移值 ---- */
|
||||
if (a > abs(max)) { a = abs(max)}
|
||||
if (a < abs(min)) { a = abs(min)}
|
||||
if (max < 0.0f32) { a = MAX_ANGLE - a }
|
||||
callback_setAngle(a)
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
macro package bot4cj.hd.extern
|
||||
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.ast.*
|
||||
|
||||
public macro ExternHardware(attrTokens: Tokens, inputTokens: Tokens): Tokens {
|
||||
let expr = LitConstExpr(attrTokens)
|
||||
let file = File(expr.literal.value, OpenMode.Read)
|
||||
let bytes = readToEnd(file)
|
||||
file.close()
|
||||
let sb = StringBuilder()
|
||||
sb.appendFromUtf8(bytes)
|
||||
let code = sb.toString()
|
||||
return cangjieLex(code)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package bot4cj.hd
|
||||
import ctype4cj.*
|
||||
|
||||
const FBIOGET_VSCREENINFO = 0x4602u64
|
||||
const FBIOGET_FSCREENINFO = 0x4600u64
|
||||
const PROT_WRITE: C_Int = 0x2
|
||||
const MAP_SHARED: C_Int = 0x01
|
||||
|
||||
/* --- 定义在I2C.cj里了 ---*/
|
||||
//foreign func ioctl(fd: C_Int, request: C_ULong, ...): C_Int
|
||||
/* ---- 内存映射 ---- */
|
||||
public type off_t = C_LongLong
|
||||
foreign func mmap(addr: CPointer<Unit>, length: UIntNative, prot: C_Int, flags: C_Int, fd: C_Int, offset: off_t): CPointer<Unit>
|
||||
|
||||
/* -------- 打开framebuffer帧缓冲设备 -------- */
|
||||
@C
|
||||
public func fb_open(fb_dev: CString): C_Int {
|
||||
return unsafe { libc_open(fb_dev, O_RDWR) }
|
||||
}
|
||||
|
||||
/* -------- 帧缓冲区固定参数 -------- */
|
||||
@C
|
||||
public struct fb_fix_screeninfo {
|
||||
var id: VArray<C_Char, $16> /* 设备驱动名称字符串,比如"TT Builtin" */
|
||||
var smem_start: C_ULong = 0 /* 帧缓冲内存(显存)的物理起始地址 */
|
||||
/* (physical address) */
|
||||
var smem_len: UInt32 = 0 /* 帧缓冲内存的总长度,单位字节 */
|
||||
var type_: UInt32 = 0 /* 显存类型(fb存储布局)see FB_TYPE_* */
|
||||
var type_aux: UInt32 = 0 /* 显存附加类型 */
|
||||
var visual: UInt32 = 0 /* 色彩显示模式 see FB_VISUAL_* */
|
||||
var xpanstep: UInt16 = 0 /* zero if no hardware panning */
|
||||
var ypanstep: UInt16 = 0 /* zero if no hardware panning */
|
||||
var ywrapstep: UInt16 = 0 /* zero if no hardware ywrap */
|
||||
var line_length: UInt32 = 0 /* 显示屏上一行像素数据所占用的字节数 */
|
||||
var mmio_start: C_ULong = 0 /* 内存映射I/O的物理起始地址 */
|
||||
/* (physical address) */
|
||||
var mmio_len: UInt32 = 0 /* 内存映射的区域长度,单位字节 */
|
||||
var accel: UInt32 = 0 /* 硬件加速类型 */
|
||||
/* specific chip/card we have */
|
||||
var capabilities: UInt16 = 0 /* see FB_CAP_* */
|
||||
var reserved: VArray<UInt16, $2> /* 保留字段,用于未来扩展,确保兼容 */
|
||||
|
||||
public init() {
|
||||
id = VArray<C_Char, $16> (repeat: 0)
|
||||
reserved = VArray<UInt16, $2> (repeat: 0)
|
||||
}
|
||||
}
|
||||
|
||||
/* 颜色字段偏移量的解释:所有偏移量均从右侧开始计算,
|
||||
* 位于一个“像素”值内部,该像素值的宽度正好等于 'bits_per_pixel'
|
||||
* (也就是说:你可以将偏移量作为 << 的右操作数使用)。
|
||||
* 之后的像素就是一个比特流,会原封不动地写入显存。
|
||||
*
|
||||
* 对于伪彩色:所有颜色分量的偏移量和长度应该相同。
|
||||
* 偏移量指定了调色板索引的最低有效位在像素值中的位置。
|
||||
* 长度表示可用的调色板条目数量(即条目数 = 1 << 长度)。
|
||||
*/
|
||||
@C
|
||||
public struct fb_bitfield {
|
||||
var offset: UInt32 = 0 /* 位域的起始位置(从右侧算起)*/
|
||||
var length: UInt32 = 0 /* 位域的长度(比特数) */
|
||||
var msb_right: UInt32 = 0 /* != 0 : 最高有效位在右侧 */
|
||||
/* (即低位在右) */
|
||||
}
|
||||
|
||||
/* -------- 帧缓冲区可变参数 -------- */
|
||||
@C
|
||||
public struct fb_var_screeninfo {
|
||||
var xres: UInt32 = 0 /* visible resolution */
|
||||
var yres: UInt32 = 0
|
||||
var xres_virtual: UInt32 = 0 /* virtual resolution */
|
||||
var yres_virtual: UInt32 = 0
|
||||
var xoffset: UInt32 = 0 /* offset from virtual to visible */
|
||||
var yoffset: UInt32 = 0 /* resolution */
|
||||
|
||||
var bits_per_pixel: UInt32 = 0 /* guess what */
|
||||
var grayscale: UInt32 = 0 /* 0 = color, 1 = grayscale, */
|
||||
/* >1 = FOURCC */
|
||||
var red: fb_bitfield /* bitfield in fb mem if true color, */
|
||||
var green: fb_bitfield /* else only length is significant */
|
||||
var blue: fb_bitfield
|
||||
var transp: fb_bitfield /* transparency */
|
||||
|
||||
var nonstd: UInt32 = 0 /* != 0 Non standard pixel format */
|
||||
|
||||
var activate: UInt32 = 0 /* see FB_ACTIVATE_* */
|
||||
|
||||
var height: UInt32 = 0 /* height of picture in mm */
|
||||
var width: UInt32 = 0 /* width of picture in mm */
|
||||
|
||||
var accel_flags: UInt32 = 0 /* (OBSOLETE) see fb_info.flags */
|
||||
|
||||
/* Timing: All values in pixclocks, except pixclock (of course) */
|
||||
var pixclock: UInt32 = 0 /* pixel clock in ps (pico seconds) */
|
||||
var left_margin: UInt32 = 0 /* time from sync to picture */
|
||||
var right_margin: UInt32 = 0 /* time from picture to sync */
|
||||
var upper_margin: UInt32 = 0 /* time from sync to picture */
|
||||
var lower_margin: UInt32 = 0
|
||||
var hsync_len: UInt32 = 0 /* length of horizontal sync */
|
||||
var vsync_len: UInt32 = 0 /* length of vertical sync */
|
||||
var sync: UInt32 =0 /* see FB_SYNC_* */
|
||||
var vmode: UInt32 = 0 /* see FB_VMODE_* */
|
||||
var rotate: UInt32 = 0 /* angle we rotate counter clockwise */
|
||||
var colorspace: UInt32 = 0 /* colorspace for FOURCC-based modes */
|
||||
var reserved: VArray<UInt32, $4>/* Reserved for future compatibility */
|
||||
|
||||
public init() {
|
||||
red = fb_bitfield()
|
||||
green = fb_bitfield()
|
||||
blue = fb_bitfield()
|
||||
transp = fb_bitfield()
|
||||
reserved = VArray<UInt32, $4> (repeat: 0)
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 获取帧缓冲区可变参数 ---- */
|
||||
@C
|
||||
public func fb_get_var_info(fd: C_Int) {
|
||||
var info = fb_var_screeninfo()
|
||||
unsafe { ioctl(fd, FBIOGET_VSCREENINFO, inout info) }
|
||||
return info
|
||||
}
|
||||
|
||||
/* ---- 获取帧缓冲区固定参数 ---- */
|
||||
@C
|
||||
public func fb_get_fix_info(fd: C_Int) {
|
||||
var info = fb_fix_screeninfo()
|
||||
unsafe { ioctl(fd, FBIOGET_FSCREENINFO, inout info) }
|
||||
return info
|
||||
}
|
||||
|
||||
/* ---- 内存映射 ---- */
|
||||
@C
|
||||
public func fb_mmap(addr: CPointer<Unit>, length: UIntNative, fd: C_Int, offset: off_t): CPointer<Unit>
|
||||
{
|
||||
let buf = unsafe { mmap(addr, length, PROT_WRITE, MAP_SHARED, fd, offset) }
|
||||
return buf
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package bot4cj.hd
|
||||
import std.time.*
|
||||
//foreign func play_frequency(frequency: Int32, duration_ms: Int32, sample_rate: Int32): Bool
|
||||
//foreign func play_frequency(frequency: Float32, duration_ms: Int32, volume: Float64, channels: Int32, sample_rate: Int32): Bool
|
||||
|
||||
/* -------- 扬声器类 --------
|
||||
* TODO: 一次打开设备,一段时间后再关闭 */
|
||||
public class Spk {
|
||||
private var channels: Int32
|
||||
private var sample_rate: Int32
|
||||
public init(channels!: Int32, sample_rate!: Int32) {
|
||||
this.channels = channels
|
||||
this.sample_rate = sample_rate
|
||||
}
|
||||
|
||||
/* -------- 控制扬声器发声 -------- */
|
||||
public func play(frequency: Float32, volume: Float64, durations_ms: Int32): Bool {
|
||||
//unsafe { play_frequency(frequency, durations_ms, volume, channels, sample_rate) }
|
||||
sleep(Duration.millisecond * Int64(durations_ms))
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package bot4cj
|
||||
import std.io.*
|
||||
import std.fs.*
|
||||
import std.env.*
|
||||
import std.time.*
|
||||
import std.argopt.*
|
||||
import std.convert.*
|
||||
import std.collection.*
|
||||
import stdx.encoding.json.*
|
||||
import bot4cj.hd.*
|
||||
import bot4cj.math.*
|
||||
import ctype4cj.*
|
||||
|
||||
/* ---- 神经网络输出路径 ---- */
|
||||
var NNS_OUT: Option<String> = None
|
||||
main(args: Array<String>): Int64 {
|
||||
|
||||
/* ---- 解析命令行参数 ---- */
|
||||
let specs = [
|
||||
ArgumentSpec.Long("help", ArgumentMode.NoValue), /* ---- 拉起帮助菜单 ---- */
|
||||
ArgumentSpec.Short(r'i', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输入 ---- */
|
||||
ArgumentSpec.Short(r'o', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输出 ---- */
|
||||
ArgumentSpec.Long("test", ArgumentMode.RequiredValue) /* ---- 运行一个测试分支 - 需要输入测试类型 ---- */
|
||||
]
|
||||
|
||||
/* ---- 执行解析 ---- */
|
||||
let parsed = parseArguments(args, specs)
|
||||
let options = parsed.options
|
||||
|
||||
//---------------------------
|
||||
/* 读取设置项 */
|
||||
|
||||
/*---------------------------*/
|
||||
/*---------------------------*/
|
||||
/* 初始化神经网络 (init nn)
|
||||
* TODO: 可以去掉内部初始化参数,使用矩阵式模板进行初始化 */
|
||||
|
||||
|
||||
//var nns: ArrayList<Nn> =
|
||||
//if (options.keys().contains("i")) {
|
||||
// let filename = options['i']
|
||||
// load_nns(filename) }
|
||||
//else {
|
||||
// let nn_in = ArrayList<Nn>()
|
||||
// nn_in.add(Nn(41, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(80, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(80, 40, ActivationFuncs.Leaky_ReLU, 0.2f32))
|
||||
// nn_in.add(Nn(40, 17, ActivationFuncs.Sigmoid, 0.2f32)) // 16个舵机加1个时延
|
||||
// nn_in }
|
||||
|
||||
/* ---- 使用-o选项进行输出路径的选择 ---- */
|
||||
if (options.contains("o")) {
|
||||
NNS_OUT = Option<String>.Some(options['o'])
|
||||
}
|
||||
|
||||
/*---------------------------*/
|
||||
/*---------------------------*/
|
||||
/* 分支选择 */
|
||||
|
||||
|
||||
/* -------- 拉起帮助手册
|
||||
* -------- */
|
||||
if (options.contains("help")) {
|
||||
print_help()
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 启动一个测试分支
|
||||
* -------- */
|
||||
else if (options.contains("test")) {
|
||||
run_test(options["test"])
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 缺省行为:从标准输入获取动作脚本
|
||||
* --------
|
||||
* -------- TODO(20260519-qing): 进行脚本引入(相对路径也可,从**环境变量**的路径中搜寻也可) */
|
||||
else {
|
||||
let consoleReader = getStdIn()
|
||||
let robot = RobotStruct(consoleReader)
|
||||
try {
|
||||
robot.active()
|
||||
robot.run()
|
||||
}
|
||||
finally {
|
||||
robot.deactive()
|
||||
}
|
||||
|
||||
/* ---- NOTE(20260523-qing): 储存微调网络模型
|
||||
* ---- */
|
||||
// let consoleReader = getStdIn()
|
||||
// run_action_drl(consoleReader, nns)
|
||||
// if (options.contains("o")) {
|
||||
// save_nns(nns, options['o']) }
|
||||
}
|
||||
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/* -------- 打印帮助手册
|
||||
* -------- */
|
||||
func print_help()
|
||||
{
|
||||
println("这是一个机器人动作脚本解析器。")
|
||||
println("使用管道输入动作脚本,示例:")
|
||||
println("cat std.mt | ./bot")
|
||||
println()
|
||||
println("--i2c=/path/to/device\t选择i2c设备")
|
||||
println()
|
||||
println("-i 选择输入的模型的路径")
|
||||
println("-o 设置输出的模型的路径")
|
||||
}
|
||||
|
||||
/* -------- 集成多种常规测试、临时测试
|
||||
* -------- */
|
||||
func run_test(key: String) {
|
||||
|
||||
/* ----------- 神经网络与门测试
|
||||
* -----------
|
||||
* ----------- INFO: 我们使用随机生成的动态数据集,这样会导致一些后果 */
|
||||
if (key == "nn") {
|
||||
let nn_t = ArrayList<Nn1>()
|
||||
nn_t.add(Nn1(2, 4, 0.05f32, Af.Leaky_ReLU(FP32(0.01))))
|
||||
nn_t.add(Nn1(4, 1, 0.05f32, Af.Sigmoid))
|
||||
let batch = 1
|
||||
let num =2000
|
||||
let prt_num = 100
|
||||
for (i in 0..(num*batch)) {
|
||||
let m = Matrix1(1, 2)
|
||||
let t = Matrix1(1, 1)
|
||||
prepare_andDoor(m, t)
|
||||
let l = train1(nn_t, m, t, 1.0f32) // 这个1.0是学习率的折扣,而非学习率本身
|
||||
if (i % prt_num == prt_num-1) {
|
||||
print("${i}\t:")
|
||||
print("${l}")
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
else if (key == "cnn") {
|
||||
testCNN()
|
||||
}
|
||||
|
||||
else if (key == "cnn1") {
|
||||
testGeometryCNN()
|
||||
}
|
||||
|
||||
|
||||
/* -------- 测试解析矩阵,并且输出
|
||||
* -------- */
|
||||
else if (key == "matrix_o") {
|
||||
let file = File("ss/test.mt", OpenMode.Read)
|
||||
let robot = RobotStruct(file)
|
||||
file.close()
|
||||
let outf = File("ss/output.mt", OpenMode.Write)
|
||||
robot.save(outf)
|
||||
outf.close()
|
||||
}
|
||||
|
||||
/* -------- 解析测试输出的矩阵
|
||||
* -------- */
|
||||
else if (key == "matrix_2") {
|
||||
let file = File("ss/output.mt", OpenMode.Read)
|
||||
let robot = RobotStruct(file)
|
||||
file.close()
|
||||
robot.active()
|
||||
robot.run()
|
||||
robot.deactive()
|
||||
let outf = File("ss/output_1.mt", OpenMode.Write)
|
||||
robot.save(outf)
|
||||
outf.close()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Adam.cj - Adam 优化器实现(添加全连接层支持)
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
public class Adam {
|
||||
let learningRate: FP32
|
||||
let beta1: FP32
|
||||
let beta2: FP32
|
||||
let epsilon: FP32
|
||||
|
||||
var m: HashMap<String, Matrix1>
|
||||
var v: HashMap<String, Matrix1>
|
||||
var t: Int64
|
||||
|
||||
// 完整参数构造函数
|
||||
public init(
|
||||
lr: FP32,
|
||||
beta1: FP32,
|
||||
beta2: FP32,
|
||||
epsilon: FP32
|
||||
) {
|
||||
learningRate = lr
|
||||
this.beta1 = beta1
|
||||
this.beta2 = beta2
|
||||
this.epsilon = epsilon
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
// 默认参数构造函数
|
||||
public init() {
|
||||
learningRate = FP32(0.001)
|
||||
beta1 = FP32(0.9)
|
||||
beta2 = FP32(0.999)
|
||||
epsilon = FP32(0.00000001)
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
// 仅自定义学习率的构造函数
|
||||
public init(lr: FP32) {
|
||||
learningRate = lr
|
||||
beta1 = FP32(0.9)
|
||||
beta2 = FP32(0.999)
|
||||
epsilon = FP32(0.00000001)
|
||||
m = HashMap<String, Matrix1>()
|
||||
v = HashMap<String, Matrix1>()
|
||||
t = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算幂运算的辅助函数
|
||||
*/
|
||||
private func power(base: FP32, exp: Int64): FP32 {
|
||||
var result = FP32(1.0)
|
||||
for (i in 0..exp) {
|
||||
result = result * base
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算平方根的辅助函数 - 使用牛顿迭代法
|
||||
*/
|
||||
private func sqrt(val: FP32): FP32 {
|
||||
let x = val.getValue()
|
||||
if (x <= 0.0) {
|
||||
return FP32(0.0)
|
||||
}
|
||||
var guess = x
|
||||
for (i in 0..10) {
|
||||
guess = (guess + x / guess) / 2.0
|
||||
}
|
||||
return FP32(guess)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新矩阵参数(通用方法)
|
||||
* @param param 要更新的参数矩阵
|
||||
* @param grad 梯度矩阵
|
||||
* @param key 参数在HashMap中的键名
|
||||
*/
|
||||
private func updateMatrix(
|
||||
param: Matrix1,
|
||||
grad: Matrix1,
|
||||
key: String
|
||||
): Unit {
|
||||
// 初始化动量
|
||||
let hasState = m.contains(key)
|
||||
if (!hasState) {
|
||||
let rows = param.get_row()
|
||||
let cols = param.get_col()
|
||||
m.add(key, Matrix1(rows, cols))
|
||||
v.add(key, Matrix1(rows, cols))
|
||||
}
|
||||
|
||||
let mMat = m.get(key).getOrThrow()
|
||||
let vMat = v.get(key).getOrThrow()
|
||||
|
||||
let rows = param.get_row()
|
||||
let cols = param.get_col()
|
||||
|
||||
for (r in 0..rows) {
|
||||
for (c in 0..cols) {
|
||||
let g = grad.get(r, c).getOrThrow()
|
||||
|
||||
// 更新一阶矩
|
||||
let oldM = mMat.get(r, c).getOrThrow()
|
||||
let newM = beta1 * oldM + (FP32(1.0) - beta1) * g
|
||||
mMat.set(r, c, newM)
|
||||
|
||||
// 更新二阶矩
|
||||
let oldV = vMat.get(r, c).getOrThrow()
|
||||
let newV = beta2 * oldV + (FP32(1.0) - beta2) * g * g
|
||||
vMat.set(r, c, newV)
|
||||
|
||||
// 偏差修正
|
||||
let one = FP32(1.0)
|
||||
let beta1Pow = power(beta1, t)
|
||||
let beta2Pow = power(beta2, t)
|
||||
|
||||
let mHat = newM / (one - beta1Pow)
|
||||
let vHat = newV / (one - beta2Pow)
|
||||
|
||||
// 更新参数
|
||||
let oldParam = param.get(r, c).getOrThrow()
|
||||
let update = learningRate * mHat / (sqrt(vHat) + epsilon)
|
||||
param.set(r, c, oldParam - update)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新卷积层参数
|
||||
*/
|
||||
public func updateConvLayer(layer: ConvLayer): Unit {
|
||||
t += 1
|
||||
|
||||
// 更新卷积核
|
||||
var kGrad = match (layer.kernelsGrad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No kernel gradients")
|
||||
}
|
||||
|
||||
for (oc in 0..layer.nOutputChannels) {
|
||||
let key = "conv_kernel_" + oc.toString()
|
||||
updateMatrix(layer.kernels[oc], kGrad[oc], key)
|
||||
}
|
||||
|
||||
// 更新偏置
|
||||
var bGrad = match (layer.biasGrad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
let biasKey = "conv_bias"
|
||||
updateMatrix(layer.biases, bGrad, biasKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新全连接层参数(Nn1)
|
||||
*/
|
||||
public func updateFCLayer(layer: Nn1): Unit {
|
||||
t += 1
|
||||
|
||||
// 更新权重
|
||||
var wGrad = match (layer.weights_grad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No weights gradient")
|
||||
}
|
||||
|
||||
let weightKey = "fc_weights"
|
||||
updateMatrix(layer.weights, wGrad, weightKey)
|
||||
|
||||
// 更新偏置
|
||||
var bGrad = match (layer.bias_grad) {
|
||||
case Some(val) => val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
let biasKey = "fc_bias"
|
||||
updateMatrix(layer.bias, bGrad, biasKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置优化器状态
|
||||
*/
|
||||
public func reset(): Unit {
|
||||
m.clear()
|
||||
v.clear()
|
||||
t = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前迭代次数
|
||||
*/
|
||||
public func getStep(): Int64 {
|
||||
t
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package bot4cj.math
|
||||
import std.math.*
|
||||
|
||||
/* -------- Activation Functions 激活函数族 --------
|
||||
* TODO(20260516-qing): 增加序列化、反序列化工厂函数 */
|
||||
public enum Af {
|
||||
|
||||
None | Sigmoid | ReLU | Leaky_ReLU(FP32) | Tanh | Softmax
|
||||
|
||||
/* -------- 执行激活函数 -------- */
|
||||
public func forward(x: Matrix1) {
|
||||
match(this) {
|
||||
case None => none(x)
|
||||
case Sigmoid => sigmoid(x)
|
||||
case ReLU => relu(x)
|
||||
case Leaky_ReLU(alpha) => leaky_relu(x, alpha)
|
||||
case Tanh => tanh(x)
|
||||
case Softmax => softmax(x)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 激活函数的导数 -------- */
|
||||
public func backward(y: Matrix1) {
|
||||
match(this) {
|
||||
case None => none_dot(y)
|
||||
case Sigmoid => sigmoid_dot(y)
|
||||
case ReLU => relu_dot(y)
|
||||
case Leaky_ReLU(alpha) => leaky_relu_dot(y, alpha)
|
||||
case Tanh => tanh_dot(y)
|
||||
case Softmax => softmax_jacobian(y)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 序列化与反序列化 -------- */
|
||||
public func get_type(): String {
|
||||
return match(this) {
|
||||
case None => "None"
|
||||
case Sigmoid => "Sigmoid"
|
||||
case ReLU => "ReLU"
|
||||
case Leaky_ReLU(alpha) => "Leaky_ReLU"
|
||||
case Tanh => "Tanh"
|
||||
case Softmax => "Softmax"
|
||||
}
|
||||
}
|
||||
|
||||
/* */
|
||||
public func get_value(): FP32 {
|
||||
return match(this) {
|
||||
case None => FP32(0.0f32)
|
||||
case Sigmoid => FP32(0.0f32)
|
||||
case ReLU => FP32(0.0f32)
|
||||
case Leaky_ReLU(alpha) => alpha
|
||||
case Tanh => FP32(0.0f32)
|
||||
case Softmax => FP32(0.0f32)
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- 空的激活函数 -------- */
|
||||
private static func none(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
N.set(r, c, FP32(x1)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 空激活函数的导数 -------- */
|
||||
private static func none_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
N.set(r, c, FP32(1.0f32)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- S型生长曲线 -------- */
|
||||
private static func sigmoid(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- S型生长曲线的导数 -------- */
|
||||
private static func sigmoid_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
|
||||
let x3 = x2 * (1.0f32 - x2)
|
||||
N.set(r, c, FP32(x3)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 线性整流单元 -------- */
|
||||
private static func relu(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {x1} else{0.0f32}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 线性整流单元的导数 -------- */
|
||||
private static func relu_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {1.0f32} else{0.0f32}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 带泄露的线性整流单元 -------- */
|
||||
private static func leaky_relu(M: Matrix1, alpha: FP32) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) {x1} else{x1 * alpha.getValue()}
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 带泄露的整流单元导数 -------- */
|
||||
private static func leaky_relu_dot(M: Matrix1, alpha: FP32) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 =if (x1 > 0.0f32) { 1.0f32 } else{alpha.getValue()}
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 双曲正切函数 --------*/
|
||||
private static func tanh(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = tanh(x1)
|
||||
N.set(r, c, FP32(x2)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 双曲正切函数的导数 -------- */
|
||||
private static func tanh_dot(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let y = M.get(r, c).getOrThrow()
|
||||
let y_val= y.getValue() /*---- NOTE(20260525-qing): 修复命名(x => y) ----*/
|
||||
let rea = 1.0f32 - y_val * y_val
|
||||
N.set(r, c, FP32(rea)) }
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 归一化指数函数 -------- */
|
||||
private static func softmax(M: Matrix1) {
|
||||
let row = M.get_row()
|
||||
let col = M.get_col()
|
||||
let N = Matrix1(row, col)
|
||||
var max_val = 0.0f32
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
if (x1 > max_val) {
|
||||
max_val = x1 }
|
||||
}
|
||||
}
|
||||
var sum = 0.0f32
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = M.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = exp(x1 - max_val)
|
||||
N.set(r, c, FP32(x2))
|
||||
sum += x2
|
||||
}
|
||||
}
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let x = N.get(r, c).getOrThrow()
|
||||
let x1= x.getValue()
|
||||
let x2 = x1 / sum
|
||||
N.set(r, c, FP32(x2))
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 计算Softmax的雅可比矩阵 --------
|
||||
* 输入:已计算好的softmax输出(概率分布)
|
||||
* 输出:雅可比矩阵 J[i][j] = ∂y_i/∂x_j */
|
||||
private func softmax_jacobian(softmax_output: Matrix1) {
|
||||
let row = softmax_output.get_row()
|
||||
let col = softmax_output.get_col()
|
||||
let n = row * col
|
||||
var jacobian = Matrix1(n, n)
|
||||
|
||||
for (i in 0..n) {
|
||||
for (j in 0..n) {
|
||||
|
||||
let row_i = i / col /* 需要将一维索引转换为二维索引来访问Matrix1 */
|
||||
let col_i = i % col
|
||||
let row_j = j / col
|
||||
let col_j = j % col
|
||||
|
||||
let val_i = softmax_output.get(row_i, col_i).getOrThrow().getValue()
|
||||
let val_j = softmax_output.get(row_j, col_j).getOrThrow().getValue()
|
||||
|
||||
if (i == j) { /* 对角线元素: y_i * (1 - y_i) */
|
||||
jacobian.set(i, j, FP32(val_i * (1.0f32 - val_i))) }
|
||||
else { /* 非对角线元素: -y_i * y_j */
|
||||
jacobian.set(i, j, FP32(-val_i * val_j)) }
|
||||
}
|
||||
}
|
||||
|
||||
return jacobian
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package bot4cj.math
|
||||
import std.random.*
|
||||
import std.time.*
|
||||
import std.math.*
|
||||
|
||||
/* -------- 神经网络使用的权重矩阵初始化类 --------
|
||||
* TODO(20260517-qing): 将该函数改为枚举类,统一一个接口,根据模式匹配执行初始化
|
||||
* - 这样在矩阵式中可以定义初始化方式 */
|
||||
public class Ini {
|
||||
|
||||
/* -------- xavier正态初始化 -------- */
|
||||
public static func xavierNormalInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
|
||||
let seed = DateTime.now().nanosecond
|
||||
let random = Random(UInt64(seed))
|
||||
let sigma = sqrt(2.0f32 / Float32(fan_in + fan_out)) /* 计算标准差 */
|
||||
for (r in 0..fan_in) {
|
||||
for (c in 0..fan_out) {
|
||||
let v = random.nextGaussianFloat32(mean: 0.0f32, sigma:sigma)
|
||||
target.set(r, c, FP32(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------- Glorot均匀初始化 -------- */
|
||||
public static func xavierUniformInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
|
||||
let limit = sqrt(6.0f32 / Float32(fan_in + fan_out))
|
||||
let seed = DateTime.now().nanosecond
|
||||
let random = Random(UInt64(seed))
|
||||
|
||||
for (r in 0..fan_in) {
|
||||
for (c in 0..fan_out) {
|
||||
let v = (random.nextFloat32() * 2.0f32 - 1.0f32) * limit /* 均匀分布 [-limit, limit] */
|
||||
target.set(r, c, FP32(v)) }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
package bot4cj.math
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.convert.*
|
||||
//foreign func createMatrix(row: UInt64, col: UInt64): CPointer<Unit>
|
||||
//foreign func freeMatrix(M: CPointer<Unit>): Unit
|
||||
//foreign func witchMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): CPointer<Float32>
|
||||
//foreign func getMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): Float32
|
||||
//foreign func setMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64, v: Float32): Unit
|
||||
//foreign func getrowMatrix(M: CPointer<Unit>): UInt64
|
||||
//foreign func getcolMatrix(M: CPointer<Unit>): UInt64
|
||||
//foreign func sameShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
|
||||
//foreign func dotShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
|
||||
//foreign func plusMatrix(M: CPointer<Unit>, S:CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func plusBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func subMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func subBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func multiMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func multiBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func divMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func divBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
|
||||
//foreign func dotMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func transposeMatrix(M: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func xavierNormalInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
|
||||
//foreign func xavierUniformInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
|
||||
|
||||
|
||||
/* -------- 32位浮点数 -------- */
|
||||
public struct FP32 {
|
||||
private var val: Float32
|
||||
public init(t: Float32) { /* 以Float32初始化对象 */
|
||||
val = t }
|
||||
public func getValue() { /* 获取值 */
|
||||
return val }
|
||||
/* -------- 运算符重载 -------- */
|
||||
//public operator func =(t: Float32) { /*-------- this can not --------*/
|
||||
// val = t }
|
||||
public operator func +(t: Float32) {
|
||||
return FP32(val + t)}
|
||||
public operator func +(S: FP32) {
|
||||
return this + S.getValue() }
|
||||
public operator func -(t: Float32) {
|
||||
return FP32(val - t) }
|
||||
public operator func -(S: FP32) {
|
||||
return this - S.getValue() }
|
||||
public operator func *(t: Float32) {
|
||||
return FP32(val * t) }
|
||||
public operator func *(S: FP32) {
|
||||
return this * S.getValue() }
|
||||
public operator func /(t: Float32) {
|
||||
return FP32(val / t) }
|
||||
public operator func /(S: FP32) {
|
||||
return this / S.getValue() }
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 新版的矩阵对象,仓颉原生实现
|
||||
* TODO(20260517-qing): 设计原生的多维数组类
|
||||
*
|
||||
|
||||
___col___
|
||||
/ \
|
||||
+---------+ \
|
||||
| + + + + | | row - 第一维度
|
||||
| + + + + | |
|
||||
+---------+ /
|
||||
|
||||
|
||||
* TODO(20260523-qing): 以某种方式统一矩阵式的字符串矩阵和这里的数值矩阵
|
||||
*/
|
||||
public class Matrix1 {
|
||||
|
||||
private var row: Int64 /* ---- 行数 ---- */
|
||||
private var col: Int64 /* ---- 列数 ---- */
|
||||
private var arr: Array<FP32> /* ---- 数据 ---- */
|
||||
|
||||
/* -------- 构造器,默认值为0 -------- */
|
||||
public Matrix1(row: Int64, col: Int64) {
|
||||
this.row = row
|
||||
this.col = col
|
||||
this.arr = Array<FP32>(row * col, {_ => FP32(0.0f32)})
|
||||
}
|
||||
|
||||
/* --------- 这几个方法是访问器 -------- */
|
||||
/* ------------------------------------- */
|
||||
/* -------- TODO(20260524-qing): 使用prop成员 -------- */
|
||||
|
||||
public func get(r: Int64, c: Int64) {
|
||||
return arr.get(r * this.col + c)
|
||||
}
|
||||
|
||||
public func set(r: Int64, c: Int64, v: FP32) {
|
||||
arr[r * this.col + c] = v
|
||||
}
|
||||
|
||||
public func get_row() { return row }
|
||||
|
||||
public func get_col() { return col }
|
||||
|
||||
/* -------- 这两个函数是比较器 -------- */
|
||||
/* ------------------------------------ */
|
||||
public func has_same_shape(other: Matrix1) {
|
||||
let a = this.get_row() == other.get_row()
|
||||
let b = this.get_col() == other.get_col()
|
||||
return a && b
|
||||
}
|
||||
|
||||
public func has_dot_shape(other: Matrix1) {
|
||||
return this.get_col() == other.get_row()
|
||||
}
|
||||
|
||||
/* -------- 这些是运算符重载 -------- */
|
||||
/* ---------------------------------- */
|
||||
public operator func +(a: FP32) { /* ---- 矩阵广播加法 ---- */
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v + a)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func +(S: Matrix1) { /* ---- 矩阵加法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v + vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func -(a: FP32) { /* ---- 矩阵广播减法 ----*/
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v - a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func -(S: Matrix1) { /* ---- 矩阵减法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v - vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func *(a: FP32) { /* ---- 矩阵广播乘法 ---- */
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v * a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func *(S: Matrix1) { /* ---- 逐元素相乘 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v * vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func /(a: FP32) { /* ---- 矩阵广播除法 ---- */
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c){
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v / a)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
public operator func /(S: Matrix1) { /* ---- 逐元素除法 ---- */
|
||||
if (!has_same_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = this.get(ir, ic).getOrThrow()
|
||||
let vs = S.get(ir, ic).getOrThrow()
|
||||
M.set(ir, ic, v / vs)
|
||||
}
|
||||
}
|
||||
|
||||
return M
|
||||
}
|
||||
|
||||
/* ------------------------------ */
|
||||
/* -------- 矩阵点积乘法 -------- */
|
||||
public func dot(S: Matrix1) {
|
||||
if (!has_dot_shape(S)) {
|
||||
throw IllegalArgumentException("Matrixes doesn't match")
|
||||
}
|
||||
let r =get_row()
|
||||
let c = S.get_col()
|
||||
let M = Matrix1(r, c)
|
||||
for (ir in 0..get_row()) {
|
||||
for (ic in 0..S.get_col()) {
|
||||
var sum = FP32(0.0f32)
|
||||
for (ik in 0..get_col()) {
|
||||
let a = get(ir, ik).getOrThrow()
|
||||
let b = S.get(ik, ic).getOrThrow()
|
||||
sum += a * b
|
||||
}
|
||||
M.set(ir, ic, sum)
|
||||
}
|
||||
}
|
||||
return M
|
||||
}
|
||||
|
||||
/* -------- 克隆一个一模一样的矩阵 -------- */
|
||||
public func clone() {
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let N = Matrix1(r, c)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = get(ir, ic).getOrThrow()
|
||||
N.set(ir, ic, v)
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
|
||||
/* -------- 矩阵转置,返回转置后的矩阵 -------- */
|
||||
public func T() {
|
||||
let r = get_row()
|
||||
let c = get_col()
|
||||
let N = Matrix1(c, r)
|
||||
for (ir in 0..r) {
|
||||
for (ic in 0..c) {
|
||||
let v = get(ir, ic).getOrThrow()
|
||||
N.set(ic, ir, v)
|
||||
}
|
||||
}
|
||||
return N
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* -------- 旧版的矩阵类,依赖c++编译的共享动态库 --------
|
||||
* TODO: 将矩阵元数据存储在仓颉部分(done at 202605xx ) */
|
||||
//public class Matrix {
|
||||
//
|
||||
// /* -------- 主初始化器,初始化并获取资源 -------- */
|
||||
// public init(row: UInt64, col: UInt64) {
|
||||
// unsafe { M = createMatrix(row, col) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 初始化器,利用已经存在的元数据进行构建 -------- */
|
||||
// public Matrix(m: CPointer<Unit>) { M = m }
|
||||
//
|
||||
// /* -------- Xavier正态分布初始化 -------- */
|
||||
// public static func initWithXavierNormal(fan_in: UInt64, fan_out: UInt64) {
|
||||
// unsafe {
|
||||
// let m = xavierNormalInit(fan_in, fan_out);
|
||||
// return Matrix(m)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- Glorot均匀初始化 -------- */
|
||||
// public static func initWithXavierUniform(fan_in: UInt64, fan_out: UInt64) {
|
||||
// unsafe {
|
||||
// let m = xavierUniformInit(fan_in, fan_out);
|
||||
// return Matrix(m)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* ======== 在析构函数中释放资源 ======== */
|
||||
// ~init() {
|
||||
// unsafe { freeMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 取下标,由于不能返回值类型的引用被弃用 -------- */
|
||||
// //public operator func [](r: UInt64, c: UInt64): CPointer<Float32> {
|
||||
// // unsafe { return witchMatrix(M, r, c) }
|
||||
// //}
|
||||
//
|
||||
// /* -------- 获得某个矩阵单元的值 -------- */
|
||||
// public func get(r: UInt64, c: UInt64): Float32 {
|
||||
// unsafe { return getMatrix(M, r, c) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 设置某个矩阵单元的值 --------*/
|
||||
// public func set(r: UInt64, c: UInt64, v: Float32) {
|
||||
// unsafe { setMatrix(M, r, c, v) }
|
||||
// }
|
||||
//
|
||||
// /* ------- 两个矩阵形状是否相同 --------
|
||||
// * FIXME: 这个函数所重载的运算符不贴切 */
|
||||
// public operator func ==(s: Matrix): Bool {
|
||||
// unsafe { return sameShapeMatrix(M, s.getData()) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获得行数 -------- */
|
||||
// public func getRow(): UInt64 {
|
||||
// unsafe { return getrowMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获取列数 --------*/
|
||||
// public func getCol(): UInt64 {
|
||||
// unsafe { return getcolMatrix(M) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 获取指向元数据的C风格指针 -------- */
|
||||
// public func getData(): CPointer<Unit> { return M; }
|
||||
//
|
||||
// /* -------- 是否两个矩阵具有点积和的形状 -------- */
|
||||
// public func hasDotShapeWith(s: Matrix): Bool {
|
||||
// unsafe { return dotShapeMatrix(M, s.getData()) }
|
||||
// }
|
||||
//
|
||||
// /* -------- 逐元素算数运算 -------- */
|
||||
// /* -------------------------------- */
|
||||
// public operator func +(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!sameShapeMatrix(M, s.getData())) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = plusMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func +(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = plusBroadcastMatrix(M, t)
|
||||
// return Matrix(r);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func -(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = subMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func -(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = subBroadcastMatrix(M, t)
|
||||
// return Matrix(r);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func *(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = multiMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func *(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = multiBroadcastMatrix(M, t)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func /(s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!(this == s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not same shape")
|
||||
// }
|
||||
// let r = divMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public operator func /(t: Float32): Matrix {
|
||||
// unsafe {
|
||||
// let r = divBroadcastMatrix(M, t)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------------------------------- */
|
||||
// /* -------------------------------- */
|
||||
//
|
||||
//
|
||||
// /* -------- 矩阵点积和算法 -------- */
|
||||
// public func dot (s: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// if (!hasDotShapeWith(s)) {
|
||||
// println('------------')
|
||||
// print_shape()
|
||||
// s.print_shape()
|
||||
// println('------------')
|
||||
// throw IllegalArgumentException("not dot shape")
|
||||
// }
|
||||
// let r = dotMatrix(M, s.getData())
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 矩阵转置 -------- */
|
||||
// public func T(): Matrix {
|
||||
// unsafe {
|
||||
// let r = transposeMatrix(M)
|
||||
// return Matrix(r)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印矩阵 -------- */
|
||||
// public func print(): Unit {
|
||||
// unsafe {
|
||||
// for (i in 0..getRow()) {
|
||||
// for (j in 0..getCol()) {
|
||||
// let a = this.get(i, j)
|
||||
// print(a)
|
||||
// print(" ")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// println()
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印形状 -------- */
|
||||
// public func print_shape() {
|
||||
// println("${getRow()}, ${getCol()}")
|
||||
// }
|
||||
//
|
||||
// /* -------- 保存矩阵 -------- */
|
||||
// public func save(file: File) {
|
||||
// unsafe {
|
||||
// for (i in 0..getRow()) {
|
||||
// for (j in 0..getCol()) {
|
||||
// file.write(this.get(i, j).toString().toArray())
|
||||
// if (j < getCol()-1) {
|
||||
// file.write(" ".toArray()) }
|
||||
// else { file.write("\n".toArray()) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载单独项 -------- */
|
||||
// private static func loadItem(sr: StringReader<InputStream>) {
|
||||
// let s = sr.readUntil({r => r==r' ' || r ==r'\n'})
|
||||
// return Float32.parse(s.getOrThrow())
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载矩阵 -------- */
|
||||
// public static func load(sr: StringReader<InputStream>, row: UInt64, col: UInt64): Matrix {
|
||||
// let m = Matrix(row, col)
|
||||
// unsafe {
|
||||
// for (i in 0..m.getRow()) {
|
||||
// for (j in 0..m.getCol()) {
|
||||
// let f = loadItem(sr)
|
||||
// m.set(i, j, f)
|
||||
// }
|
||||
// }
|
||||
// m
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /* -------- 矩阵的C风格指针 -------- */
|
||||
// private var M: CPointer<Unit>
|
||||
//}
|
||||
@@ -0,0 +1,56 @@
|
||||
package bot4cj.math
|
||||
import std.collection.*
|
||||
|
||||
/* -------- 张量 --------
|
||||
* INFO(20260524-qing): 输入一个维度列表 */
|
||||
class Tensor<T> {
|
||||
private let data: Array<T>
|
||||
private let dims: Array<Int64>
|
||||
private let size: Int64
|
||||
public init(dims: Array<Int64>, val: T) {
|
||||
var size = 1
|
||||
for (item in dims) { /* ---- 计算总量 ---- */
|
||||
size *= item
|
||||
}
|
||||
this.data = Array<T>(size, {_ => val})
|
||||
this.dims = dims
|
||||
this.size = size
|
||||
}
|
||||
|
||||
/* -------- 私有构造函数,用于内部创建张量 -------- */
|
||||
private init(dims: Array<Int64>, data: Array<T>) {
|
||||
this.dims = dims
|
||||
this.data = data
|
||||
var size = 1
|
||||
for (item in dims) {
|
||||
size *= item
|
||||
}
|
||||
this.size = size
|
||||
}
|
||||
|
||||
/* -------- 获取维度列表(形状) -------- */
|
||||
public func shape(): Array<Int64> {
|
||||
return dims
|
||||
}
|
||||
|
||||
/* -------- 逐元素进行某种运算 -------- */
|
||||
public func oneByOne(S: Tensor<T>, f: (T, T) -> T): Tensor<T> {
|
||||
let s1 = S.shape()
|
||||
|
||||
/* ---- 比较维度数量 ---- */
|
||||
if (s1 != dims) {
|
||||
throw IllegalArgumentException("Tensors' shape are not same")
|
||||
}
|
||||
|
||||
/* ---- 比较维度形状 ----*/
|
||||
for (i in 0..dims.size) {
|
||||
if (s1[i] != dims[i]) {
|
||||
throw IllegalArgumentException("Tensors' shape are not same")
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 逐个进行计算 ---- */
|
||||
let res_data = Array<T>(size, {i => f(data[i], S.data[i])})
|
||||
return Tensor<T>(dims, res_data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// cnn.cj - 修正 forward 中的偏置加法
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
public class ConvLayer {
|
||||
|
||||
let nInputChannels: Int64
|
||||
let nOutputChannels: Int64
|
||||
let kernelHeight: Int64
|
||||
let kernelWidth: Int64
|
||||
let stride: Int64
|
||||
let padding: Int64
|
||||
let learningRate: FP32
|
||||
let f: Af
|
||||
|
||||
var connectingMatrix: Matrix1
|
||||
var kernels: Array<Matrix1>
|
||||
var biases: Matrix1
|
||||
|
||||
var prevInput: Option<Matrix1> = Option.None
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevCombination: Option<Matrix1> = Option.None
|
||||
var prevOutput: Option<Matrix1> = Option.None
|
||||
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
|
||||
var kernelsGrad: Option<Array<Matrix1>> = Option.None
|
||||
var biasGrad: Option<Matrix1> = Option.None
|
||||
|
||||
public init(
|
||||
inCh: Int64,
|
||||
outCh: Int64,
|
||||
kernelSize: (Int64, Int64),
|
||||
stride: Int64,
|
||||
padding: Int64,
|
||||
lr: FP32,
|
||||
activation: Af,
|
||||
connMatrix: ?Matrix1
|
||||
) {
|
||||
nInputChannels = inCh
|
||||
nOutputChannels = outCh
|
||||
kernelHeight = kernelSize[0]
|
||||
kernelWidth = kernelSize[1]
|
||||
this.stride = stride
|
||||
this.padding = padding
|
||||
learningRate = lr
|
||||
this.f = activation
|
||||
|
||||
// 初始化 connectingMatrix
|
||||
connectingMatrix = match (connMatrix) {
|
||||
case m: Matrix1 => m
|
||||
case _ => Matrix1(nInputChannels, nOutputChannels)
|
||||
}
|
||||
let isNone = match (connMatrix) {
|
||||
case _: Matrix1 => false
|
||||
case _ => true
|
||||
}
|
||||
if (isNone) {
|
||||
for (r in 0..nInputChannels) {
|
||||
for (c in 0..nOutputChannels) {
|
||||
connectingMatrix.set(r, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化 kernels
|
||||
kernels = Array(nOutputChannels, { _ => Matrix1(1, 1) })
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var nConnections = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let val = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (val.getValue() > 0.0) {
|
||||
nConnections += 1
|
||||
}
|
||||
}
|
||||
let kernelLen = nConnections * kernelHeight * kernelWidth
|
||||
let kernel = Matrix1(1, kernelLen)
|
||||
|
||||
Ini.xavierUniformInit(kernel, 1, kernelLen)
|
||||
|
||||
kernels[oc] = kernel
|
||||
}
|
||||
|
||||
// 初始化 biases
|
||||
biases = Matrix1(1, nOutputChannels)
|
||||
Ini.xavierUniformInit(biases, 1, nOutputChannels)
|
||||
}
|
||||
|
||||
// ==================== 前向传播 ====================
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = Option.Some(x)
|
||||
prevInputShape = Option.Some(inputShape)
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
|
||||
let outH = (inH + 2 * padding - kernelHeight) / stride + 1
|
||||
let outW = (inW + 2 * padding - kernelWidth) / stride + 1
|
||||
let outSize = batch * outH * outW
|
||||
|
||||
var combination = Matrix1(outSize, nOutputChannels)
|
||||
var output = Matrix1(outSize, nOutputChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let outRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var sum = FP32(0.0)
|
||||
var kernelIdx = 0
|
||||
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let xVal = x.get(inRow, ic).getOrThrow()
|
||||
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
|
||||
sum = sum + xVal * kVal
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
combination.set(outRow, oc, sum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 手动广播偏置加法(因为 Matrix1 的 + 要求形状相同)
|
||||
for (r in 0..outSize) {
|
||||
for (c in 0..nOutputChannels) {
|
||||
let val = combination.get(r, c).getOrThrow()
|
||||
let biasVal = biases.get(0, c).getOrThrow()
|
||||
combination.set(r, c, val + biasVal)
|
||||
}
|
||||
}
|
||||
|
||||
prevCombination = Option.Some(combination)
|
||||
|
||||
output = f.forward(combination)
|
||||
prevOutput = Option.Some(output)
|
||||
prevOutputShape = Option.Some((batch, outH, outW, nOutputChannels))
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// ==================== 反向传播 ====================
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var x = Matrix1(1, 1)
|
||||
match (prevInput) {
|
||||
case Some(T) => x = T
|
||||
case _ => throw Exception("No input data")
|
||||
}
|
||||
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(T) => shape = T
|
||||
case _ => throw Exception("No input shape")
|
||||
}
|
||||
|
||||
var combination = Matrix1(1, 1)
|
||||
match (prevCombination) {
|
||||
case Some(val) => combination = val
|
||||
case _ => throw Exception("No combination")
|
||||
}
|
||||
|
||||
var outShape = (0, 0, 0, 0)
|
||||
match (prevOutputShape) {
|
||||
case Some(val) => outShape = val
|
||||
case _ => throw Exception("No output shape")
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let outH = outShape[1]
|
||||
let outW = outShape[2]
|
||||
|
||||
let delta = errs * f.backward(combination)
|
||||
|
||||
calcKernelGradient(x, delta, batch, inH, inW, outH, outW)
|
||||
calcBiasGradient(delta)
|
||||
|
||||
let errsUp = calcUpsampledError(delta, batch, inH, inW, outH, outW)
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算卷积核梯度
|
||||
*/
|
||||
private func calcKernelGradient(
|
||||
x: Matrix1,
|
||||
delta: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Unit {
|
||||
var grads = Array(nOutputChannels, { _ => Matrix1(1, 1) })
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let kernelCols = kernels[oc].get_col()
|
||||
var grad = Matrix1(1, kernelCols)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
|
||||
|
||||
var kernelIdx = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let xVal = x.get(inRow, ic).getOrThrow()
|
||||
let oldGrad = grad.get(0, kernelIdx).getOrThrow()
|
||||
grad.set(0, kernelIdx, oldGrad + xVal * deltaVal)
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
grads[oc] = grad
|
||||
}
|
||||
|
||||
kernelsGrad = Option.Some(grads)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算偏置梯度
|
||||
*/
|
||||
private func calcBiasGradient(delta: Matrix1): Unit {
|
||||
let rows = delta.get_row()
|
||||
var grad = Matrix1(1, nOutputChannels)
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
var sum = FP32(0.0)
|
||||
for (r in 0..rows) {
|
||||
sum = sum + delta.get(r, oc).getOrThrow()
|
||||
}
|
||||
grad.set(0, oc, sum)
|
||||
}
|
||||
|
||||
biasGrad = Option.Some(grad)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算传递到上一层的误差
|
||||
*/
|
||||
private func calcUpsampledError(
|
||||
delta: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Matrix1 {
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nInputChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
|
||||
|
||||
var kernelIdx = 0
|
||||
for (ic in 0..nInputChannels) {
|
||||
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
|
||||
if (connVal.getValue() > 0.0) {
|
||||
for (kh in 0..kernelHeight) {
|
||||
for (kw in 0..kernelWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
|
||||
let oldErr = errsUp.get(inRow, ic).getOrThrow()
|
||||
errsUp.set(inRow, ic, oldErr + deltaVal * kVal)
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
// ==================== 参数更新 ====================
|
||||
public func update(discount: Float32): Unit {
|
||||
var kGrad = Array(0, { _ => Matrix1(1, 1) })
|
||||
match (kernelsGrad) {
|
||||
case Some(val) => kGrad = val
|
||||
case _ => throw Exception("No kernel gradients")
|
||||
}
|
||||
|
||||
var bGrad = Matrix1(1, 1)
|
||||
match (biasGrad) {
|
||||
case Some(val) => bGrad = val
|
||||
case _ => throw Exception("No bias gradient")
|
||||
}
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let rows = kernels[oc].get_row()
|
||||
let cols = kernels[oc].get_col()
|
||||
for (r in 0..rows) {
|
||||
for (c in 0..cols) {
|
||||
let oldVal = kernels[oc].get(r, c).getOrThrow()
|
||||
let gradVal = kGrad[oc].get(r, c).getOrThrow()
|
||||
kernels[oc].set(r, c, oldVal - gradVal * learningRate * FP32(discount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (oc in 0..nOutputChannels) {
|
||||
let oldBias = biases.get(0, oc).getOrThrow()
|
||||
let gradBias = bGrad.get(0, oc).getOrThrow()
|
||||
biases.set(0, oc, oldBias - gradBias * learningRate * FP32(discount))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 形状访问器 ====================
|
||||
public func getInShape(): (Int64, Int64) {
|
||||
(nInputChannels, kernelHeight * kernelWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
nOutputChannels
|
||||
}
|
||||
|
||||
public func getLR(): FP32 {
|
||||
learningRate
|
||||
}
|
||||
|
||||
public func getFType(): String {
|
||||
f.get_type()
|
||||
}
|
||||
|
||||
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
|
||||
let outH = (inputHeight + 2 * padding - kernelHeight) / stride + 1
|
||||
let outW = (inputWidth + 2 * padding - kernelWidth) / stride + 1
|
||||
(outH, outW)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package bot4cj.math
|
||||
//foreign func start_fft(M: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func gen_wave(): CPointer<Unit>
|
||||
/*-------- 生成测试用的正弦波 --------*/
|
||||
//public func genWave(): Matrix {
|
||||
// unsafe { return Matrix(gen_wave()) }
|
||||
//}
|
||||
/*-------- 执行fft算法 --------*/
|
||||
//public func start_fft(M: Matrix): Matrix {
|
||||
// unsafe { return Matrix(start_fft(M.getData())) }
|
||||
//}
|
||||
@@ -0,0 +1,136 @@
|
||||
// flatten.cj - 移除调试代码
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
public class FlattenLayer {
|
||||
|
||||
let nInputChannels: Int64
|
||||
let inputHeight: Int64
|
||||
let inputWidth: Int64
|
||||
let outputSize: Int64
|
||||
|
||||
var prevInput: Option<Matrix1>
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)>
|
||||
var prevOutput: Option<Matrix1>
|
||||
var prevOutputShape: Option<(Int64, Int64)>
|
||||
|
||||
public init(inputChannels: Int64, inputHeight: Int64, inputWidth: Int64) {
|
||||
nInputChannels = inputChannels
|
||||
this.inputHeight = inputHeight
|
||||
this.inputWidth = inputWidth
|
||||
outputSize = inputChannels * inputHeight * inputWidth
|
||||
|
||||
prevInput = None
|
||||
prevInputShape = None
|
||||
prevOutput = None
|
||||
prevOutputShape = None
|
||||
}
|
||||
|
||||
public init(inputShape: (Int64, Int64, Int64)) {
|
||||
nInputChannels = inputShape[0]
|
||||
inputHeight = inputShape[1]
|
||||
inputWidth = inputShape[2]
|
||||
outputSize = nInputChannels * inputHeight * inputWidth
|
||||
|
||||
prevInput = None
|
||||
prevInputShape = None
|
||||
prevOutput = None
|
||||
prevOutputShape = None
|
||||
}
|
||||
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = x
|
||||
prevInputShape = inputShape
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
let inCh = inputShape[3]
|
||||
|
||||
let totalRows = batch * outputSize
|
||||
var output = Matrix1(totalRows, 1)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (h in 0..inH) {
|
||||
for (w in 0..inW) {
|
||||
for (ch in 0..inCh) {
|
||||
let inRow = b * inH * inW + h * inW + w
|
||||
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
|
||||
if (outRow < totalRows) {
|
||||
let val = x.get(inRow, ch).getOrThrow()
|
||||
output.set(outRow, 0, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevOutput = output
|
||||
prevOutputShape = (totalRows, 1)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(T) => shape = T
|
||||
case _ => throw Exception("FlattenLayer.backward: No input shape. Call forward() first.")
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let inCh = shape[3]
|
||||
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nInputChannels)
|
||||
|
||||
let errsCols = errs.get_col()
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (h in 0..inH) {
|
||||
for (w in 0..inW) {
|
||||
let inRow = b * inH * inW + h * inW + w
|
||||
|
||||
for (ch in 0..inCh) {
|
||||
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
|
||||
|
||||
if (outRow < errs.get_row()) {
|
||||
if (errsCols == 1) {
|
||||
let val = errs.get(outRow, 0).getOrThrow()
|
||||
errsUp.set(inRow, ch, val)
|
||||
} else {
|
||||
var sum = FP32(0.0)
|
||||
for (col in 0..errsCols) {
|
||||
sum = sum + errs.get(outRow, col).getOrThrow()
|
||||
}
|
||||
errsUp.set(inRow, ch, sum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
public func update(discount: Float32): Unit {
|
||||
// 无参数
|
||||
}
|
||||
|
||||
public func getInShape(): (Int64, Int64, Int64) {
|
||||
(nInputChannels, inputHeight, inputWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
outputSize
|
||||
}
|
||||
|
||||
public func getOutputSize(): Int64 {
|
||||
outputSize
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package bot4cj.math
|
||||
|
||||
/**
|
||||
* 一维卡尔曼滤波器
|
||||
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
|
||||
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
|
||||
* 使用示例
|
||||
* extKalman_t p; //定义一个卡尔曼滤波器结构体
|
||||
* float SersorData; //需要进行滤波的数据
|
||||
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
|
||||
* while(1)
|
||||
* {
|
||||
* SersorData = sersor(); //获取数据
|
||||
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
|
||||
* }
|
||||
*/
|
||||
@C
|
||||
public struct extKalman {
|
||||
var X_last: Float32 // 上一时刻的最优结果 X(k-|k-1)
|
||||
var P_last: Float32 // 上一时刻最优结果的协方差 P(k-1|k-1)
|
||||
var A: Float32 // 系统参数
|
||||
var B: Float32
|
||||
var Q: Float32
|
||||
var R: Float32
|
||||
var H: Float32
|
||||
|
||||
/**
|
||||
* @name kalmanCreate
|
||||
* @brief 创建一个卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* T_Q:系统噪声协方差
|
||||
* T_R:测量噪声协方差
|
||||
*
|
||||
* @retval none
|
||||
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
|
||||
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
|
||||
*/
|
||||
public init(T_Q: Float32, T_R: Float32) {
|
||||
X_last = 0.0f32
|
||||
P_last = 0.0f32
|
||||
Q = T_Q
|
||||
R = T_R
|
||||
A = 1.0f32
|
||||
B = 0.0f32
|
||||
H = 1.0f32
|
||||
}
|
||||
|
||||
/**
|
||||
* @name KalmanFilter
|
||||
* @brief 卡尔曼滤波器
|
||||
* @param p: 滤波器
|
||||
* dat:待滤波数据
|
||||
* @retval 滤波后的数据
|
||||
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
|
||||
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
|
||||
* 以下是卡尔曼的5个核心公式
|
||||
* 一阶H'即为它本身,否则为转置矩阵
|
||||
*/
|
||||
public mut func filter(dat: Float32): Float32 {
|
||||
let X_mid = A * X_last //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
|
||||
let P_mid = A * P_last + Q //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
|
||||
let kg = P_mid / (P_mid + R) //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
|
||||
let X_now = X_mid + kg * (dat - X_mid) //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
|
||||
let P_now = (1.0f32 - kg) * P_mid //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
|
||||
P_last = P_now
|
||||
X_last = X_now
|
||||
return X_now //输出预测结果x(k|k)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
package bot4cj.math
|
||||
import std.fs.*
|
||||
import std.io.*
|
||||
import std.convert.*
|
||||
//foreign func Sigmoid_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Sigmoid_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func ReLU_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func ReLU_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Leaky_ReLU_do(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
|
||||
//foreign func Leaky_ReLU_dot(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
|
||||
//foreign func Tanh_do(m: CPointer<Unit>): CPointer<Unit>
|
||||
//foreign func Tanh_dot(m: CPointer<Unit>): CPointer<Unit>
|
||||
|
||||
|
||||
/* -------- 新版的神经网络,使用原生矩阵对象实现 --------
|
||||
* TODO(20260517-qing): 增加batch批次处理,使用ArrayList<Matrix1>
|
||||
* TODO*(20260519-qing): 将神经网络转换为MatrixObject矩阵式对象
|
||||
* TODO(20260524-qing): 增加Adam优化器 */
|
||||
|
||||
public class Nn1 {
|
||||
|
||||
let inputno: Int64 /* -------- 输入 -------- */
|
||||
let outputno: Int64 /* -------- 输出 -------- */
|
||||
var weights: Matrix1 /* -- (inputs, outputs) - */
|
||||
var bias: Matrix1 /* ----- (1, output) ---- */
|
||||
let learning_rate: FP32 /* ------- 学习率 ------- */
|
||||
let f: Af /* ------ 激活函数 ------ */
|
||||
|
||||
/*------------------*/
|
||||
var prev_inputs: Option<Matrix1> = None /* ---- 之前的输入 ---- */
|
||||
var prev_raw_inputs: Option<Matrix1> = None /* ---- 之前的原始输出 ---- */
|
||||
var prev_outputs: Option<Matrix1> = None /* ---- 之前的输出 ---- */
|
||||
/*------------------*/
|
||||
var weights_grad: Option<Matrix1> = None /* ---- 权重梯度 ---- */
|
||||
var bias_grad: Option<Matrix1> = None /* ---- 偏置梯度 ---- */
|
||||
|
||||
|
||||
/* -------- 初始化器 --------
|
||||
* TODO(20260516-qing): 不要把初始化方法写死,使用枚举类 */
|
||||
public init(i: Int64, o: Int64, lr: Float32, f: Af) {
|
||||
inputno = i
|
||||
outputno =o
|
||||
learning_rate=FP32(lr)
|
||||
this.f = f
|
||||
weights = Matrix1(inputno, outputno)
|
||||
bias = Matrix1(1, outputno)
|
||||
|
||||
/* -------- 初始化权重 -------- */
|
||||
Ini.xavierUniformInit(weights, inputno, outputno)
|
||||
}
|
||||
|
||||
/* -------- 神经网络推理 -------- */
|
||||
public func forward(x: Matrix1) {
|
||||
prev_inputs = x
|
||||
var r = x.dot(weights)
|
||||
r = r + bias
|
||||
prev_raw_inputs = r
|
||||
let r1 = f.forward(r)
|
||||
prev_outputs = r1
|
||||
return r1
|
||||
}
|
||||
|
||||
/* -------- 反向传播算法 -------- */
|
||||
public func backward(errs: Matrix1)
|
||||
{
|
||||
if (is_softmax(f)) {
|
||||
return softmax_backward(errs)
|
||||
}
|
||||
let fbak = f.backward(prev_raw_inputs.getOrThrow())
|
||||
let first = errs * fbak /* 逐元素相乘 */
|
||||
let mx = prev_inputs.getOrThrow().T()
|
||||
weights_grad = mx.dot(first)
|
||||
bias_grad = first
|
||||
let errs_up = first.dot(weights.T())
|
||||
return errs_up
|
||||
}
|
||||
|
||||
/* -------- 更新权重值、偏爱因子 --------
|
||||
* NOTE: discount(折扣)是为了方便深度强化学习给出奖励值 */
|
||||
public func update(discount: Float32) {
|
||||
weights = weights - weights_grad.getOrThrow() * learning_rate * FP32(discount)
|
||||
bias = bias - bias_grad.getOrThrow() * learning_rate * FP32(discount)
|
||||
}
|
||||
|
||||
/**/
|
||||
private static func is_softmax(f: Af) {
|
||||
return match (f) {
|
||||
case Af.Softmax => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private func softmax_backward(errs: Matrix1) {
|
||||
/* 使用雅可比矩阵的方式 */
|
||||
let y = prev_outputs.getOrThrow() /* softmax 输出 */
|
||||
let dy = errs /* 上游梯度 ∂L/∂y */
|
||||
let jacobian = f.backward(y) /* n×n 雅可比矩阵 */
|
||||
|
||||
/* 将 dy 展平成列向量 n×1 */
|
||||
let row = dy.get_row()
|
||||
let col = dy.get_col()
|
||||
let n = row * col
|
||||
var dy_vec = Matrix1(n, 1)
|
||||
var idx = 0
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let val = dy.get(r, c).getOrThrow()
|
||||
dy_vec.set(idx, 0, val)
|
||||
idx = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
/* dx_vec = J · dy_vec (n×n × n×1 = n×1) */
|
||||
var dx_vec = Matrix1(n, 1)
|
||||
for (i in 0..n) {
|
||||
var sum = 0.0f32
|
||||
for (j in 0..n) {
|
||||
let j_val = jacobian.get(i, j).getOrThrow().getValue()
|
||||
let dy_val = dy_vec.get(j, 0).getOrThrow().getValue()
|
||||
sum = sum + j_val * dy_val
|
||||
}
|
||||
dx_vec.set(i, 0, FP32(sum))
|
||||
}
|
||||
|
||||
/* 恢复为原始形状,作为 first */
|
||||
var first = Matrix1(row, col)
|
||||
idx = 0
|
||||
for (r in 0..row) {
|
||||
for (c in 0..col) {
|
||||
let val = dx_vec.get(idx, 0).getOrThrow()
|
||||
first.set(r, c, val)
|
||||
idx = idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
let mx = prev_inputs.getOrThrow().T()
|
||||
weights_grad = mx.dot(first)
|
||||
bias_grad = first
|
||||
let errs_up = first.dot(weights.T())
|
||||
return errs_up
|
||||
}
|
||||
|
||||
|
||||
/* -------- 形状访问器 -------- */
|
||||
public func get_in_shape() {
|
||||
return inputno }
|
||||
|
||||
public func get_out_shape() {
|
||||
return outputno }
|
||||
|
||||
public func get_lr() {
|
||||
return learning_rate }
|
||||
|
||||
public func get_f_type() {
|
||||
return f.get_type() }
|
||||
|
||||
public func get_f_val() {
|
||||
return f.get_value() }
|
||||
/* ---------------------------- */
|
||||
|
||||
}
|
||||
|
||||
|
||||
///* -------- 旧版激活函数 --------*/
|
||||
//public enum ActivationFuncs {
|
||||
// Sigmoid | ReLU | Leaky_ReLU | Tanh
|
||||
// func fdo(m: Matrix): Matrix { // 暂时只设置ReLU
|
||||
// unsafe {
|
||||
// match(this) {
|
||||
// case Sigmoid => Matrix(Sigmoid_do(m.getData()))
|
||||
// case ReLU => Matrix(ReLU_do(m.getData()))
|
||||
// case Leaky_ReLU => Matrix(Leaky_ReLU_do(m.getData(), 0.1f32))
|
||||
// case Tanh => Matrix(Tanh_do(m.getData()))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// func fdot(m: Matrix): Matrix {
|
||||
// unsafe {
|
||||
// match(this) {
|
||||
// case Sigmoid => Matrix(Sigmoid_dot(m.getData()))
|
||||
// case ReLU => Matrix(ReLU_dot(m.getData()))
|
||||
// case Leaky_ReLU => Matrix(Leaky_ReLU_dot(m.getData(), 0.1f32))
|
||||
// case Tanh => Matrix(Tanh_dot(m.getData()))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// func toString(): String {
|
||||
// match(this) {
|
||||
// case Sigmoid => "Sigmoid"
|
||||
// case ReLU => "ReLU"
|
||||
// case Leaky_ReLU => "Leaky_ReLU"
|
||||
// case Tanh => "Tanh"
|
||||
// }
|
||||
// }
|
||||
// static func parse(s: String) {
|
||||
// match(s) {
|
||||
// case "Sigmoid" => ActivationFuncs.Sigmoid
|
||||
// case "ReLU" => ActivationFuncs.ReLU
|
||||
// case "Leaky_ReLU" => ActivationFuncs.Leaky_ReLU
|
||||
// case "Tanh" => ActivationFuncs.Tanh
|
||||
// case _ => throw IllegalArgumentException()
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
///* -------- 旧版的神经网络类 --------
|
||||
// * 这个实现依赖C++语言的动态分享库,特点是速度快
|
||||
// * 这个实现可能是有问题的 */
|
||||
//public class Nn {
|
||||
//
|
||||
// /* -------- 主初始化器 -------- */
|
||||
// public init(
|
||||
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32) {
|
||||
// this.inputno = inputno
|
||||
// this.outputno = outputno
|
||||
// weights = Matrix.initWithXavierUniform(inputno, outputno)
|
||||
// bias = Matrix(1, outputno)
|
||||
// this.f = f
|
||||
// this.learning_rate = learning_rate
|
||||
// }
|
||||
//
|
||||
// /* -------- 提供了权重矩阵的初始化器 -------- */
|
||||
// public init(
|
||||
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32, weights: Matrix, bias: Matrix) {
|
||||
// this.inputno = inputno
|
||||
// this.outputno = outputno
|
||||
// this.weights = weights
|
||||
// this.bias = bias
|
||||
// this.f = f
|
||||
// this.learning_rate = learning_rate
|
||||
// }
|
||||
//
|
||||
// /* -------- 神经网络推理 -------- */
|
||||
// func forward(x: Matrix): Matrix {
|
||||
// /* x => (1, inputs) */
|
||||
// let r = x.dot(weights) + bias /* (1, outputno) */
|
||||
// return r
|
||||
// }
|
||||
//
|
||||
// /* -------- 实施激活函数 -------- */
|
||||
// func fdo(x: Matrix): Matrix {
|
||||
// return f.fdo(x) }
|
||||
//
|
||||
// /* -------- 激活函数导数 -------- */
|
||||
// func fdot(x: Matrix): Matrix {
|
||||
// return f.fdot(x) }
|
||||
//
|
||||
// /* This is Wrong */
|
||||
// //func bak_bsgrad(prev_raw_outputs_dotf: Matrix, errs: Matrix): Matrix {
|
||||
// /* prev_raw_outputs_dotf => (1, outputno)
|
||||
// * errs => (1, outputno) */
|
||||
//
|
||||
// // return errs * prev_raw_outputs_dotf
|
||||
// //}
|
||||
//
|
||||
// /* -------- 权重梯度的传播 -------- */
|
||||
// func bak_wsgrad(prev_inputs: Matrix, delta: Matrix) {
|
||||
// /* prev_inputs => (1, inputs) */
|
||||
// return prev_inputs.T().dot(delta) /* (inputs, outputs) */
|
||||
// }
|
||||
//
|
||||
// /* -------- 计算上一层的误差 -------- */
|
||||
// func bak_uperrs(delta: Matrix, prev_dotf: Matrix) {
|
||||
// let first = weights.dot(delta.T()).T() // (1, inputs)
|
||||
//
|
||||
// var uperrs = first * prev_dotf
|
||||
// uperrs
|
||||
// }
|
||||
//
|
||||
// /* -------- 更新权重、偏爱因子 -------- */
|
||||
// func update(weights_grad: Matrix, bias_grad: Matrix, discount: Float32) {
|
||||
// weights -= weights_grad * learning_rate * discount
|
||||
// bias -= bias_grad * learning_rate * discount
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印权重、偏爱因子 -------- */
|
||||
// func print() {
|
||||
// weights.print()
|
||||
// bias.print()
|
||||
// }
|
||||
//
|
||||
// /* -------- 打印神经网络全连接层的形状 -------- */
|
||||
// func print_shape() {
|
||||
// println("${inputno}, ${outputno}") }
|
||||
//
|
||||
// /* -------- 储存神经网络 -------- */
|
||||
// func save(file: File) {
|
||||
// file.write("FullConnectLayer ".toArray())
|
||||
// file.write(inputno.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(outputno.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(learning_rate.toString().toArray())
|
||||
// file.write(" ".toArray())
|
||||
// file.write(f.toString().toArray())
|
||||
// file.write("\n".toArray())
|
||||
// weights.save(file)
|
||||
// //file.write("\n".toArray())
|
||||
// bias.save(file)
|
||||
// //file.write("\n".toArray())
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载单个项目 -------- */
|
||||
// static func loadItem<T>(sr: StringReader<InputStream>) where T <: Parsable<T>
|
||||
// {
|
||||
// let s = sr.readUntil({r => r == r" " || r == r"\n"})
|
||||
// let ss = s.getOrThrow().trimAscii()
|
||||
// println(ss)
|
||||
// return T.parse(ss)
|
||||
// }
|
||||
//
|
||||
// /* -------- 静态方法,加载神经网络 -------- */
|
||||
// static func load(sr: StringReader<InputStream>)
|
||||
// {
|
||||
// let inputno = loadItem<UInt64>(sr)
|
||||
// let outputno = loadItem<UInt64>(sr)
|
||||
// let lr = loadItem<Float32>(sr)
|
||||
// let fna = sr.readUntil(r'\n')
|
||||
// let f = ActivationFuncs.parse(fna.getOrThrow().trimAscii())
|
||||
// let weights = Matrix.load(sr, inputno, outputno)
|
||||
// let bias = Matrix.load(sr, 1, outputno)
|
||||
// Nn(inputno, outputno, f, lr, weights, bias)
|
||||
// }
|
||||
//
|
||||
// let inputno: UInt64
|
||||
// let outputno: UInt64
|
||||
// var weights: Matrix /* (inputs, outputs) */
|
||||
// var bias: Matrix /* (1, outputs) */
|
||||
// var learning_rate: Float32
|
||||
// let f: ActivationFuncs
|
||||
//}
|
||||
@@ -0,0 +1,164 @@
|
||||
package bot4cj.math
|
||||
import std.io.*
|
||||
import std.fs.*
|
||||
import std.math.*
|
||||
import std.time.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
/* -------- 准备与门的测试用例 --------
|
||||
* INFO(20260524-qing): 输入{2},输出{1} */
|
||||
public func prepare_andDoor(m: Matrix1, t: Matrix1) {
|
||||
let random = Random(UInt64(DateTime.now().nanosecond))
|
||||
let a = random.nextInt8(2)
|
||||
let b = random.nextInt8(2)
|
||||
let t0 = if ((a == 0 && b == 0) || (a ==1 && b == 1)) { 1.0f32 } else { 0.0f32 }
|
||||
//let t1 = if (a != b) { 1.0f32 } else { 0.0f32 }
|
||||
m.set(0, 0, FP32(Float32(a)))
|
||||
m.set(0, 1, FP32(Float32(b)))
|
||||
t.set(0, 0, FP32(t0))
|
||||
//t.set(0, 1, FP32(t1))
|
||||
}
|
||||
|
||||
/* -------- 新版神经网络推理,使用原生矩阵 -------- */
|
||||
public func forward1(nns: ArrayList<Nn1>, m: Matrix1) {
|
||||
var out_dof = Matrix1(1, 1) /* ---- 给一个值占位 ----*/
|
||||
for (idx in 0..nns.size) {
|
||||
let x = if (idx == 0) { m } else { out_dof }
|
||||
out_dof= nns[idx].forward(x)
|
||||
}
|
||||
return out_dof
|
||||
}
|
||||
|
||||
/* 【神经网络训练】
|
||||
* description: 输入一组原始数据和教师数据,进行神经网络训练
|
||||
* @param nns: 输入神经网络结构
|
||||
* @param m: 输入一组原始数据
|
||||
* @param t: 输入一组教师数据
|
||||
* @param discount: 输入本次训练的折扣系数
|
||||
*/
|
||||
public func train1(nns: ArrayList<Nn1>, m: Matrix1, t: Matrix1, discount: Float32) {
|
||||
|
||||
/* ======== 这个对象用于暂时占位 ======== */
|
||||
var res = Matrix1(1, 1)
|
||||
|
||||
/* ======== 正向传播 ======== */
|
||||
for (idx in 0..nns.size) {
|
||||
let x = if (idx == 0) { m } else { res }
|
||||
res = nns[idx].forward(x)
|
||||
}
|
||||
|
||||
/* ======== 计算误差 ======== */
|
||||
var errs = t - res
|
||||
|
||||
/* ======== 反向传播 ======== */
|
||||
for (idx in (nns.size-1)..=0:-1) {
|
||||
errs = nns[idx].backward(errs)
|
||||
}
|
||||
|
||||
/* ======== 梯度更新 ======== */
|
||||
for (idx in 0..nns.size) {
|
||||
nns[idx].update(discount)
|
||||
}
|
||||
|
||||
/* ======== 计算MSE损失 ========
|
||||
FIXME*(20260517-qing): 对于多个结果计算误差(目前只计算了第一个结果) */
|
||||
|
||||
let t0 = t.get(0, 0).getOrThrow().getValue()
|
||||
//let t1 = t.get(0, 1).getOrThrow().getValue()
|
||||
let r0 = res.get(0, 0).getOrThrow().getValue()
|
||||
//let r1 = res.get(0, 1).getOrThrow().getValue()
|
||||
let loss0 = 0.5 * (t0 - r0) * (t0 - r0)
|
||||
//let loss1 = 0.5 * (t1 - r1) * (t1 - r1)
|
||||
//return (loss0 + loss1) / 2.0
|
||||
return loss0
|
||||
}
|
||||
|
||||
/* -------- (旧版)加载多层神经网络 --------
|
||||
TODO: 也许用类封装
|
||||
INFO(20260517-qing): 新版使用仓颉原生矩阵,然后与MatrixObject类互转 */
|
||||
//public func load_nns(filename: String): ArrayList<Nn> {
|
||||
// let nns = ArrayList<Nn>()
|
||||
// let file = File(filename, OpenMode.Read)
|
||||
// let sr = StringReader<InputStream>(file)
|
||||
// while (true) {
|
||||
// if (let Some(S) <- sr.readUntil(r' ')) {
|
||||
// var S2 = S.trimAscii()
|
||||
// if (S2 == "FullConnectLayer") {
|
||||
// nns.add(Nn.load(sr)) }
|
||||
// else if (S2 == "") { break }
|
||||
// else { throw IllegalArgumentException() }
|
||||
// }
|
||||
// else { break }
|
||||
// }
|
||||
|
||||
// file.close()
|
||||
// return nns
|
||||
//}
|
||||
|
||||
/* -------- (旧版)储存多层神经网络 -------- */
|
||||
//public func save_nns(nns: ArrayList<Nn>, filename: String) {
|
||||
//
|
||||
// let file: File
|
||||
// if (exists(filename)) {
|
||||
// file = File(filename, OpenMode.Write) }
|
||||
// else { file= File.create(filename) }
|
||||
// for (nn in nns) { /* 存储全部的层 */
|
||||
// nn.save(file) }
|
||||
// file.close()
|
||||
//}
|
||||
|
||||
/* -------- 旧版神经网络推理 -------- */
|
||||
//public func forward(nns: ArrayList<Nn>, m: Matrix) {
|
||||
// var out_dof = Matrix(1, 1)
|
||||
// for (idx in 0..nns.size) {
|
||||
// let x = if (idx == 0) { m } else { out_dof }
|
||||
// let res = nns[idx].forward(x)
|
||||
// out_dof = nns[idx].fdo(res)
|
||||
// }
|
||||
// return out_dof
|
||||
//}
|
||||
|
||||
/* -------- 旧版的神经网络训练 -------- */
|
||||
//public func train(nns: ArrayList<Nn>, m: Matrix, t: Matrix, discount: Float32) {
|
||||
//
|
||||
// /* ----------------- */
|
||||
// let outs_dof = ArrayList<Matrix> ()
|
||||
// let outs_dotf = ArrayList<Matrix> ()
|
||||
//
|
||||
// /* -------- 正向传播 -------- */
|
||||
// for (idx in 0..nns.size) {
|
||||
// let x = if (idx == 0) { m } else { outs_dof[idx-1] }
|
||||
// let res = nns[idx].forward(x)
|
||||
// let out_dof = nns[idx].fdo(res)
|
||||
// let out_dotf = nns[idx].fdot(out_dof)
|
||||
// outs_dof.add(out_dof)
|
||||
// outs_dotf.add(out_dotf)
|
||||
// }
|
||||
//
|
||||
// /* -------- 计算误差 -------- */
|
||||
// var errs = outs_dof[nns.size-1] - t
|
||||
//
|
||||
// /* -------- 反向传播 -------- */
|
||||
// for (idx in (nns.size-1)..=0:-1) {
|
||||
// let up = if (idx == 0) { m} else { outs_dof[idx-1] }
|
||||
// let bias_grad = errs
|
||||
// //let delta = nns[idx].bak_bsgrad(errs, outs_dotf[idx])
|
||||
// let weights_grad = nns[idx].bak_wsgrad(up, bias_grad)
|
||||
// if (idx > 0) {
|
||||
// errs = nns[idx].bak_uperrs(bias_grad, outs_dotf[idx-1]) }
|
||||
// nns[idx].update(weights_grad, bias_grad, discount)
|
||||
// }
|
||||
//
|
||||
// /* -------- 输出损失 -------- */
|
||||
// unsafe {
|
||||
// let t0 = t.get(0, 0)
|
||||
// let t1 = t.get(0, 1)
|
||||
// let r0 = outs_dof[nns.size-1].get(0, 0)
|
||||
// let r1 = outs_dof[nns.size-1].get(0, 1)
|
||||
// let loss0 = -t0 * log(r0) - (1.0f32-t0) * log(1.0f32-r0);
|
||||
// let loss1 = -t1 * log(r1) - (1.0f32-t1) * log(1.0f32-r1);
|
||||
// return 0.5 * (loss0 * loss0 + loss1 * loss1)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@@ -0,0 +1,418 @@
|
||||
// pooling_layer.cj
|
||||
package bot4cj.math
|
||||
|
||||
import std.math.*
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* 池化层类
|
||||
* 设计风格与 Nn1 和 ConvLayer 保持一致
|
||||
*/
|
||||
public class PoolingLayer {
|
||||
|
||||
// ==================== 层配置参数 ====================
|
||||
let nChannels: Int64
|
||||
let poolHeight: Int64
|
||||
let poolWidth: Int64
|
||||
let stride: Int64
|
||||
let padding: Int64
|
||||
let poolType: String
|
||||
let f: Af
|
||||
let learningRateWeight: FP32
|
||||
let learningRateBias: FP32
|
||||
|
||||
// ==================== 可学习参数 ====================
|
||||
var weights: Matrix1
|
||||
var biases: Matrix1
|
||||
|
||||
// ==================== 运行时缓存 ====================
|
||||
var prevInput: Option<Matrix1> = Option.None
|
||||
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevCombination: Option<Matrix1> = Option.None
|
||||
var prevOutput: Option<Matrix1> = Option.None
|
||||
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
|
||||
var prevMaxIndices: Option<Matrix1> = Option.None
|
||||
|
||||
var weightsGrad: Option<Matrix1> = Option.None
|
||||
var biasGrad: Option<Matrix1> = Option.None
|
||||
|
||||
// ==================== 构造函数 ====================
|
||||
public init(
|
||||
nChannels: Int64,
|
||||
poolSize: (Int64, Int64),
|
||||
stride: Int64,
|
||||
padding: Int64,
|
||||
poolType: String,
|
||||
activation: Af,
|
||||
lrWeight: FP32,
|
||||
lrBias: FP32
|
||||
) {
|
||||
this.nChannels = nChannels
|
||||
poolHeight = poolSize[0]
|
||||
poolWidth = poolSize[1]
|
||||
this.stride = stride
|
||||
this.padding = padding
|
||||
this.poolType = poolType
|
||||
this.f = activation
|
||||
learningRateWeight = lrWeight
|
||||
learningRateBias = lrBias
|
||||
|
||||
// 初始化权值
|
||||
weights = Matrix1(1, nChannels)
|
||||
if (poolType == "average") {
|
||||
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, avgWeight)
|
||||
}
|
||||
} else {
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化偏置为 0
|
||||
biases = Matrix1(1, nChannels)
|
||||
for (c in 0..nChannels) {
|
||||
biases.set(0, c, FP32(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
nChannels: Int64,
|
||||
poolSize: (Int64, Int64),
|
||||
poolType: String,
|
||||
activation: Af
|
||||
) {
|
||||
this.nChannels = nChannels
|
||||
poolHeight = poolSize[0]
|
||||
poolWidth = poolSize[1]
|
||||
stride = poolHeight
|
||||
padding = 0
|
||||
this.poolType = poolType
|
||||
this.f = activation
|
||||
learningRateWeight = FP32(0.1)
|
||||
learningRateBias = FP32(0.1)
|
||||
|
||||
weights = Matrix1(1, nChannels)
|
||||
if (poolType == "average") {
|
||||
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, avgWeight)
|
||||
}
|
||||
} else {
|
||||
for (c in 0..nChannels) {
|
||||
weights.set(0, c, FP32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
biases = Matrix1(1, nChannels)
|
||||
for (c in 0..nChannels) {
|
||||
biases.set(0, c, FP32(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 前向传播 ====================
|
||||
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
|
||||
prevInput = Option.Some(x)
|
||||
prevInputShape = Option.Some(inputShape)
|
||||
|
||||
let batch = inputShape[0]
|
||||
let inH = inputShape[1]
|
||||
let inW = inputShape[2]
|
||||
|
||||
let outH = (inH + 2 * padding - poolHeight) / stride + 1
|
||||
let outW = (inW + 2 * padding - poolWidth) / stride + 1
|
||||
let outSize = batch * outH * outW
|
||||
|
||||
var combination = Matrix1(outSize, nChannels)
|
||||
var output = Matrix1(outSize, nChannels)
|
||||
var maxIndices = Matrix1(outSize, nChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let outRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
var pooledVal = FP32(0.0)
|
||||
var maxIdx = 0
|
||||
|
||||
if (poolType == "max") {
|
||||
var maxVal = FP32(-1.0e30)
|
||||
var kernelIdx = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let val = x.get(inRow, ch).getOrThrow()
|
||||
if (val.getValue() > maxVal.getValue()) {
|
||||
maxVal = val
|
||||
maxIdx = kernelIdx
|
||||
}
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
pooledVal = maxVal
|
||||
} else {
|
||||
var sum = FP32(0.0)
|
||||
var count = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
sum = sum + x.get(inRow, ch).getOrThrow()
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
pooledVal = sum / FP32(Float32(count))
|
||||
}
|
||||
}
|
||||
|
||||
combination.set(outRow, ch, pooledVal)
|
||||
maxIndices.set(outRow, ch, FP32(Float32(maxIdx)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 逐通道应用权值和偏置
|
||||
for (r in 0..outSize) {
|
||||
for (ch in 0..nChannels) {
|
||||
let val = combination.get(r, ch).getOrThrow()
|
||||
let w = weights.get(0, ch).getOrThrow()
|
||||
let b = biases.get(0, ch).getOrThrow()
|
||||
combination.set(r, ch, val * w + b)
|
||||
}
|
||||
}
|
||||
|
||||
prevCombination = Option.Some(combination)
|
||||
|
||||
output = f.forward(combination)
|
||||
prevOutput = Option.Some(output)
|
||||
prevOutputShape = Option.Some((batch, outH, outW, nChannels))
|
||||
prevMaxIndices = Option.Some(maxIndices)
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
// ==================== 反向传播 ====================
|
||||
public func backward(errs: Matrix1): Matrix1 {
|
||||
var x = Matrix1(1, 1)
|
||||
match (prevInput) {
|
||||
case Some(val) => x = val
|
||||
case _ => throw Exception("No input data")
|
||||
}
|
||||
|
||||
var shape = (0, 0, 0, 0)
|
||||
match (prevInputShape) {
|
||||
case Some(val) => shape = val
|
||||
case _ => throw Exception("No input shape")
|
||||
}
|
||||
|
||||
var combination = Matrix1(1, 1)
|
||||
match (prevCombination) {
|
||||
case Some(val) => combination = val
|
||||
case _ => throw Exception("No combination")
|
||||
}
|
||||
|
||||
var outShape = (0, 0, 0, 0)
|
||||
match (prevOutputShape) {
|
||||
case Some(val) => outShape = val
|
||||
case _ => throw Exception("No output shape")
|
||||
}
|
||||
|
||||
var maxIndices = Matrix1(1, 1)
|
||||
let hasMaxIndices = match (prevMaxIndices) {
|
||||
case Some(_) => true
|
||||
case _ => false
|
||||
}
|
||||
if (hasMaxIndices) {
|
||||
maxIndices = match (prevMaxIndices) {
|
||||
case Some(val) => val
|
||||
case _ => Matrix1(1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
let batch = shape[0]
|
||||
let inH = shape[1]
|
||||
let inW = shape[2]
|
||||
let outH = outShape[1]
|
||||
let outW = outShape[2]
|
||||
|
||||
let delta = errs * f.backward(combination)
|
||||
|
||||
calcWeightBiasGradient(delta, combination)
|
||||
|
||||
let errsUp = calcUpsampledError(delta, maxIndices, batch, inH, inW, outH, outW)
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算权值和偏置梯度
|
||||
*/
|
||||
private func calcWeightBiasGradient(delta: Matrix1, combination: Matrix1): Unit {
|
||||
let outSize = delta.get_row()
|
||||
|
||||
var wGrad = Matrix1(1, nChannels)
|
||||
var bGrad = Matrix1(1, nChannels)
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
var wSum = FP32(0.0)
|
||||
var bSum = FP32(0.0)
|
||||
for (r in 0..outSize) {
|
||||
let d = delta.get(r, ch).getOrThrow()
|
||||
bSum = bSum + d
|
||||
let comb = combination.get(r, ch).getOrThrow()
|
||||
wSum = wSum + d * comb
|
||||
}
|
||||
wGrad.set(0, ch, wSum)
|
||||
bGrad.set(0, ch, bSum)
|
||||
}
|
||||
|
||||
weightsGrad = Option.Some(wGrad)
|
||||
biasGrad = Option.Some(bGrad)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上采样误差(池化层的反向传播核心)
|
||||
*/
|
||||
private func calcUpsampledError(
|
||||
delta: Matrix1,
|
||||
maxIndices: Matrix1,
|
||||
batch: Int64,
|
||||
inH: Int64,
|
||||
inW: Int64,
|
||||
outH: Int64,
|
||||
outW: Int64
|
||||
): Matrix1 {
|
||||
let inSize = batch * inH * inW
|
||||
var errsUp = Matrix1(inSize, nChannels)
|
||||
|
||||
for (b in 0..batch) {
|
||||
for (oh in 0..outH) {
|
||||
for (ow in 0..outW) {
|
||||
let deltaRow = b * outH * outW + oh * outW + ow
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let deltaVal = delta.get(deltaRow, ch).getOrThrow()
|
||||
let w = weights.get(0, ch).getOrThrow()
|
||||
|
||||
if (poolType == "max") {
|
||||
let maxIdx = Int64(maxIndices.get(deltaRow, ch).getOrThrow().getValue())
|
||||
|
||||
var kernelIdx = 0
|
||||
var found = false
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
if (kernelIdx == maxIdx && !found) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
errsUp.set(inRow, ch, deltaVal * w)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
kernelIdx += 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var count = 0
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
let avgDelta = deltaVal * w / FP32(Float32(count))
|
||||
for (kh in 0..poolHeight) {
|
||||
for (kw in 0..poolWidth) {
|
||||
let ih = oh * stride + kh - padding
|
||||
let iw = ow * stride + kw - padding
|
||||
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
|
||||
let inRow = b * inH * inW + ih * inW + iw
|
||||
let oldVal = errsUp.get(inRow, ch).getOrThrow()
|
||||
errsUp.set(inRow, ch, oldVal + avgDelta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errsUp
|
||||
}
|
||||
|
||||
// ==================== 参数更新 ====================
|
||||
public func update(discount: Float32): Unit {
|
||||
var wGrad = Matrix1(1, 1)
|
||||
match (weightsGrad) {
|
||||
case Some(val) => wGrad = val
|
||||
case _ => throw Exception("No weight gradients")
|
||||
}
|
||||
|
||||
var bGrad = Matrix1(1, 1)
|
||||
match (biasGrad) {
|
||||
case Some(val) => bGrad = val
|
||||
case _ => throw Exception("No bias gradients")
|
||||
}
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let oldW = weights.get(0, ch).getOrThrow()
|
||||
let gW = wGrad.get(0, ch).getOrThrow()
|
||||
weights.set(0, ch, oldW + gW * learningRateWeight * FP32(discount))
|
||||
}
|
||||
|
||||
for (ch in 0..nChannels) {
|
||||
let oldB = biases.get(0, ch).getOrThrow()
|
||||
let gB = bGrad.get(0, ch).getOrThrow()
|
||||
biases.set(0, ch, oldB + gB * learningRateBias * FP32(discount))
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 形状访问器 ====================
|
||||
public func getInShape(): (Int64, Int64) {
|
||||
(nChannels, poolHeight * poolWidth)
|
||||
}
|
||||
|
||||
public func getOutShape(): Int64 {
|
||||
nChannels
|
||||
}
|
||||
|
||||
public func getPoolType(): String {
|
||||
poolType
|
||||
}
|
||||
|
||||
public func getLR(): FP32 {
|
||||
learningRateWeight
|
||||
}
|
||||
|
||||
public func getFType(): String {
|
||||
f.get_type()
|
||||
}
|
||||
|
||||
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
|
||||
let outH = (inputHeight + 2 * padding - poolHeight) / stride + 1
|
||||
let outW = (inputWidth + 2 * padding - poolWidth) / stride + 1
|
||||
(outH, outW)
|
||||
}
|
||||
}
|
||||
+1071
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,310 @@
|
||||
// test_cnn.cj - 使用 Adam 优化器的版本(所有层)
|
||||
package bot4cj
|
||||
import bot4cj.math.*
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
func generateSolidColorData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
|
||||
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
|
||||
var labels = Array(numSamples, { _ => 0 })
|
||||
let rng = Random()
|
||||
|
||||
for (i in 0..numSamples) {
|
||||
let colorClass = Int64(rng.nextFloat64() * 3.0)
|
||||
labels[i] = colorClass
|
||||
|
||||
let h = 8
|
||||
let w = 8
|
||||
var matrix = Matrix1(h * w, 1)
|
||||
|
||||
let colorValue = match (colorClass) {
|
||||
case 0 => FP32(1.0)
|
||||
case 1 => FP32(0.5)
|
||||
case _ => FP32(0.0)
|
||||
}
|
||||
|
||||
for (r in 0..h * w) {
|
||||
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.1)
|
||||
var val = colorValue + noise
|
||||
if (val.getValue() > 1.0) {
|
||||
val = FP32(1.0)
|
||||
} else if (val.getValue() < 0.0) {
|
||||
val = FP32(0.0)
|
||||
}
|
||||
matrix.set(r, 0, val)
|
||||
}
|
||||
|
||||
inputs[i] = matrix
|
||||
}
|
||||
|
||||
(inputs, labels)
|
||||
}
|
||||
|
||||
func printSeparator(): Unit {
|
||||
println("==================================================")
|
||||
}
|
||||
|
||||
func testCNN(): Unit {
|
||||
printSeparator()
|
||||
println("CNN 纯色矩阵识别测试 (所有层使用 Adam 优化器)")
|
||||
printSeparator()
|
||||
println()
|
||||
|
||||
let nTrainSamples = 200
|
||||
let nTestSamples = 60
|
||||
let nEpochs = 50
|
||||
let batchSize = 10
|
||||
|
||||
println("训练样本数: ${nTrainSamples}")
|
||||
println("测试样本数: ${nTestSamples}")
|
||||
println("训练轮数: ${nEpochs}")
|
||||
println("Batch 大小: ${batchSize}")
|
||||
println()
|
||||
|
||||
println("创建网络结构...")
|
||||
println("Input(8x8x1) -> Conv(3x3,1->4) -> Pool(2x2) -> Conv(3x3,4->8) -> Flatten -> FC(8,3)")
|
||||
println()
|
||||
|
||||
let lr = FP32(0.001)
|
||||
|
||||
var conv1 = ConvLayer(1, 4, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool1 = PoolingLayer(4, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var conv2 = ConvLayer(4, 8, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var flatten = FlattenLayer(8, 1, 1)
|
||||
var fc = Nn1(8, 3, Float32(0.4), Af.Softmax)
|
||||
|
||||
// 创建 Adam 优化器(为每个可训练层创建独立的优化器)
|
||||
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
|
||||
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
|
||||
var adamFC = Adam(FP32(0.0005)) // 全连接层(使用更小的学习率)
|
||||
|
||||
println("网络创建完成 (Conv1, Conv2, FC 使用 Adam 优化器)")
|
||||
println()
|
||||
|
||||
println("生成数据...")
|
||||
let (trainInputs, trainLabels) = generateSolidColorData(nTrainSamples)
|
||||
let (testInputs, testLabels) = generateSolidColorData(nTestSamples)
|
||||
println("训练集: ${nTrainSamples} 样本")
|
||||
println("测试集: ${nTestSamples} 样本")
|
||||
println()
|
||||
|
||||
println("开始训练...")
|
||||
println()
|
||||
|
||||
for (epoch in 0..nEpochs) {
|
||||
var epochLoss = Float32(0.0)
|
||||
var epochCorrect = 0
|
||||
|
||||
for (i in 0..nTrainSamples) {
|
||||
let x = trainInputs[i]
|
||||
let label = trainLabels[i]
|
||||
|
||||
// ===== 前向传播 =====
|
||||
let c1Out = conv1.forward(x, (1, 8, 8, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
|
||||
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
|
||||
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
|
||||
|
||||
// 调试:确认 flatten.forward 执行成功
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: flatten.forward done, flatOut rows: ${flatOut.get_row()}, cols: ${flatOut.get_col()}")
|
||||
}
|
||||
|
||||
var fcInput = Matrix1(1, 8)
|
||||
for (j in 0..8) {
|
||||
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
let fcOut = fc.forward(fcInput)
|
||||
|
||||
// Softmax
|
||||
var softmax = Matrix1(1, 3)
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
softmax.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
for (c in 0..3) {
|
||||
let v = softmax.get(0, c).getOrThrow().getValue()
|
||||
softmax.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
let probVal = softmax.get(0, label).getOrThrow().getValue()
|
||||
let loss = -log(max(probVal, Float32(1e-10)))
|
||||
epochLoss = epochLoss + loss
|
||||
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
for (c in 0..3) {
|
||||
let p = softmax.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
if (predClass == label) {
|
||||
epochCorrect += 1
|
||||
}
|
||||
|
||||
// ===== 反向传播 =====
|
||||
var delta = Matrix1(1, 3)
|
||||
for (c in 0..3) {
|
||||
let prob = softmax.get(0, c).getOrThrow()
|
||||
if (Int64(c) == label) {
|
||||
delta.set(0, c, prob - FP32(1.0))
|
||||
} else {
|
||||
delta.set(0, c, prob)
|
||||
}
|
||||
}
|
||||
|
||||
let fcDelta = fc.backward(delta)
|
||||
|
||||
// 调试:确认 fcDelta 形状
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: fc.backward done, fcDelta rows: ${fcDelta.get_row()}, cols: ${fcDelta.get_col()}")
|
||||
}
|
||||
|
||||
var flatDelta = Matrix1(8, 1)
|
||||
for (j in 0..8) {
|
||||
flatDelta.set(j, 0, fcDelta.get(0, j).getOrThrow())
|
||||
}
|
||||
|
||||
// 调试:确认 flatDelta 形状
|
||||
if (i == 0 && epoch == 0) {
|
||||
println("DEBUG: flatDelta rows: ${flatDelta.get_row()}, cols: ${flatDelta.get_col()}")
|
||||
}
|
||||
|
||||
let c2Delta = flatten.backward(flatDelta)
|
||||
let p1Delta = conv2.backward(c2Delta)
|
||||
let c1Delta = pool1.backward(p1Delta)
|
||||
let _ = conv1.backward(c1Delta)
|
||||
|
||||
// ===== 参数更新 =====
|
||||
if ((i + 1) % batchSize == 0) {
|
||||
// 所有可训练层使用 Adam 更新
|
||||
adamConv1.updateConvLayer(conv1) // 卷积层1 - Adam
|
||||
adamConv2.updateConvLayer(conv2) // 卷积层2 - Adam
|
||||
adamFC.updateFCLayer(fc) // 全连接层 - Adam
|
||||
|
||||
// 池化层使用 SGD 更新(如果需要的话)
|
||||
pool1.update(Float32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// Epoch 结束时的更新
|
||||
adamConv1.updateConvLayer(conv1)
|
||||
adamConv2.updateConvLayer(conv2)
|
||||
adamFC.updateFCLayer(fc)
|
||||
pool1.update(Float32(1.0))
|
||||
|
||||
let avgLoss = epochLoss / Float32(nTrainSamples)
|
||||
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
|
||||
|
||||
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%")
|
||||
|
||||
// 打印 Adam 的步数信息
|
||||
if (epoch == 0) {
|
||||
println(" Adam Step: ${adamConv1.getStep()}")
|
||||
}
|
||||
}
|
||||
|
||||
println("")
|
||||
println("开始测试...")
|
||||
println("")
|
||||
|
||||
var testCorrect = 0
|
||||
var confusion = Array(3, { _ => Array(3, { _ => 0 }) })
|
||||
|
||||
for (i in 0..nTestSamples) {
|
||||
let x = testInputs[i]
|
||||
let label = testLabels[i]
|
||||
|
||||
let c1Out = conv1.forward(x, (1, 8, 8, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
|
||||
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
|
||||
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
|
||||
|
||||
var fcInput = Matrix1(1, 8)
|
||||
for (j in 0..8) {
|
||||
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
let fcOut = fc.forward(fcInput)
|
||||
|
||||
var softmax = Matrix1(1, 3)
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..3) {
|
||||
let raw = fcOut.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
softmax.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
for (c in 0..3) {
|
||||
let v = softmax.get(0, c).getOrThrow().getValue()
|
||||
softmax.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
for (c in 0..3) {
|
||||
let p = softmax.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
|
||||
confusion[label][predClass] += 1
|
||||
if (predClass == label) {
|
||||
testCorrect += 1
|
||||
}
|
||||
}
|
||||
|
||||
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
|
||||
println("测试准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
|
||||
print("")
|
||||
|
||||
let classNames = ["红色(高)", "绿色(中)", "蓝色(低)"]
|
||||
println("各类别准确率:")
|
||||
for (c in 0..3) {
|
||||
var total = 0
|
||||
for (p in 0..3) {
|
||||
total += confusion[c][p]
|
||||
}
|
||||
if (total > 0) {
|
||||
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
|
||||
print(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
|
||||
}
|
||||
}
|
||||
println("")
|
||||
|
||||
println("混淆矩阵:")
|
||||
println(" 预测红 预测绿 预测蓝")
|
||||
for (t in 0..3) {
|
||||
var line = "真实${classNames[t]}:"
|
||||
for (p in 0..3) {
|
||||
line = line + " ${confusion[t][p]} "
|
||||
}
|
||||
print(line)
|
||||
}
|
||||
println("")
|
||||
|
||||
printSeparator()
|
||||
println("测试完成!")
|
||||
printSeparator()
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
// test_geometry_cnn.cj - 几何图形分类测试(Adam 优化器版本)
|
||||
package bot4cj
|
||||
import bot4cj.math.*
|
||||
import std.math.*
|
||||
import std.random.*
|
||||
import std.collection.*
|
||||
|
||||
/**
|
||||
* 计算圆形像素值
|
||||
*/
|
||||
func calcCirclePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let dx = Float64(x) - center + offsetX
|
||||
let dy = Float64(y) - center + offsetY
|
||||
let dist = sqrt(dx * dx + dy * dy)
|
||||
|
||||
if (dist <= radius) {
|
||||
let intensity = 1.0 - (dist / radius) * 0.3
|
||||
FP32(Float32(intensity))
|
||||
} else if (dist <= radius + 1.5) {
|
||||
let edgeDist = dist - radius
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.7
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算正方形像素值
|
||||
*/
|
||||
func calcSquarePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let halfSize = radius * 0.8
|
||||
let left = center - halfSize + offsetX
|
||||
let right = center + halfSize + offsetX
|
||||
let top = center - halfSize + offsetY
|
||||
let bottom = center + halfSize + offsetY
|
||||
|
||||
let fx = Float64(x)
|
||||
let fy = Float64(y)
|
||||
|
||||
if (fx >= left && fx <= right && fy >= top && fy <= bottom) {
|
||||
let distToEdge = min(
|
||||
min(fx - left, right - fx),
|
||||
min(fy - top, bottom - fy)
|
||||
)
|
||||
let intensity = min(1.0, 0.7 + distToEdge / halfSize * 0.3)
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
let distX = max(0.0, max(left - fx, fx - right))
|
||||
let distY = max(0.0, max(top - fy, fy - bottom))
|
||||
let edgeDist = sqrt(distX * distX + distY * distY)
|
||||
if (edgeDist <= 1.5) {
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算三角形像素值
|
||||
*/
|
||||
func calcTrianglePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
|
||||
let height = radius * 2.0
|
||||
let baseWidth = height * 1.2
|
||||
let topY = center - height * 0.5 + offsetY
|
||||
let bottomY = center + height * 0.5 + offsetY
|
||||
|
||||
let fx = Float64(x)
|
||||
let fy = Float64(y)
|
||||
|
||||
if (fy >= topY && fy <= bottomY) {
|
||||
let t = (fy - topY) / (bottomY - topY)
|
||||
let halfWidthAtY = baseWidth * (1.0 - t) * 0.5
|
||||
let centerX = center + offsetX
|
||||
|
||||
if (fx >= centerX - halfWidthAtY && fx <= centerX + halfWidthAtY) {
|
||||
let distToEdge = min(
|
||||
fx - (centerX - halfWidthAtY),
|
||||
(centerX + halfWidthAtY) - fx
|
||||
)
|
||||
let intensity = min(1.0, 0.7 + distToEdge / halfWidthAtY * 0.3)
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
let distToLeft = (centerX - halfWidthAtY) - fx
|
||||
let distToRight = fx - (centerX + halfWidthAtY)
|
||||
let edgeDist = max(distToLeft, distToRight)
|
||||
if (edgeDist <= 1.5) {
|
||||
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
|
||||
FP32(Float32(intensity))
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FP32(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成几何图形数据
|
||||
* 图形类型:0=圆形, 1=正方形, 2=三角形
|
||||
*/
|
||||
func generateGeometryData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
|
||||
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
|
||||
var labels = Array(numSamples, { _ => 0 })
|
||||
let rng = Random()
|
||||
|
||||
let imgSize = 16
|
||||
|
||||
for (i in 0..numSamples) {
|
||||
let shapeType = Int64(rng.nextFloat64() * 3.0)
|
||||
labels[i] = shapeType
|
||||
|
||||
var matrix = Matrix1(imgSize * imgSize, 1)
|
||||
let center = Float64(imgSize) / 2.0
|
||||
let radius = Float64(imgSize) / 3.0 + rng.nextFloat64() * 2.0 - 1.0
|
||||
|
||||
let offsetX = (rng.nextFloat64() - 0.5) * 2.0
|
||||
let offsetY = (rng.nextFloat64() - 0.5) * 2.0
|
||||
|
||||
for (y in 0..imgSize) {
|
||||
for (x in 0..imgSize) {
|
||||
let idx = y * imgSize + x
|
||||
|
||||
var pixelValue = match (shapeType) {
|
||||
case 0 => calcCirclePixel(x, y, center, radius, offsetX, offsetY)
|
||||
case 1 => calcSquarePixel(x, y, center, radius, offsetX, offsetY)
|
||||
case _ => calcTrianglePixel(x, y, center, radius, offsetX, offsetY)
|
||||
}
|
||||
|
||||
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.05)
|
||||
pixelValue = pixelValue + noise
|
||||
|
||||
if (pixelValue.getValue() > 1.0) {
|
||||
pixelValue = FP32(1.0)
|
||||
} else if (pixelValue.getValue() < 0.0) {
|
||||
pixelValue = FP32(0.0)
|
||||
}
|
||||
|
||||
matrix.set(idx, 0, pixelValue)
|
||||
}
|
||||
}
|
||||
|
||||
inputs[i] = matrix
|
||||
}
|
||||
|
||||
(inputs, labels)
|
||||
}
|
||||
|
||||
/**
|
||||
* 可视化一个样本
|
||||
*/
|
||||
func showSample(matrix: Matrix1, imgSize: Int64, label: Int64): Unit {
|
||||
let shapeNames = ["圆形", "正方形", "三角形"]
|
||||
println("标签: ${shapeNames[label]}")
|
||||
|
||||
for (y in 0..imgSize) {
|
||||
var line = ""
|
||||
for (x in 0..imgSize) {
|
||||
let idx = y * imgSize + x
|
||||
let val = matrix.get(idx, 0).getOrThrow().getValue()
|
||||
if (val > 0.8) {
|
||||
line = line + "██"
|
||||
} else if (val > 0.6) {
|
||||
line = line + "▓▓"
|
||||
} else if (val > 0.4) {
|
||||
line = line + "▒▒"
|
||||
} else if (val > 0.2) {
|
||||
line = line + "░░"
|
||||
} else {
|
||||
line = line + " "
|
||||
}
|
||||
}
|
||||
println(line)
|
||||
}
|
||||
println("")
|
||||
}
|
||||
|
||||
/**
|
||||
* Softmax 辅助函数
|
||||
*/
|
||||
func computeSoftmax(input: Matrix1): Matrix1 {
|
||||
let cols = input.get_col()
|
||||
var output = Matrix1(1, cols)
|
||||
|
||||
var maxVal = Float32(-1.0e30)
|
||||
for (c in 0..cols) {
|
||||
let raw = input.get(0, c).getOrThrow().getValue()
|
||||
if (raw > maxVal) {
|
||||
maxVal = raw
|
||||
}
|
||||
}
|
||||
|
||||
var expSum = Float32(0.0)
|
||||
for (c in 0..cols) {
|
||||
let raw = input.get(0, c).getOrThrow().getValue()
|
||||
let ev = Float32(exp(raw - maxVal))
|
||||
output.set(0, c, FP32(ev))
|
||||
expSum = expSum + ev
|
||||
}
|
||||
|
||||
for (c in 0..cols) {
|
||||
let v = output.get(0, c).getOrThrow().getValue()
|
||||
output.set(0, c, FP32(v / expSum))
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预测类别
|
||||
*/
|
||||
func predictClass(probs: Matrix1): Int64 {
|
||||
let cols = probs.get_col()
|
||||
var maxProb = Float32(-1.0)
|
||||
var predClass = 0
|
||||
|
||||
for (c in 0..cols) {
|
||||
let p = probs.get(0, c).getOrThrow().getValue()
|
||||
if (p > maxProb) {
|
||||
maxProb = p
|
||||
predClass = Int64(c)
|
||||
}
|
||||
}
|
||||
|
||||
predClass
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印分隔线
|
||||
*/
|
||||
func printLine(): Unit {
|
||||
println("============================================================")
|
||||
}
|
||||
|
||||
/**
|
||||
* 主测试函数
|
||||
*/
|
||||
func testGeometryCNN(): Unit {
|
||||
printLine()
|
||||
println("CNN 几何图形分类测试 (Adam 优化器版本)")
|
||||
printLine()
|
||||
println("")
|
||||
|
||||
let imgSize = 16
|
||||
let nTrainSamples = 200
|
||||
let nTestSamples = 60
|
||||
let nEpochs = 50
|
||||
let batchSize = 10
|
||||
let nClasses = 3
|
||||
|
||||
println("配置信息:")
|
||||
println(" 图像大小: ${imgSize}x${imgSize}")
|
||||
println(" 训练样本数: ${nTrainSamples}")
|
||||
println(" 测试样本数: ${nTestSamples}")
|
||||
println(" 训练轮数: ${nEpochs}")
|
||||
println(" Batch 大小: ${batchSize}")
|
||||
println(" 类别数: ${nClasses} (圆形, 正方形, 三角形)")
|
||||
println(" 优化器: Adam")
|
||||
println("")
|
||||
|
||||
println("网络结构:")
|
||||
println(" Input(16x16x1)")
|
||||
println(" -> Conv1(5x5, 1->8) -> Pool1(2x2)")
|
||||
println(" -> Conv2(3x3, 8->16) -> Pool2(2x2)")
|
||||
println(" -> Flatten -> FC(64->32) -> FC(32->3)")
|
||||
println(" -> Softmax")
|
||||
println("")
|
||||
|
||||
let lr = FP32(0.01)
|
||||
|
||||
// 构建网络
|
||||
var conv1 = ConvLayer(1, 8, (5, 5), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool1 = PoolingLayer(8, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var conv2 = ConvLayer(8, 16, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
|
||||
var pool2 = PoolingLayer(16, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
|
||||
var flatten = FlattenLayer(16, 2, 2)
|
||||
var fc1 = Nn1(64, 32, Float32(0.3), Af.Leaky_ReLU(FP32(0.01)))
|
||||
var fc2 = Nn1(32, 3, Float32(0.3), Af.Softmax)
|
||||
|
||||
// 创建 Adam 优化器
|
||||
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
|
||||
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
|
||||
var adamFC1 = Adam(FP32(0.0005)) // 全连接层1
|
||||
var adamFC2 = Adam(FP32(0.0005)) // 全连接层2
|
||||
|
||||
println("网络创建完成 (Adam优化器: Conv1, Conv2, FC1, FC2)")
|
||||
println("")
|
||||
|
||||
// 生成数据
|
||||
println("生成训练数据...")
|
||||
let (trainInputs, trainLabels) = generateGeometryData(nTrainSamples)
|
||||
println("生成测试数据...")
|
||||
let (testInputs, testLabels) = generateGeometryData(nTestSamples)
|
||||
println("数据生成完成")
|
||||
println("")
|
||||
|
||||
// 显示几个样本
|
||||
println("样本预览:")
|
||||
for (i in 0..3) {
|
||||
if (i < nTrainSamples) {
|
||||
println("样本 ${i + 1}:")
|
||||
showSample(trainInputs[i], imgSize, trainLabels[i])
|
||||
}
|
||||
}
|
||||
|
||||
println("开始训练...")
|
||||
println("")
|
||||
|
||||
var bestAccuracy = Float32(0.0)
|
||||
|
||||
// 计算卷积输出尺寸
|
||||
let conv1OutH = imgSize - 4 // 16 - 5 + 1 = 12
|
||||
let conv1OutW = imgSize - 4
|
||||
let pool1OutH = conv1OutH / 2 // 6
|
||||
let pool1OutW = conv1OutW / 2
|
||||
let conv2OutH = pool1OutH - 2 // 6 - 3 + 1 = 4
|
||||
let conv2OutW = pool1OutW - 2
|
||||
let pool2OutH = conv2OutH / 2 // 2
|
||||
let pool2OutW = conv2OutW / 2
|
||||
let flatSize = 16 * pool2OutH * pool2OutW // 64
|
||||
|
||||
for (epoch in 0..nEpochs) {
|
||||
var epochLoss = Float32(0.0)
|
||||
var epochCorrect = 0
|
||||
|
||||
// 训练
|
||||
for (i in 0..nTrainSamples) {
|
||||
let x = trainInputs[i]
|
||||
let label = trainLabels[i]
|
||||
|
||||
// 前向传播
|
||||
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
|
||||
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
|
||||
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
|
||||
|
||||
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
|
||||
|
||||
// 转换为全连接层输入
|
||||
var fc1Input = Matrix1(1, flatSize)
|
||||
for (j in 0..flatSize) {
|
||||
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
|
||||
let fc1Out = fc1.forward(fc1Input)
|
||||
let fc2Out = fc2.forward(fc1Out)
|
||||
|
||||
// Softmax
|
||||
let probs = computeSoftmax(fc2Out)
|
||||
|
||||
// 计算损失和准确率
|
||||
let probVal = probs.get(0, label).getOrThrow().getValue()
|
||||
let loss = -log(max(probVal, Float32(1e-10)))
|
||||
epochLoss = epochLoss + loss
|
||||
|
||||
let predClass = predictClass(probs)
|
||||
if (predClass == label) {
|
||||
epochCorrect += 1
|
||||
}
|
||||
|
||||
// 反向传播
|
||||
var delta = Matrix1(1, nClasses)
|
||||
for (c in 0..nClasses) {
|
||||
let prob = probs.get(0, c).getOrThrow()
|
||||
if (Int64(c) == label) {
|
||||
delta.set(0, c, prob - FP32(1.0))
|
||||
} else {
|
||||
delta.set(0, c, prob)
|
||||
}
|
||||
}
|
||||
|
||||
let fc2Delta = fc2.backward(delta)
|
||||
let fc1Delta = fc1.backward(fc2Delta)
|
||||
|
||||
// 转换回特征图格式
|
||||
var flatDelta = Matrix1(flatSize, 1)
|
||||
for (j in 0..flatSize) {
|
||||
flatDelta.set(j, 0, fc1Delta.get(0, j).getOrThrow())
|
||||
}
|
||||
|
||||
let p2Delta = flatten.backward(flatDelta)
|
||||
let c2Delta = pool2.backward(p2Delta)
|
||||
let p1Delta = conv2.backward(c2Delta)
|
||||
let c1Delta = pool1.backward(p1Delta)
|
||||
let _ = conv1.backward(c1Delta)
|
||||
|
||||
// 批次更新 - 使用 Adam
|
||||
if ((i + 1) % batchSize == 0) {
|
||||
adamConv1.updateConvLayer(conv1) // Conv1 - Adam
|
||||
adamConv2.updateConvLayer(conv2) // Conv2 - Adam
|
||||
adamFC1.updateFCLayer(fc1) // FC1 - Adam
|
||||
adamFC2.updateFCLayer(fc2) // FC2 - Adam
|
||||
|
||||
// 池化层仍使用 SGD
|
||||
pool1.update(Float32(1.0))
|
||||
pool2.update(Float32(1.0))
|
||||
}
|
||||
}
|
||||
|
||||
// 最终更新 - 使用 Adam
|
||||
adamConv1.updateConvLayer(conv1)
|
||||
adamConv2.updateConvLayer(conv2)
|
||||
adamFC1.updateFCLayer(fc1)
|
||||
adamFC2.updateFCLayer(fc2)
|
||||
pool1.update(Float32(1.0))
|
||||
pool2.update(Float32(1.0))
|
||||
|
||||
let avgLoss = epochLoss / Float32(nTrainSamples)
|
||||
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
|
||||
|
||||
if (accuracy > bestAccuracy) {
|
||||
bestAccuracy = accuracy
|
||||
}
|
||||
|
||||
// 输出训练信息
|
||||
if (epoch % 1 == 0 || epoch == nEpochs - 1) {
|
||||
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%, Adam Step: ${adamConv1.getStep()}")
|
||||
}
|
||||
}
|
||||
|
||||
println("")
|
||||
println("训练完成! 最佳训练准确率: ${bestAccuracy}%")
|
||||
println("总更新步数: ${adamConv1.getStep()}")
|
||||
println("")
|
||||
|
||||
// 测试
|
||||
println("开始测试...")
|
||||
println("")
|
||||
|
||||
var testCorrect = 0
|
||||
var confusion = Array(nClasses, { _ => Array(nClasses, { _ => 0 }) })
|
||||
var testLoss = Float32(0.0)
|
||||
|
||||
for (i in 0..nTestSamples) {
|
||||
let x = testInputs[i]
|
||||
let label = testLabels[i]
|
||||
|
||||
// 前向传播
|
||||
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
|
||||
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
|
||||
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
|
||||
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
|
||||
|
||||
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
|
||||
|
||||
var fc1Input = Matrix1(1, flatSize)
|
||||
for (j in 0..flatSize) {
|
||||
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
|
||||
}
|
||||
|
||||
let fc1Out = fc1.forward(fc1Input)
|
||||
let fc2Out = fc2.forward(fc1Out)
|
||||
|
||||
let probs = computeSoftmax(fc2Out)
|
||||
|
||||
// 计算损失
|
||||
let probVal = probs.get(0, label).getOrThrow().getValue()
|
||||
testLoss = testLoss + Float32(-log(max(probVal, Float32(1e-10))))
|
||||
|
||||
// 预测
|
||||
let predClass = predictClass(probs)
|
||||
|
||||
confusion[label][predClass] += 1
|
||||
if (predClass == label) {
|
||||
testCorrect += 1
|
||||
}
|
||||
|
||||
// 显示前几个错误分类的样本
|
||||
if (predClass != label && testCorrect < 10) {
|
||||
println("错误分类样本 ${i + 1}:")
|
||||
showSample(x, imgSize, label)
|
||||
let shapeNames = ["圆形", "正方形", "三角形"]
|
||||
println(" 真实标签: ${shapeNames[label]}, 预测标签: ${shapeNames[predClass]}")
|
||||
println("")
|
||||
}
|
||||
}
|
||||
|
||||
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
|
||||
let avgTestLoss = testLoss / Float32(nTestSamples)
|
||||
|
||||
println("测试结果:")
|
||||
println(" 准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
|
||||
println(" 平均损失: ${avgTestLoss}")
|
||||
println("")
|
||||
|
||||
let classNames = ["圆形 ", "正方形", "三角形"]
|
||||
println("各类别准确率:")
|
||||
for (c in 0..nClasses) {
|
||||
var total = 0
|
||||
for (p in 0..nClasses) {
|
||||
total += confusion[c][p]
|
||||
}
|
||||
if (total > 0) {
|
||||
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
|
||||
println(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
|
||||
}
|
||||
}
|
||||
println("")
|
||||
|
||||
println("混淆矩阵:")
|
||||
println(" 预测圆形 预测正方形 预测三角形")
|
||||
for (t in 0..nClasses) {
|
||||
var line = "真实${classNames[t]}:"
|
||||
for (p in 0..nClasses) {
|
||||
line = line + " ${confusion[t][p]} "
|
||||
}
|
||||
println(line)
|
||||
}
|
||||
println("")
|
||||
|
||||
printLine()
|
||||
println("几何图形分类测试完成!")
|
||||
printLine()
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* ads1115_linux.c
|
||||
*
|
||||
* Linux C 通过 I2C 采集 ADS1115 示例
|
||||
* ADS1115: TI 16位 4通道 ΔΣ ADC, 最高 860SPS, I2C 接口
|
||||
*
|
||||
* 编译: gcc -o ads1115 ads1115_linux.c
|
||||
* 运行: ./ads1115
|
||||
* ./ads1115 0x48 # 指定 I2C 地址
|
||||
* ./ads1115 0x48 1 2 # 指定地址、PGA、数据速率
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
// ============================================================
|
||||
// I2C 配置
|
||||
// ============================================================
|
||||
#define I2C_DEVICE "/dev/i2c-1" // I2C 总线 (树莓派是 i2c-1)
|
||||
#define ADS1115_ADDR 0x48 // ADDR 接 GND 时默认地址
|
||||
|
||||
// ============================================================
|
||||
// ADS1115 寄存器
|
||||
// ============================================================
|
||||
#define ADS1115_REG_CONVERSION 0x00 // 转换结果 (只读)
|
||||
#define ADS1115_REG_CONFIG 0x01 // 配置寄存器 (读/写)
|
||||
#define ADS1115_REG_LO_THRESH 0x02 // 低阈值比较
|
||||
#define ADS1115_REG_HI_THRESH 0x03 // 高阈值比较
|
||||
|
||||
// ============================================================
|
||||
// ADS1115 配置寄存器 bit 定义 (16 位)
|
||||
// ============================================================
|
||||
// BIT15: OS[0] — 单次转换启动位 (1=开始)
|
||||
// BIT14-12: MUX[2:0] — 输入多路选择
|
||||
// BIT11-9: PGA[2:0] — 可编程增益放大器
|
||||
// BIT8: MODE — 0=连续转换, 1=单次(省电)
|
||||
// BIT7-5: DR[2:0] — 数据速率
|
||||
// BIT4: COMP_MODE — 比较模式 (0=传统)
|
||||
// BIT3: COMP_POL — 比较极性 (0=低有效)
|
||||
// BIT2: COMP_LAT — 比较锁存 (0=不锁存)
|
||||
// BIT1-0: COMP_QUE — 比较器队列 (11=禁用)
|
||||
|
||||
// --- MUX 输入通道 ---
|
||||
#define ADS1115_MUX_AIN0_AIN1 0x0000 // 差分: P=AIN0, N=AIN1 (默认)
|
||||
#define ADS1115_MUX_AIN0_AIN3 0x1000 // 差分: P=AIN0, N=AIN3
|
||||
#define ADS1115_MUX_AIN1_AIN3 0x2000 // 差分: P=AIN1, N=AIN3
|
||||
#define ADS1115_MUX_AIN2_AIN3 0x3000 // 差分: P=AIN2, N=AIN3
|
||||
#define ADS1115_MUX_AIN0_GND 0x4000 // 单端: AIN0
|
||||
#define ADS1115_MUX_AIN1_GND 0x5000 // 单端: AIN1
|
||||
#define ADS1115_MUX_AIN2_GND 0x6000 // 单端: AIN2
|
||||
#define ADS1115_MUX_AIN3_GND 0x7000 // 单端: AIN3
|
||||
|
||||
// --- PGA 增益 (满量程范围) ---
|
||||
#define ADS1115_PGA_6_144V 0x0000 // ±6.144V (1LSB=0.1875mV)
|
||||
#define ADS1115_PGA_4_096V 0x0200 // ±4.096V (1LSB=0.125mV)
|
||||
#define ADS1115_PGA_2_048V 0x0400 // ±2.048V (1LSB=0.0625mV) (默认)
|
||||
#define ADS1115_PGA_1_024V 0x0600 // ±1.024V (1LSB=0.03125mV)
|
||||
#define ADS1115_PGA_0_512V 0x0800 // ±0.512V (1LSB=0.015625mV)
|
||||
#define ADS1115_PGA_0_256V 0x0A00 // ±0.256V (1LSB=0.0078125mV)
|
||||
|
||||
// --- 数据速率 ---
|
||||
#define ADS1115_DR_8SPS 0x0000 // 8 SPS
|
||||
#define ADS1115_DR_16SPS 0x0020 // 16 SPS
|
||||
#define ADS1115_DR_32SPS 0x0040 // 32 SPS
|
||||
#define ADS1115_DR_64SPS 0x0060 // 64 SPS
|
||||
#define ADS1115_DR_128SPS 0x0080 // 128 SPS (默认)
|
||||
#define ADS1115_DR_250SPS 0x00A0 // 250 SPS
|
||||
#define ADS1115_DR_475SPS 0x00C0 // 475 SPS
|
||||
#define ADS1115_DR_860SPS 0x00E0 // 860 SPS
|
||||
|
||||
// --- 比较器队列 (COMP_QUE) ---
|
||||
#define ADS1115_COMP_QUE_1 0x0000 // 1 次后触发
|
||||
#define ADS1115_COMP_QUE_2 0x0001 // 2 次后触发
|
||||
#define ADS1115_COMP_QUE_4 0x0002 // 4 次后触发
|
||||
#define ADS1115_COMP_QUE_DISABLE 0x0003 // 禁用比较器 (默认)
|
||||
|
||||
// --- 操作模式 ---
|
||||
#define ADS1115_MODE_CONTINUOUS 0x0000 // 连续转换
|
||||
#define ADS1115_MODE_SINGLE 0x0100 // 单次转换 (默认, 省电)
|
||||
|
||||
// --- 启动转换 ---
|
||||
#define ADS1115_OS_START 0x8000 // 启动单次转换
|
||||
|
||||
// ============================================================
|
||||
// 结构体: ADS1115 配置上下文
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
int fd; // I2C 文件描述符
|
||||
uint8_t addr; // I2C 设备地址
|
||||
uint16_t config; // 当前配置值
|
||||
double fs_voltage; // 满量程电压 (取决于 PGA)
|
||||
} ads1115_t;
|
||||
|
||||
// ============================================================
|
||||
// PGA 对应的满量程电压 (单位: V)
|
||||
// ============================================================
|
||||
static const double pga_fs[] = {
|
||||
[0] = 6.144, // ADS1115_PGA_6_144V
|
||||
[1] = 4.096, // ADS1115_PGA_4_096V
|
||||
[2] = 2.048, // ADS1115_PGA_2_048V
|
||||
[3] = 1.024, // ADS1115_PGA_1_024V
|
||||
[4] = 0.512, // ADS1115_PGA_0_512V
|
||||
[5] = 0.256, // ADS1115_PGA_0_256V
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// PGA 增益对应的满量程电压名
|
||||
// ============================================================
|
||||
static const char *pga_names[] = {
|
||||
[0] = "±6.144V",
|
||||
[1] = "±4.096V",
|
||||
[2] = "±2.048V",
|
||||
[3] = "±1.024V",
|
||||
[4] = "±0.512V",
|
||||
[5] = "±0.256V",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 数据速率字符串
|
||||
// ============================================================
|
||||
static const char *dr_names[] = {
|
||||
[0] = "8 SPS",
|
||||
[1] = "16 SPS",
|
||||
[2] = "32 SPS",
|
||||
[3] = "64 SPS",
|
||||
[4] = "128 SPS",
|
||||
[5] = "250 SPS",
|
||||
[6] = "475 SPS",
|
||||
[7] = "860 SPS",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 毫秒延时
|
||||
// ============================================================
|
||||
static void delay_ms(int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 微秒延时
|
||||
// ============================================================
|
||||
static void delay_us(int us)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = us / 1000000;
|
||||
ts.tv_nsec = (us % 1000000) * 1000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 初始化
|
||||
// ============================================================
|
||||
static int i2c_init(ads1115_t *dev, const char *i2c_dev, uint8_t addr)
|
||||
{
|
||||
dev->fd = open(i2c_dev, O_RDWR);
|
||||
if (dev->fd < 0) {
|
||||
perror("打开 I2C 设备失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ioctl(dev->fd, I2C_SLAVE, addr) < 0) {
|
||||
perror("设置 I2C 从机地址失败");
|
||||
close(dev->fd);
|
||||
dev->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->addr = addr;
|
||||
dev->config = 0;
|
||||
printf("I2C 初始化完成: 设备=%s, 地址=0x%02X\n", i2c_dev, addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 写 2 字节 (大端)
|
||||
// ============================================================
|
||||
static int i2c_write_word(ads1115_t *dev, uint8_t reg, uint16_t value)
|
||||
{
|
||||
uint8_t buf[3];
|
||||
buf[0] = reg;
|
||||
buf[1] = (value >> 8) & 0xFF; // 高字节
|
||||
buf[2] = value & 0xFF; // 低字节
|
||||
|
||||
if (write(dev->fd, buf, 3) != 3) {
|
||||
perror("I2C 写失败");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 读 2 字节 (大端)
|
||||
// ============================================================
|
||||
static int i2c_read_word(ads1115_t *dev, uint8_t reg, uint16_t *value)
|
||||
{
|
||||
// 先写入寄存器地址
|
||||
if (write(dev->fd, ®, 1) != 1) {
|
||||
perror("I2C 设置寄存器地址失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 读取 2 字节
|
||||
uint8_t buf[2];
|
||||
if (read(dev->fd, buf, 2) != 2) {
|
||||
perror("I2C 读失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*value = ((uint16_t)buf[0] << 8) | buf[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写配置寄存器
|
||||
// ============================================================
|
||||
static int ads1115_write_config(ads1115_t *dev, uint16_t config)
|
||||
{
|
||||
dev->config = config;
|
||||
return i2c_write_word(dev, ADS1115_REG_CONFIG, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读配置寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_config(ads1115_t *dev, uint16_t *config)
|
||||
{
|
||||
return i2c_read_word(dev, ADS1115_REG_CONFIG, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读转换结果寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_conversion(ads1115_t *dev, int16_t *value)
|
||||
{
|
||||
uint16_t raw;
|
||||
int ret = i2c_read_word(dev, ADS1115_REG_CONVERSION, &raw);
|
||||
if (ret == 0) {
|
||||
*value = (int16_t)raw;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读阈值寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_thresh(ads1115_t *dev, int16_t *lo, int16_t *hi)
|
||||
{
|
||||
uint16_t raw_lo, raw_hi;
|
||||
if (i2c_read_word(dev, ADS1115_REG_LO_THRESH, &raw_lo) < 0) return -1;
|
||||
if (i2c_read_word(dev, ADS1115_REG_HI_THRESH, &raw_hi) < 0) return -1;
|
||||
*lo = (int16_t)raw_lo;
|
||||
*hi = (int16_t)raw_hi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写阈值寄存器
|
||||
// ============================================================
|
||||
static int ads1115_write_thresh(ads1115_t *dev, int16_t lo, int16_t hi)
|
||||
{
|
||||
if (i2c_write_word(dev, ADS1115_REG_LO_THRESH, (uint16_t)lo) < 0) return -1;
|
||||
if (i2c_write_word(dev, ADS1115_REG_HI_THRESH, (uint16_t)hi) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 初始化 ADS1115
|
||||
//
|
||||
// pga_idx: 0~5 对应 ADS1115_PGA_xxx
|
||||
// dr_idx: 0~7 对应 ADS1115_DR_xxx
|
||||
// ============================================================
|
||||
static int ads1115_init(ads1115_t *dev, int pga_idx, int dr_idx)
|
||||
{
|
||||
if (pga_idx < 0 || pga_idx > 5) pga_idx = 2; // 默认 ±2.048V
|
||||
if (dr_idx < 0 || dr_idx > 7) dr_idx = 4; // 默认 128SPS
|
||||
|
||||
dev->fs_voltage = pga_fs[pga_idx];
|
||||
|
||||
// 构建配置:
|
||||
// - 单次转换模式
|
||||
// - 禁用比较器
|
||||
// - 默认通道 AIN0-GND
|
||||
uint16_t config = ADS1115_MUX_AIN0_GND // 单端 AIN0
|
||||
| (pga_idx << 9) // PGA
|
||||
| ADS1115_MODE_SINGLE // 单次模式
|
||||
| (dr_idx << 5) // 数据速率
|
||||
| ADS1115_COMP_QUE_DISABLE; // 禁用比较器
|
||||
|
||||
int ret = ads1115_write_config(dev, config);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
printf("ADS1115 初始化完成: ");
|
||||
printf("PGA=%s, 速率=%s, 满量程=%.3fV\n",
|
||||
pga_names[pga_idx], dr_names[dr_idx], dev->fs_voltage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取单端通道 (单次转换)
|
||||
// ch: 0~3 对应 AIN0~AIN3
|
||||
// ============================================================
|
||||
static double ads1115_read_single(ads1115_t *dev, int ch)
|
||||
{
|
||||
if (ch < 0 || ch > 3) {
|
||||
fprintf(stderr, "通道号无效: %d (有效: 0~3)\n", ch);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 1. 重写配置: 设置通道 + 启动转换
|
||||
uint16_t mux = ADS1115_MUX_AIN0_GND | (ch << 12); // AINch-GND
|
||||
uint16_t config = (dev->config & ~0x7000) // 清除 MUX 位
|
||||
| mux
|
||||
| ADS1115_OS_START; // 启动单次转换
|
||||
|
||||
ads1115_write_config(dev, config);
|
||||
|
||||
// 2. 等待转换完成 (OS 位被硬件清 0)
|
||||
// 根据数据速率等待足够时间
|
||||
uint16_t dr_idx = (config >> 5) & 0x07;
|
||||
int wait_ms = 1000 / (8 << dr_idx) + 1; // 保守估计
|
||||
delay_ms(wait_ms + 2);
|
||||
|
||||
// 3. 读转换值
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 4. 转换为电压
|
||||
// ADS1115 16位有符号, 满量程 = FS_VOLTAGE
|
||||
// V = raw * FS_VOLTAGE / 32768
|
||||
double voltage = (double)raw * dev->fs_voltage / 32768.0;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取差分通道 (单次转换)
|
||||
// pos: 正极通道 (0~3)
|
||||
// neg: 负极通道 (0~3)
|
||||
// ============================================================
|
||||
static double ads1115_read_diff(ads1115_t *dev, int pos, int neg)
|
||||
{
|
||||
if (pos < 0 || pos > 3 || neg < 0 || neg > 3) {
|
||||
fprintf(stderr, "通道号无效\n");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 构建 MUX 值 (差分)
|
||||
// AIN0-AIN1: 0, AIN0-AIN3: 1, AIN1-AIN3: 2, AIN2-AIN3: 3
|
||||
uint16_t mux_val;
|
||||
if (pos == 0 && neg == 1) mux_val = 0;
|
||||
else if (pos == 0 && neg == 3) mux_val = 1;
|
||||
else if (pos == 1 && neg == 3) mux_val = 2;
|
||||
else if (pos == 2 && neg == 3) mux_val = 3;
|
||||
else {
|
||||
fprintf(stderr, "不支持的差分组合: AIN%d - AIN%d\n", pos, neg);
|
||||
fprintf(stderr, "支持: 0-1, 0-3, 1-3, 2-3\n");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
uint16_t mux = mux_val << 12;
|
||||
uint16_t config = (dev->config & ~0x7000)
|
||||
| mux
|
||||
| ADS1115_OS_START;
|
||||
|
||||
ads1115_write_config(dev, config);
|
||||
|
||||
uint16_t dr_idx = (config >> 5) & 0x07;
|
||||
int wait_ms = 1000 / (8 << dr_idx) + 1;
|
||||
delay_ms(wait_ms + 2);
|
||||
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double voltage = (double)raw * dev->fs_voltage / 32768.0;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续模式读数 (需先配置为连续模式)
|
||||
// ============================================================
|
||||
static int ads1115_start_continuous(ads1115_t *dev, int ch)
|
||||
{
|
||||
if (ch < 0 || ch > 3) return -1;
|
||||
|
||||
uint16_t config = (dev->config & ~0x7100) // 清除 MUX+MODE 位
|
||||
| (ADS1115_MUX_AIN0_GND | (ch << 12)) // 通道
|
||||
| ADS1115_MODE_CONTINUOUS // 连续模式
|
||||
| ADS1115_COMP_QUE_DISABLE;
|
||||
|
||||
return ads1115_write_config(dev, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续模式读数
|
||||
// ============================================================
|
||||
static double ads1115_read_continuous(ads1115_t *dev)
|
||||
{
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (double)raw * dev->fs_voltage / 32768.0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 扫描所有单端通道
|
||||
// ============================================================
|
||||
static void ads1115_scan_all(ads1115_t *dev)
|
||||
{
|
||||
printf("\n===== 单端通道扫描 =====\n");
|
||||
const char *ch_names[] = {"AIN0", "AIN1", "AIN2", "AIN3"};
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
double v = ads1115_read_single(dev, ch);
|
||||
printf(" %s: %+.6f V\n", ch_names[ch], v);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印当前配置
|
||||
// ============================================================
|
||||
static void ads1115_print_config(ads1115_t *dev)
|
||||
{
|
||||
uint16_t config;
|
||||
if (ads1115_read_config(dev, &config) < 0) return;
|
||||
|
||||
int mux = (config >> 12) & 0x07;
|
||||
int pga = (config >> 9) & 0x07;
|
||||
int mode = (config >> 8) & 0x01;
|
||||
int dr = (config >> 5) & 0x07;
|
||||
int comp = config & 0x03;
|
||||
|
||||
printf("\n===== ADS1115 寄存器 =====\n");
|
||||
printf(" 配置寄存器: 0x%04X\n", config);
|
||||
printf(" MUX: %d (", mux);
|
||||
switch (mux) {
|
||||
case 0: printf("AIN0-AIN1"); break;
|
||||
case 1: printf("AIN0-AIN3"); break;
|
||||
case 2: printf("AIN1-AIN3"); break;
|
||||
case 3: printf("AIN2-AIN3"); break;
|
||||
case 4: printf("AIN0-GND"); break;
|
||||
case 5: printf("AIN1-GND"); break;
|
||||
case 6: printf("AIN2-GND"); break;
|
||||
case 7: printf("AIN3-GND"); break;
|
||||
}
|
||||
printf(")\n");
|
||||
printf(" PGA: %d -> %s (满量程 %.3fV)\n", pga,
|
||||
(pga <= 5) ? pga_names[pga] : "未知",
|
||||
(pga <= 5) ? pga_fs[pga] : 0);
|
||||
printf(" 模式: %s\n", mode ? "单次" : "连续");
|
||||
printf(" 速率: %d -> %s\n", dr, dr_names[dr & 0x07]);
|
||||
printf(" 比较器: %s\n", comp == 3 ? "禁用" : "启用");
|
||||
|
||||
// 读阈值
|
||||
int16_t lo, hi;
|
||||
if (ads1115_read_thresh(dev, &lo, &hi) == 0) {
|
||||
printf(" 阈值: LO=%d (%.6fV), HI=%d (%.6fV)\n",
|
||||
lo, (double)lo * dev->fs_voltage / 32768.0,
|
||||
hi, (double)hi * dev->fs_voltage / 32768.0);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印转换结果
|
||||
// ============================================================
|
||||
static void print_reading(const char *label, double voltage)
|
||||
{
|
||||
printf(" %s: %+.6f V (%.3f mV)\n", label, voltage, voltage * 1000.0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印帮助
|
||||
// ============================================================
|
||||
static void print_help(const char *prog)
|
||||
{
|
||||
printf("\n用法: %s [I2C地址] [PGA] [数据速率]\n", prog);
|
||||
printf("\n参数 (可选):\n");
|
||||
printf(" I2C地址 0x48~0x4B (默认 0x48)\n");
|
||||
printf(" PGA 0~5 (默认 2 = ±2.048V)\n");
|
||||
printf(" 0=±6.144V, 1=±4.096V, 2=±2.048V\n");
|
||||
printf(" 3=±1.024V, 4=±0.512V, 5=±0.256V\n");
|
||||
printf(" 数据速率 0~7 (默认 4 = 128SPS)\n");
|
||||
printf(" 0=8, 1=16, 2=32, 3=64\n");
|
||||
printf(" 4=128, 5=250, 6=475, 7=860 (SPS)\n");
|
||||
printf("\n示例:\n");
|
||||
printf(" %s\n", prog);
|
||||
printf(" %s 0x49\n", prog);
|
||||
printf(" %s 0x48 2 6\n", prog);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
ads1115_t dev;
|
||||
uint8_t addr = ADS1115_ADDR;
|
||||
int pga = 2; // ±2.048V
|
||||
int dr = 4; // 128 SPS
|
||||
|
||||
// 解析命令行参数
|
||||
if (argc >= 2) {
|
||||
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
addr = (uint8_t)strtol(argv[1], NULL, 0);
|
||||
}
|
||||
if (argc >= 3) pga = atoi(argv[2]);
|
||||
if (argc >= 4) dr = atoi(argv[3]);
|
||||
|
||||
printf("ADS1115 Linux I2C 采集示例\n");
|
||||
printf("============================\n\n");
|
||||
|
||||
// 1. 初始化 I2C
|
||||
if (i2c_init(&dev, I2C_DEVICE, addr) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. 初始化 ADS1115
|
||||
if (ads1115_init(&dev, pga, dr) < 0) {
|
||||
close(dev.fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. 单通道测试 (AIN0)
|
||||
printf("\n===== 单通道测试 (AIN0, 连续读 10 次) =====\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
double v = ads1115_read_single(&dev, 0);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d]", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 4. 扫描所有单端通道
|
||||
ads1115_scan_all(&dev);
|
||||
|
||||
// 5. 差分模式示例
|
||||
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double v = ads1115_read_diff(&dev, 0, 1);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d]", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 6. 连续模式测试
|
||||
printf("\n===== 连续模式测试 (AIN0, 5 次) =====\n");
|
||||
if (ads1115_start_continuous(&dev, 0) == 0) {
|
||||
delay_ms(50); // 等第一次转换完成
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double v = ads1115_read_continuous(&dev);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d] 连续", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
} else {
|
||||
printf(" 启动连续模式失败\n");
|
||||
}
|
||||
|
||||
// 7. 打印配置信息
|
||||
ads1115_print_config(&dev);
|
||||
|
||||
// 8. 清理
|
||||
close(dev.fd);
|
||||
printf("\n程序结束.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* ads1256_linux.c
|
||||
*
|
||||
* Linux C 通过 SPI 采集 ADS1256 示例
|
||||
* 适用平台: Raspberry Pi / 任何 Linux + SPI 设备
|
||||
* 编译: gcc -o ads1256 ads1256_linux.c -lwiringPi (树莓派用 wiringPi)
|
||||
* gcc -o ads1256 ads1256_linux.c (通用 /dev/spidev)
|
||||
*
|
||||
* ADS1256: TI 24位 8通道 ΔΣ ADC, 最高 30kSPS
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
#include <time.h>
|
||||
|
||||
// ============================================================
|
||||
// 配置参数 - 按需修改
|
||||
// ============================================================
|
||||
#define SPI_DEVICE "/dev/spidev0.0" // SPI 设备路径
|
||||
#define SPI_SPEED 1920000 // SPI 时钟 (1.92MHz, ADS1256 最大支持 2MHz)
|
||||
#define SPI_MODE 1 // SPI 模式: CPOL=0, CPHA=1 (模式1)
|
||||
#define SPI_BITS 8 // 8 位传输
|
||||
|
||||
// ADS1256 片选用 GPIO (如果使用硬件 CS 则不需要)
|
||||
// 若用 /dev/spidev 的硬件 CS, 这部分不需要手动控制
|
||||
// #define CS_PIN 8 // 例如 wiringPi 的 GPIO 8
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 寄存器地址
|
||||
// ============================================================
|
||||
#define ADS1256_REG_STATUS 0x00
|
||||
#define ADS1256_REG_MUX 0x01
|
||||
#define ADS1256_REG_ADCON 0x02
|
||||
#define ADS1256_REG_DRATE 0x03
|
||||
#define ADS1256_REG_IO 0x04
|
||||
#define ADS1256_REG_OFC0 0x05
|
||||
#define ADS1256_REG_OFC1 0x06
|
||||
#define ADS1256_REG_OFC2 0x07
|
||||
#define ADS1256_REG_FSC0 0x08
|
||||
#define ADS1256_REG_FSC1 0x09
|
||||
#define ADS1256_REG_FSC2 0x0A
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 命令定义
|
||||
// ============================================================
|
||||
#define ADS1256_CMD_WAKEUP 0x00 // 退出待机
|
||||
#define ADS1256_CMD_RDATA 0x01 // 读数据
|
||||
#define ADS1256_CMD_RDATAC 0x03 // 连续读数据
|
||||
#define ADS1256_CMD_SDATAC 0x0F // 停止连续读
|
||||
#define ADS1256_CMD_RREG 0x10 // 读寄存器 (| 寄存器地址)
|
||||
#define ADS1256_CMD_WREG 0x50 // 写寄存器 (| 寄存器地址)
|
||||
#define ADS1256_CMD_SELFCAL 0xF0 // 自校准
|
||||
#define ADS1256_CMD_SELFOCAL 0xF1 // 自失调校准
|
||||
#define ADS1256_CMD_SELFGCAL 0xF2 // 自增益校准
|
||||
#define ADS1256_CMD_SYSOCAL 0xF3 // 系统失调校准
|
||||
#define ADS1256_CMD_SYSGCAL 0xF4 // 系统增益校准
|
||||
#define ADS1256_CMD_SYNC 0xFC // 同步
|
||||
#define ADS1256_CMD_STANDBY 0xFD // 待机
|
||||
#define ADS1256_CMD_RESET 0xFE // 复位
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 数据速率 (DRATE 寄存器值)
|
||||
// ============================================================
|
||||
#define ADS1256_DRATE_30000 0xF0 // 30,000 SPS
|
||||
#define ADS1256_DRATE_15000 0xE0 // 15,000 SPS
|
||||
#define ADS1256_DRATE_7500 0xD0 // 7,500 SPS
|
||||
#define ADS1256_DRATE_3750 0xC0 // 3,750 SPS
|
||||
#define ADS1256_DRATE_2000 0xB0 // 2,000 SPS
|
||||
#define ADS1256_DRATE_1000 0xA0 // 1,000 SPS
|
||||
#define ADS1256_DRATE_500 0x90 // 500 SPS
|
||||
#define ADS1256_DRATE_100 0x80 // 100 SPS
|
||||
#define ADS1256_DRATE_60 0x70 // 60 SPS
|
||||
#define ADS1256_DRATE_50 0x60 // 50 SPS
|
||||
#define ADS1256_DRATE_30 0x50 // 30 SPS
|
||||
#define ADS1256_DRATE_25 0x40 // 25 SPS
|
||||
#define ADS1256_DRATE_15 0x30 // 15 SPS
|
||||
#define ADS1256_DRATE_10 0x20 // 10 SPS
|
||||
#define ADS1256_DRATE_5 0x10 // 5 SPS
|
||||
|
||||
// ============================================================
|
||||
// PGA 增益 (ADCON 寄存器的低 3 位)
|
||||
// ============================================================
|
||||
#define ADS1256_PGA_1 0x00 // ±5V
|
||||
#define ADS1256_PGA_2 0x01 // ±2.5V
|
||||
#define ADS1256_PGA_4 0x02 // ±1.25V
|
||||
#define ADS1256_PGA_8 0x03 // ±0.625V
|
||||
#define ADS1256_PGA_16 0x04 // ±0.3125V
|
||||
#define ADS1256_PGA_32 0x05 // ±0.15625V
|
||||
#define ADS1256_PGA_64 0x06 // ±0.078125V
|
||||
|
||||
// 参考电压 (V)
|
||||
#define ADS1256_VREF 2.5
|
||||
|
||||
// ============================================================
|
||||
// 全局变量
|
||||
// ============================================================
|
||||
static int spi_fd = -1; // SPI 文件描述符
|
||||
static uint8_t spi_txbuf[32];
|
||||
static uint8_t spi_rxbuf[32];
|
||||
|
||||
// ============================================================
|
||||
// SPI 初始化
|
||||
// ============================================================
|
||||
int spi_init(const char *device, uint32_t speed, uint8_t mode, uint8_t bits)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spi_fd = open(device, O_RDWR);
|
||||
if (spi_fd < 0) {
|
||||
perror("打开 SPI 设备失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置 SPI 模式
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 模式失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置位数
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 位数失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置速度
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 速度失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 读取并验证设置
|
||||
uint8_t rb_mode, rb_bits;
|
||||
uint32_t rb_speed;
|
||||
|
||||
ioctl(spi_fd, SPI_IOC_RD_MODE, &rb_mode);
|
||||
ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &rb_bits);
|
||||
ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &rb_speed);
|
||||
|
||||
printf("SPI 初始化完成: 模式=%d, 位数=%d, 速度=%d Hz\n",
|
||||
rb_mode, rb_bits, rb_speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 收发 (全双工)
|
||||
// ============================================================
|
||||
int spi_transfer(uint8_t *tx, uint8_t *rx, int len)
|
||||
{
|
||||
struct spi_ioc_transfer tr = {
|
||||
.tx_buf = (unsigned long)tx,
|
||||
.rx_buf = (unsigned long)rx,
|
||||
.len = (uint32_t)len,
|
||||
.delay_usecs = 0,
|
||||
.speed_hz = SPI_SPEED,
|
||||
.bits_per_word = SPI_BITS,
|
||||
.cs_change = 0,
|
||||
};
|
||||
|
||||
int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr);
|
||||
if (ret < 0) {
|
||||
perror("SPI 传输失败");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 发送单字节
|
||||
// ============================================================
|
||||
static inline void spi_write_byte(uint8_t byte)
|
||||
{
|
||||
spi_txbuf[0] = byte;
|
||||
spi_transfer(spi_txbuf, spi_rxbuf, 1);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 读单字节
|
||||
// ============================================================
|
||||
static inline uint8_t spi_read_byte(void)
|
||||
{
|
||||
spi_txbuf[0] = 0xFF; // MOSI 发 0xFF 以产生时钟
|
||||
spi_transfer(spi_txbuf, spi_rxbuf, 1);
|
||||
return spi_rxbuf[0];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 等待 DRDY 引脚变低 (数据就绪)
|
||||
//
|
||||
// 注意: 这里用延时模拟等待,实际应接 GPIO 中断或轮询
|
||||
// 若 DRDY 接了 GPIO (如 wiringPi 的 pin 25),可改为:
|
||||
// while(digitalRead(DRDY_PIN));
|
||||
// ============================================================
|
||||
void wait_drdy(void)
|
||||
{
|
||||
// 实际项目中用 GPIO 轮询更准确
|
||||
// 这里用延时估算
|
||||
usleep(100); // 100us,根据采样率调整
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 复位
|
||||
// ============================================================
|
||||
void ads1256_reset(void)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_RESET);
|
||||
usleep(1000); // 等待复位完成
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP); // 退出待机
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写 ADS1256 寄存器
|
||||
// ============================================================
|
||||
void ads1256_write_reg(uint8_t reg, uint8_t value)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_WREG | reg);
|
||||
spi_write_byte(0x00); // 后续字节数 - 1 (0 = 1 byte)
|
||||
spi_write_byte(value);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读 ADS1256 寄存器
|
||||
// ============================================================
|
||||
uint8_t ads1256_read_reg(uint8_t reg)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_RREG | reg);
|
||||
spi_write_byte(0x00); // 后续字节数 - 1
|
||||
return spi_read_byte();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 设置输入通道 (MUX 寄存器)
|
||||
//
|
||||
// mux:
|
||||
// 单端模式: POS = channel, NEG = 0x08 (AINCOM)
|
||||
// 差分模式: POS = channel_p, NEG = channel_n
|
||||
// ============================================================
|
||||
void ads1256_set_channel(uint8_t pos, uint8_t neg)
|
||||
{
|
||||
uint8_t mux = ((pos & 0x07) << 4) | (neg & 0x07);
|
||||
ads1256_write_reg(ADS1256_REG_MUX, mux);
|
||||
usleep(1);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 初始化
|
||||
// ============================================================
|
||||
int ads1256_init(void)
|
||||
{
|
||||
printf("正在初始化 ADS1256...\n");
|
||||
|
||||
// 1. 复位 ADS1256
|
||||
ads1256_reset();
|
||||
usleep(10000);
|
||||
|
||||
// 2. 配置状态寄存器
|
||||
// BIT7: ID (只读)
|
||||
// BIT6: ID (只读)
|
||||
// BIT5: ORDER = 0 (MSB 优先)
|
||||
// BIT4: ACAL = 1 (自动校准)
|
||||
// BIT3: BUFEN = 0 (关闭缓冲)
|
||||
// BIT2: DRB = 0 (DRDY 低有效)
|
||||
// BIT1-0: 保留
|
||||
ads1256_write_reg(ADS1256_REG_STATUS, 0x14); // 0b00010100
|
||||
|
||||
// 3. 配置 MUX (默认: AIN0 + AINCOM)
|
||||
ads1256_set_channel(0, 0x08);
|
||||
|
||||
// 4. 配置 ADCON (A/D 控制寄存器)
|
||||
// BIT7-5: CLK1-0 = 00 (时钟输出关), SDCS = 0
|
||||
// BIT4-3: 保留
|
||||
// BIT2-0: PGA = 001 (增益 ×2)
|
||||
ads1256_write_reg(ADS1256_REG_ADCON, ADS1256_PGA_2);
|
||||
|
||||
// 5. 设置数据速率 (1000 SPS)
|
||||
ads1256_write_reg(ADS1256_REG_DRATE, ADS1256_DRATE_1000);
|
||||
|
||||
// 6. 自校准
|
||||
printf("正在进行自校准...\n");
|
||||
spi_write_byte(ADS1256_CMD_SELFCAL);
|
||||
|
||||
// 等待校准完成 (约 400ms @ 1000SPS)
|
||||
usleep(500000);
|
||||
|
||||
printf("ADS1256 初始化完成!\n");
|
||||
|
||||
// 读寄存器验证
|
||||
uint8_t status = ads1256_read_reg(ADS1256_REG_STATUS);
|
||||
uint8_t mux = ads1256_read_reg(ADS1256_REG_MUX);
|
||||
uint8_t adcon = ads1256_read_reg(ADS1256_REG_ADCON);
|
||||
uint8_t drate = ads1256_read_reg(ADS1256_REG_DRATE);
|
||||
|
||||
printf("寄存器状态: STATUS=0x%02X, MUX=0x%02X, ADCON=0x%02X, DRATE=0x%02X\n",
|
||||
status, mux, adcon, drate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单次读取 24 位数据
|
||||
//
|
||||
// 返回: 24 位有符号整数 (二进制补码)
|
||||
// ============================================================
|
||||
int32_t ads1256_read_data(void)
|
||||
{
|
||||
uint8_t buf[3];
|
||||
|
||||
// 发送 RDATA 命令
|
||||
wait_drdy();
|
||||
spi_write_byte(ADS1256_CMD_RDATA);
|
||||
|
||||
// 读 3 字节 (24 位)
|
||||
buf[0] = spi_read_byte();
|
||||
buf[1] = spi_read_byte();
|
||||
buf[2] = spi_read_byte();
|
||||
|
||||
// 组合成 32 位有符号数 (符号扩展)
|
||||
int32_t value;
|
||||
value = ((int32_t)buf[0] << 16) |
|
||||
((int32_t)buf[1] << 8) |
|
||||
((int32_t)buf[2]);
|
||||
|
||||
// 24 位有符号数符号扩展
|
||||
if (value & 0x800000) {
|
||||
value |= 0xFF000000;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 将原始值转换为电压
|
||||
// ============================================================
|
||||
double ads1256_raw_to_voltage(int32_t raw, uint8_t pga)
|
||||
{
|
||||
// ADS1256 满量程 = ±VREF / PGA
|
||||
// 24 位: -2^23 ~ 2^23-1
|
||||
double voltage = (double)raw * ADS1256_VREF / (double)(1 << 23) / (double)pga;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取指定单端通道
|
||||
// ============================================================
|
||||
int32_t ads1256_read_channel(uint8_t ch)
|
||||
{
|
||||
ads1256_set_channel(ch, 0x08); // AINch vs AINCOM
|
||||
usleep(1);
|
||||
|
||||
// 发送 SYNC + WAKEUP 以启动转换
|
||||
spi_write_byte(ADS1256_CMD_SYNC);
|
||||
usleep(1);
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP);
|
||||
|
||||
// 等待转换完成 (根据采样率调整)
|
||||
// 1000 SPS -> 约 1ms
|
||||
usleep(2000);
|
||||
|
||||
return ads1256_read_data();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取指定差分通道
|
||||
// ============================================================
|
||||
int32_t ads1256_read_diff(uint8_t pos, uint8_t neg)
|
||||
{
|
||||
ads1256_set_channel(pos, neg);
|
||||
usleep(1);
|
||||
|
||||
spi_write_byte(ADS1256_CMD_SYNC);
|
||||
usleep(1);
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP);
|
||||
|
||||
usleep(2000);
|
||||
|
||||
return ads1256_read_data();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续扫描所有 8 个单端通道
|
||||
// ============================================================
|
||||
void ads1256_scan_all(void)
|
||||
{
|
||||
printf("\n===== 多通道扫描 =====\n");
|
||||
for (int ch = 0; ch < 8; ch++) {
|
||||
int32_t raw = ads1256_read_channel(ch);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2); // PGA=2
|
||||
printf("通道 AIN%d: raw = %7d, 电压 = %+.6f V\n", ch, raw, volts);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 关闭
|
||||
// ============================================================
|
||||
void spi_close(void)
|
||||
{
|
||||
if (spi_fd >= 0) {
|
||||
close(spi_fd);
|
||||
spi_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 毫秒延时
|
||||
// ============================================================
|
||||
void delay_ms(int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
printf("ADS1256 Linux SPI 采集示例\n");
|
||||
printf("===========================\n\n");
|
||||
|
||||
// 1. 初始化 SPI
|
||||
if (spi_init(SPI_DEVICE, SPI_SPEED, SPI_MODE, SPI_BITS) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. 初始化 ADS1256
|
||||
if (ads1256_init() < 0) {
|
||||
spi_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\n===== 单通道测试 (AIN0) =====\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int32_t raw = ads1256_read_channel(0);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2);
|
||||
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 3. 扫描所有通道
|
||||
ads1256_scan_all();
|
||||
|
||||
// 4. 差分模式示例 (AIN0 - AIN1)
|
||||
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int32_t raw = ads1256_read_diff(0, 1);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2);
|
||||
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 5. 清理
|
||||
spi_close();
|
||||
printf("\n程序结束.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
图片标注工具
|
||||
用于手动标注图像中的目标区域并保存位置坐标(百分比格式)到txt文件
|
||||
支持矩形框标注、预览、编辑和删除功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, simpledialog
|
||||
from PIL import Image, ImageTk, ImageDraw
|
||||
import cv2
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Tuple
|
||||
import threading
|
||||
|
||||
|
||||
class ImageAnnotationTool:
|
||||
"""图片标注工具 - 支持手动矩形框标注"""
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("图片标注工具")
|
||||
self.root.geometry("1200x800")
|
||||
|
||||
# 数据存储
|
||||
self.current_image_path = None
|
||||
self.current_image = None
|
||||
self.image_pil = None
|
||||
self.image_tk = None
|
||||
self.zoom_factor = 1.0
|
||||
self.image_display = None
|
||||
self.annotations = [] # 存储标注:[(x1, y1, x2, y2), ...] # 移除了label字段
|
||||
|
||||
# 标注状态
|
||||
self.drawing = False
|
||||
self.start_x = None
|
||||
self.start_y = None
|
||||
self.current_rect = None
|
||||
self.selected_annotation = -1 # 当前选中的标注索引
|
||||
self.drag_mode = False # 是否在拖拽模式
|
||||
self.drag_start = None # 拖拽起始点
|
||||
|
||||
# 缩放和平移
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
self.pan_start_x = 0
|
||||
self.pan_start_y = 0
|
||||
self.panning = False
|
||||
|
||||
# 创建UI
|
||||
self.setup_ui()
|
||||
|
||||
# 绑定事件
|
||||
self.setup_bindings()
|
||||
|
||||
# 更新状态
|
||||
self.update_status()
|
||||
|
||||
def setup_ui(self):
|
||||
"""设置用户界面"""
|
||||
# 创建主框架
|
||||
main_frame = ttk.Frame(self.root)
|
||||
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# 左侧控制面板
|
||||
control_frame = ttk.Frame(main_frame, width=250)
|
||||
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 5))
|
||||
control_frame.pack_propagate(False)
|
||||
|
||||
# 文件操作
|
||||
file_group = ttk.LabelFrame(control_frame, text="文件操作", padding=5)
|
||||
file_group.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
ttk.Button(file_group, text="打开图片", command=self.open_image).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(file_group, text="保存标注", command=self.save_annotations).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(file_group, text="加载标注", command=self.load_annotations).pack(fill=tk.X, pady=2)
|
||||
|
||||
# 标注操作
|
||||
anno_group = ttk.LabelFrame(control_frame, text="标注操作", padding=5)
|
||||
anno_group.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
ttk.Button(anno_group, text="清除当前标注", command=self.clear_current_annotation).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(anno_group, text="删除所有标注", command=self.clear_all_annotations).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(anno_group, text="删除选中标注", command=self.delete_selected_annotation).pack(fill=tk.X, pady=2)
|
||||
|
||||
# 标注列表
|
||||
list_group = ttk.LabelFrame(control_frame, text="标注列表", padding=5)
|
||||
list_group.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||
|
||||
# 创建列表框和滚动条
|
||||
list_frame = ttk.Frame(list_group)
|
||||
list_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.annotation_listbox = tk.Listbox(list_frame, height=15)
|
||||
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.annotation_listbox.yview)
|
||||
self.annotation_listbox.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.annotation_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.annotation_listbox.bind('<<ListboxSelect>>', self.on_annotation_select)
|
||||
|
||||
# 信息显示
|
||||
info_group = ttk.LabelFrame(control_frame, text="信息", padding=5)
|
||||
info_group.pack(fill=tk.X)
|
||||
|
||||
self.info_label = ttk.Label(info_group, text="未加载图片", wraplength=220)
|
||||
self.info_label.pack(fill=tk.X, pady=2)
|
||||
|
||||
self.status_label = ttk.Label(info_group, text="状态: 就绪", wraplength=220)
|
||||
self.status_label.pack(fill=tk.X, pady=2)
|
||||
|
||||
# 右侧画布区域
|
||||
canvas_frame = ttk.Frame(main_frame)
|
||||
canvas_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# 工具栏
|
||||
toolbar = ttk.Frame(canvas_frame)
|
||||
toolbar.pack(fill=tk.X, pady=(0, 5))
|
||||
|
||||
ttk.Button(toolbar, text="放大", command=self.zoom_in).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="缩小", command=self.zoom_out).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="适应窗口", command=self.fit_to_window).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="重置视图", command=self.reset_view).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self.draw_mode_var = tk.BooleanVar(value=True)
|
||||
ttk.Checkbutton(toolbar, text="绘制模式", variable=self.draw_mode_var).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 画布
|
||||
self.canvas = tk.Canvas(canvas_frame, bg='gray', cursor='cross')
|
||||
self.canvas.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def setup_bindings(self):
|
||||
"""设置事件绑定"""
|
||||
self.canvas.bind('<ButtonPress-1>', self.on_mouse_down)
|
||||
self.canvas.bind('<B1-Motion>', self.on_mouse_move)
|
||||
self.canvas.bind('<ButtonRelease-1>', self.on_mouse_up)
|
||||
self.canvas.bind('<Control-ButtonPress-1>', self.on_pan_start)
|
||||
self.canvas.bind('<Control-B1-Motion>', self.on_pan_move)
|
||||
self.canvas.bind('<Control-ButtonRelease-1>', self.on_pan_end)
|
||||
self.root.bind('<Delete>', lambda e: self.delete_selected_annotation())
|
||||
self.root.bind('<Control-s>', lambda e: self.save_annotations())
|
||||
self.root.bind('<Control-o>', lambda e: self.open_image())
|
||||
|
||||
def open_image(self):
|
||||
"""打开图片文件"""
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="选择图片文件",
|
||||
filetypes=[
|
||||
("图片文件", "*.jpg *.jpeg *.png *.bmp *.tiff"),
|
||||
("所有文件", "*.*")
|
||||
]
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
# 加载图片
|
||||
self.current_image_path = file_path
|
||||
self.image_pil = Image.open(file_path)
|
||||
|
||||
# 重置状态
|
||||
self.annotations = []
|
||||
self.selected_annotation = -1
|
||||
self.zoom_factor = 1.0
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
|
||||
# 适应窗口
|
||||
self.fit_to_window()
|
||||
|
||||
# 更新标注列表
|
||||
self.update_annotation_list()
|
||||
|
||||
# 更新信息
|
||||
self.update_info()
|
||||
self.update_status("图片已加载")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"无法加载图片: {e}")
|
||||
|
||||
def fit_to_window(self):
|
||||
"""调整图片适应窗口"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
canvas_width = self.canvas.winfo_width()
|
||||
canvas_height = self.canvas.winfo_height()
|
||||
|
||||
if canvas_width <= 1 or canvas_height <= 1:
|
||||
canvas_width = 800
|
||||
canvas_height = 600
|
||||
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
# 计算缩放比例
|
||||
scale_x = canvas_width / img_width
|
||||
scale_y = canvas_height / img_height
|
||||
self.zoom_factor = min(scale_x, scale_y)
|
||||
|
||||
# 计算偏移量使图片居中
|
||||
scaled_width = img_width * self.zoom_factor
|
||||
scaled_height = img_height * self.zoom_factor
|
||||
self.offset_x = (canvas_width - scaled_width) / 2
|
||||
self.offset_y = (canvas_height - scaled_height) / 2
|
||||
|
||||
self.display_image()
|
||||
|
||||
def reset_view(self):
|
||||
"""重置视图"""
|
||||
self.zoom_factor = 1.0
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
self.fit_to_window()
|
||||
|
||||
def zoom_in(self):
|
||||
"""放大"""
|
||||
self.zoom_factor *= 1.2
|
||||
self.display_image()
|
||||
|
||||
def zoom_out(self):
|
||||
"""缩小"""
|
||||
self.zoom_factor /= 1.2
|
||||
self.display_image()
|
||||
|
||||
def display_image(self):
|
||||
"""显示图片"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
# 缩放图片
|
||||
img_width, img_height = self.image_pil.size
|
||||
new_width = int(img_width * self.zoom_factor)
|
||||
new_height = int(img_height * self.zoom_factor)
|
||||
|
||||
scaled_image = self.image_pil.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# 转换为PhotoImage
|
||||
self.image_tk = ImageTk.PhotoImage(scaled_image)
|
||||
|
||||
# 清除画布
|
||||
self.canvas.delete("all")
|
||||
|
||||
# 显示图片
|
||||
self.canvas.create_image(self.offset_x, self.offset_y, anchor=tk.NW, image=self.image_tk)
|
||||
|
||||
# 绘制标注
|
||||
self.draw_annotations()
|
||||
|
||||
def draw_annotations(self):
|
||||
"""绘制所有标注"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
# 转换坐标
|
||||
canvas_x1 = x1 * self.zoom_factor + self.offset_x
|
||||
canvas_y1 = y1 * self.zoom_factor + self.offset_y
|
||||
canvas_x2 = x2 * self.zoom_factor + self.offset_x
|
||||
canvas_y2 = y2 * self.zoom_factor + self.offset_y
|
||||
|
||||
# 确定颜色
|
||||
if idx == self.selected_annotation:
|
||||
color = "red"
|
||||
width = 3
|
||||
else:
|
||||
color = "blue"
|
||||
width = 2
|
||||
|
||||
# 绘制矩形
|
||||
self.canvas.create_rectangle(canvas_x1, canvas_y1, canvas_x2, canvas_y2,
|
||||
outline=color, width=width, tags=f"rect_{idx}")
|
||||
|
||||
# 绘制序号
|
||||
self.canvas.create_text(canvas_x1 + 5, canvas_y1 + 15,
|
||||
text=str(idx + 1), anchor=tk.NW,
|
||||
fill=color, font=("Arial", 10, "bold"),
|
||||
tags=f"label_{idx}")
|
||||
|
||||
def canvas_to_image_coords(self, canvas_x, canvas_y):
|
||||
"""将画布坐标转换为图片坐标"""
|
||||
img_x = (canvas_x - self.offset_x) / self.zoom_factor
|
||||
img_y = (canvas_y - self.offset_y) / self.zoom_factor
|
||||
return img_x, img_y
|
||||
|
||||
def on_mouse_down(self, event):
|
||||
"""鼠标按下事件"""
|
||||
if self.draw_mode_var.get():
|
||||
# 绘制模式
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
if 0 <= img_x <= self.image_pil.size[0] and 0 <= img_y <= self.image_pil.size[1]:
|
||||
self.drawing = True
|
||||
self.start_x = img_x
|
||||
self.start_y = img_y
|
||||
else:
|
||||
# 选择模式 - 查找最近的标注
|
||||
self.find_nearest_annotation(event.x, event.y)
|
||||
|
||||
def find_nearest_annotation(self, canvas_x, canvas_y):
|
||||
"""查找最近的标注"""
|
||||
if not self.annotations:
|
||||
return
|
||||
|
||||
min_dist = float('inf')
|
||||
nearest_idx = -1
|
||||
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
# 转换坐标
|
||||
cx1 = x1 * self.zoom_factor + self.offset_x
|
||||
cy1 = y1 * self.zoom_factor + self.offset_y
|
||||
cx2 = x2 * self.zoom_factor + self.offset_x
|
||||
cy2 = y2 * self.zoom_factor + self.offset_y
|
||||
|
||||
# 计算到矩形边界的距离
|
||||
if cx1 <= canvas_x <= cx2 and cy1 <= canvas_y <= cy2:
|
||||
# 点在矩形内
|
||||
dist = 0
|
||||
else:
|
||||
# 计算到最近边的距离
|
||||
dx = max(cx1 - canvas_x, 0, canvas_x - cx2)
|
||||
dy = max(cy1 - canvas_y, 0, canvas_y - cy2)
|
||||
dist = (dx**2 + dy**2)**0.5
|
||||
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
nearest_idx = idx
|
||||
|
||||
# 如果距离足够近,选中该标注
|
||||
if min_dist < 20: # 20像素阈值
|
||||
self.selected_annotation = nearest_idx
|
||||
self.annotation_listbox.selection_clear(0, tk.END)
|
||||
self.annotation_listbox.selection_set(nearest_idx)
|
||||
self.annotation_listbox.see(nearest_idx)
|
||||
self.display_image()
|
||||
self.update_status(f"选中标注 #{nearest_idx + 1}")
|
||||
|
||||
def on_mouse_move(self, event):
|
||||
"""鼠标移动事件"""
|
||||
if self.drawing:
|
||||
# 绘制临时矩形
|
||||
if self.current_rect:
|
||||
self.canvas.delete(self.current_rect)
|
||||
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
canvas_x1 = self.start_x * self.zoom_factor + self.offset_x
|
||||
canvas_y1 = self.start_y * self.zoom_factor + self.offset_y
|
||||
canvas_x2 = img_x * self.zoom_factor + self.offset_x
|
||||
canvas_y2 = img_y * self.zoom_factor + self.offset_y
|
||||
|
||||
self.current_rect = self.canvas.create_rectangle(
|
||||
canvas_x1, canvas_y1, canvas_x2, canvas_y2,
|
||||
outline="green", width=2, dash=(5, 5)
|
||||
)
|
||||
|
||||
def on_mouse_up(self, event):
|
||||
"""鼠标释放事件"""
|
||||
if self.drawing:
|
||||
# 完成绘制
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
|
||||
# 确保坐标正确排序
|
||||
x1 = min(self.start_x, img_x)
|
||||
y1 = min(self.start_y, img_y)
|
||||
x2 = max(self.start_x, img_x)
|
||||
y2 = max(self.start_y, img_y)
|
||||
|
||||
# 检查矩形大小
|
||||
if x2 - x1 > 5 and y2 - y1 > 5:
|
||||
# 添加标注(只保存坐标,不保存标签)
|
||||
self.annotations.append((x1, y1, x2, y2))
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status(f"已添加标注 #{len(self.annotations)}")
|
||||
else:
|
||||
self.update_status("标注太小,已忽略")
|
||||
|
||||
# 清理绘制状态
|
||||
self.drawing = False
|
||||
if self.current_rect:
|
||||
self.canvas.delete(self.current_rect)
|
||||
self.current_rect = None
|
||||
|
||||
def on_pan_start(self, event):
|
||||
"""开始平移"""
|
||||
self.panning = True
|
||||
self.pan_start_x = event.x
|
||||
self.pan_start_y = event.y
|
||||
|
||||
def on_pan_move(self, event):
|
||||
"""平移移动"""
|
||||
if self.panning:
|
||||
dx = event.x - self.pan_start_x
|
||||
dy = event.y - self.pan_start_y
|
||||
self.offset_x += dx
|
||||
self.offset_y += dy
|
||||
self.pan_start_x = event.x
|
||||
self.pan_start_y = event.y
|
||||
self.display_image()
|
||||
|
||||
def on_pan_end(self, event):
|
||||
"""结束平移"""
|
||||
self.panning = False
|
||||
|
||||
def update_annotation_list(self):
|
||||
"""更新标注列表"""
|
||||
self.annotation_listbox.delete(0, tk.END)
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
width = x2 - x1
|
||||
height = y2 - y1
|
||||
list_text = f"{idx + 1}. 矩形 ({width:.0f}x{height:.0f})"
|
||||
self.annotation_listbox.insert(tk.END, list_text)
|
||||
|
||||
def on_annotation_select(self, event):
|
||||
"""标注列表选择事件"""
|
||||
selection = self.annotation_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_annotation = selection[0]
|
||||
self.display_image()
|
||||
self.update_status(f"已选中标注 #{self.selected_annotation + 1}")
|
||||
|
||||
def delete_selected_annotation(self):
|
||||
"""删除选中的标注"""
|
||||
if self.selected_annotation >= 0 and self.selected_annotation < len(self.annotations):
|
||||
del self.annotations[self.selected_annotation]
|
||||
self.selected_annotation = -1
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status("已删除选中标注")
|
||||
|
||||
def clear_current_annotation(self):
|
||||
"""清除当前标注"""
|
||||
self.selected_annotation = -1
|
||||
self.display_image()
|
||||
self.update_status("已清除选中状态")
|
||||
|
||||
def clear_all_annotations(self):
|
||||
"""删除所有标注"""
|
||||
if messagebox.askyesno("确认", "确定要删除所有标注吗?"):
|
||||
self.annotations = []
|
||||
self.selected_annotation = -1
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status("已删除所有标注")
|
||||
|
||||
def save_annotations(self):
|
||||
"""保存标注到文件(仅TXT格式,纯坐标,格式:top right bottom left)"""
|
||||
if not self.current_image_path:
|
||||
messagebox.showwarning("警告", "请先加载图片")
|
||||
return
|
||||
|
||||
if not self.annotations:
|
||||
if not messagebox.askyesno("确认", "没有标注要保存,是否继续?"):
|
||||
return
|
||||
|
||||
try:
|
||||
# 获取图像尺寸
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
# 生成保存路径:图片文件名.txt(同目录下)
|
||||
image_dir = os.path.dirname(self.current_image_path)
|
||||
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
|
||||
file_path = os.path.join(image_dir, f"{image_basename}.txt")
|
||||
|
||||
# 保存标注(纯坐标格式,top right bottom left)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
for x1, y1, x2, y2 in self.annotations:
|
||||
# 转换为百分比坐标(top, right, bottom, left)
|
||||
top_pct = y1 / img_height
|
||||
right_pct = x2 / img_width
|
||||
bottom_pct = y2 / img_height
|
||||
left_pct = x1 / img_width
|
||||
|
||||
# 写入坐标数据(保留6位小数,空格分隔)
|
||||
f.write(f"{top_pct:.6f} {right_pct:.6f} {bottom_pct:.6f} {left_pct:.6f}\n")
|
||||
|
||||
self.update_status(f"已保存 {len(self.annotations)} 个标注到 {os.path.basename(file_path)}")
|
||||
messagebox.showinfo("成功", f"标注已保存到:\n{file_path}\n\n格式: top right bottom left (百分比坐标)")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"保存失败: {e}")
|
||||
|
||||
def load_annotations(self):
|
||||
"""加载标注文件(TXT格式,纯坐标,格式:top right bottom left)"""
|
||||
if not self.current_image_path:
|
||||
messagebox.showwarning("警告", "请先加载图片")
|
||||
return
|
||||
|
||||
# 默认加载同目录下的同名txt文件
|
||||
image_dir = os.path.dirname(self.current_image_path)
|
||||
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
|
||||
default_file = os.path.join(image_dir, f"{image_basename}.txt")
|
||||
|
||||
if os.path.exists(default_file):
|
||||
file_path = default_file
|
||||
else:
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="选择标注文件",
|
||||
initialdir=image_dir,
|
||||
filetypes=[
|
||||
("文本文件", "*.txt"),
|
||||
("所有文件", "*.*")
|
||||
]
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
self.load_txt_annotations(file_path)
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status(f"已加载 {len(self.annotations)} 个标注")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"加载失败: {e}")
|
||||
|
||||
def load_txt_annotations(self, file_path):
|
||||
"""加载TXT格式的标注文件(纯坐标格式,top right bottom left)"""
|
||||
self.annotations = []
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) == 4:
|
||||
try:
|
||||
top_pct = float(parts[0])
|
||||
right_pct = float(parts[1])
|
||||
bottom_pct = float(parts[2])
|
||||
left_pct = float(parts[3])
|
||||
|
||||
# 验证百分比坐标范围
|
||||
if all(0 <= v <= 1 for v in [top_pct, right_pct, bottom_pct, left_pct]):
|
||||
# 转换为绝对坐标
|
||||
x1 = left_pct * img_width
|
||||
y1 = top_pct * img_height
|
||||
x2 = right_pct * img_width
|
||||
y2 = bottom_pct * img_height
|
||||
|
||||
self.annotations.append((x1, y1, x2, y2))
|
||||
else:
|
||||
print(f"警告: 第{line_num}行坐标超出范围 [0,1]: {line}")
|
||||
except ValueError as e:
|
||||
print(f"警告: 第{line_num}行格式错误: {line}, 错误: {e}")
|
||||
else:
|
||||
print(f"警告: 第{line_num}行格式错误,需要4个坐标值: {line}")
|
||||
|
||||
def update_info(self):
|
||||
"""更新信息显示"""
|
||||
if self.current_image_path and self.image_pil:
|
||||
img_width, img_height = self.image_pil.size
|
||||
filename = os.path.basename(self.current_image_path)
|
||||
self.info_label.config(text=f"图片: {filename}\n尺寸: {img_width}x{img_height}\n标注: {len(self.annotations)}个")
|
||||
else:
|
||||
self.info_label.config(text="未加载图片")
|
||||
|
||||
def update_status(self, message=None):
|
||||
"""更新状态栏"""
|
||||
if message:
|
||||
self.status_label.config(text=f"状态: {message}")
|
||||
else:
|
||||
self.status_label.config(text="状态: 就绪")
|
||||
|
||||
def update(self):
|
||||
"""更新界面"""
|
||||
self.update_info()
|
||||
self.root.after(1000, self.update)
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
root = tk.Tk()
|
||||
app = ImageAnnotationTool(root)
|
||||
app.update()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <stdio.h>
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
int main() {
|
||||
printf("========== Lua Type Constants ==========\n");
|
||||
printf("LUA_TNONE = %d\n", LUA_TNONE);
|
||||
printf("LUA_TNIL = %d\n", LUA_TNIL);
|
||||
printf("LUA_TBOOLEAN = %d\n", LUA_TBOOLEAN);
|
||||
printf("LUA_TLIGHTUSERDATA = %d\n", LUA_TLIGHTUSERDATA);
|
||||
printf("LUA_TNUMBER = %d\n", LUA_TNUMBER);
|
||||
printf("LUA_TSTRING = %d\n", LUA_TSTRING);
|
||||
printf("LUA_TTABLE = %d\n", LUA_TTABLE);
|
||||
printf("LUA_TFUNCTION = %d\n", LUA_TFUNCTION);
|
||||
printf("LUA_TUSERDATA = %d\n", LUA_TUSERDATA);
|
||||
printf("LUA_TTHREAD = %d\n", LUA_TTHREAD);
|
||||
|
||||
printf("\n========== Testing lua_type() ==========\n");
|
||||
|
||||
// 创建 Lua 状态机
|
||||
lua_State *L = luaL_newstate();
|
||||
|
||||
if (L == NULL) {
|
||||
printf("Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 测试各种类型压栈后的 lua_type 返回值
|
||||
printf("\n1. 压入 nil:\n");
|
||||
lua_pushnil(L);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TNIL = %d)\n", lua_type(L, -1), LUA_TNIL);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n2. 压入 boolean (true):\n");
|
||||
lua_pushboolean(L, 1);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TBOOLEAN = %d)\n", lua_type(L, -1), LUA_TBOOLEAN);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n3. 压入 number:\n");
|
||||
lua_pushnumber(L, 3.14159);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TNUMBER = %d)\n", lua_type(L, -1), LUA_TNUMBER);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n4. 压入 string:\n");
|
||||
lua_pushstring(L, "hello");
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TSTRING = %d)\n", lua_type(L, -1), LUA_TSTRING);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n5. 创建并压入 table:\n");
|
||||
lua_newtable(L);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TTABLE = %d)\n", lua_type(L, -1), LUA_TTABLE);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n6. 压入函数 (C 函数):\n");
|
||||
lua_pushcfunction(L, lua_gettop);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TFUNCTION = %d)\n", lua_type(L, -1), LUA_TFUNCTION);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// 测试无效索引
|
||||
printf("\n7. 测试无效索引:\n");
|
||||
printf(" lua_type(L, 100) = %d (应为 LUA_TNONE = %d)\n", lua_type(L, 100), LUA_TNONE);
|
||||
|
||||
// 测试 lua_typename 函数
|
||||
printf("\n========== Testing lua_typename() ==========\n");
|
||||
printf("LUA_TTABLE 的类型名称: \"%s\"\n", lua_typename(L, LUA_TTABLE));
|
||||
printf("所有类型常量对应的名称:\n");
|
||||
printf(" LUA_TNONE -> \"%s\"\n", lua_typename(L, LUA_TNONE));
|
||||
printf(" LUA_TNIL -> \"%s\"\n", lua_typename(L, LUA_TNIL));
|
||||
printf(" LUA_TBOOLEAN -> \"%s\"\n", lua_typename(L, LUA_TBOOLEAN));
|
||||
printf(" LUA_TNUMBER -> \"%s\"\n", lua_typename(L, LUA_TNUMBER));
|
||||
printf(" LUA_TSTRING -> \"%s\"\n", lua_typename(L, LUA_TSTRING));
|
||||
printf(" LUA_TTABLE -> \"%s\"\n", lua_typename(L, LUA_TTABLE));
|
||||
printf(" LUA_TFUNCTION -> \"%s\"\n", lua_typename(L, LUA_TFUNCTION));
|
||||
printf(" LUA_TUSERDATA -> \"%s\"\n", lua_typename(L, LUA_TUSERDATA));
|
||||
printf(" LUA_TTHREAD -> \"%s\"\n", lua_typename(L, LUA_TTHREAD));
|
||||
|
||||
// 关闭 Lua 状态机
|
||||
lua_close(L);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
int main () {
|
||||
printf("int: %lu\n", sizeof(int));
|
||||
|
||||
printf("O_RDWR: %i\n", O_RDWR);
|
||||
|
||||
printf("pthread_t: %lu\n", sizeof(pthread_t));
|
||||
printf("pthread_mutex_t: %lu\n", sizeof(pthread_mutex_t));
|
||||
printf("pthread_mutexattr_t: %lu\n", sizeof(pthread_mutexattr_t));
|
||||
|
||||
printf("SPI_IOC_MAGIC: 0x%02X ('%c')\n", SPI_IOC_MAGIC, SPI_IOC_MAGIC);
|
||||
printf("SPI_IOC_WR_MODE: 0x%08X\n", SPI_IOC_WR_MODE);
|
||||
printf("SPI_IOC_RD_MODE: 0x%08X\n", SPI_IOC_RD_MODE);
|
||||
printf("SPI_IOC_WR_BITS_PER_WORD = 0x%08X\n", SPI_IOC_WR_BITS_PER_WORD);
|
||||
printf("SPI_IOC_RD_BITS_PER_WORD = 0x%08X\n", SPI_IOC_RD_BITS_PER_WORD);
|
||||
printf("SPI_IOC_WR_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_WR_MAX_SPEED_HZ);
|
||||
printf("SPI_IOC_RD_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_RD_MAX_SPEED_HZ);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
print("Hello from script file!")
|
||||
name = "Lua"
|
||||
print("Welcome to " .. name)
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 接收一个参数(表),返回另一个表
|
||||
function process(data)
|
||||
local name = data.name
|
||||
local age = data.age
|
||||
local city = data.city
|
||||
|
||||
-- 处理逻辑
|
||||
-- 修改:将 %d 改为 %s,因为 age 实际传入的是字符串
|
||||
local result_message = string.format("Hello %s, age %s from %s", name, age, city)
|
||||
|
||||
-- 返回字典
|
||||
return {
|
||||
result = "success",
|
||||
message = result_message,
|
||||
code = 200
|
||||
}
|
||||
end
|
||||
|
||||
-- 脚本主体:接收传入的参数,调用处理函数,返回结果
|
||||
-- 注意:传入的参数在 ... 中(可变参数)
|
||||
local input = ... -- 获取第一个参数(我们的字典)
|
||||
return process(input)
|
||||
@@ -0,0 +1,111 @@
|
||||
#prjs
|
||||
*.bin
|
||||
*.build
|
||||
*.dist
|
||||
*.onefile-build
|
||||
nuitka-crash-report.xml
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
+88
-73
@@ -1,27 +1,28 @@
|
||||
# CAPTCHA 验证码服务
|
||||
|
||||
一个轻量级的验证码图片服务,仅依赖 Python 标准库实现 HTTP 服务(无需 Flask),配合 Pillow 生成验证码图片。提供图片生成、Token 校验、健康检查等接口,适合作为后端服务的验证码组件。
|
||||
一个轻量级的图片验证码服务,基于自研 [pymsg](../pymsg/) 协议(TCP 长度前缀帧 + JSON),配合 Pillow 生成验证码图片。提供图片生成、Token 校验、健康检查三个路由,由 `gateway` 经 pymsg 调用并代理给浏览器。
|
||||
|
||||
服务由 `core`(PMC)的 `init.txt` 拉起,监听 `127.0.0.1:8009`(TCP),仅允许本机/内网调用。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- **图片验证码生成**:5 位字符(字母 + 数字),自动排除易混淆字符(`l I 1 O 0 o`)
|
||||
- **Token 机制**:每次生成图片返回唯一 Token,校验时一次性消费,防止重放
|
||||
- **过期自动清理**:Token 默认 300 秒(5 分钟)失效,每次请求时顺带清理过期项
|
||||
- **线程安全**:使用锁保护内存存储,支持并发请求
|
||||
- **恒定时间比较**:答案校验使用逐字符异或比较,避免计时攻击
|
||||
- **干扰元素**:随机字符颜色、位置偏移、干扰线与噪点,提升识别难度
|
||||
- **零框架依赖**:HTTP 层基于 `http.server`,除 Pillow 外无第三方依赖
|
||||
- **图片验证码生成**:4 位字符(字母 + 数字),自动排除易混淆字符(`l I 1 O 0 o`)
|
||||
- **Token 机制**:每次生成图片返回唯一 Token(32 位十六进制),校验时**一次性消费**,防止重放
|
||||
- **过期自动清理**:Token 默认 300 秒(5 分钟)失效;`image` 路由请求时顺带清理过期项
|
||||
- **线程安全**:使用锁(`STORE_LOCK`)保护内存存储,支持并发请求
|
||||
- **恒定时间比较**:答案校验使用逐字符异或(`diff |= ord(a) ^ ord(b)`),避免计时攻击
|
||||
- **干扰元素**:随机字符颜色、垂直位置偏移、干扰线与噪点,提升机器识别难度
|
||||
- **零框架依赖**:基于 pymsg(项目内置库),除 Pillow 外无第三方依赖
|
||||
|
||||
## 技术栈
|
||||
|
||||
- Python 3
|
||||
- [Pillow](https://python-pillow.org/)(PIL)—— 图片绘制
|
||||
- Python 标准库:`http.server`、`threading`、`secrets`、`json`、`urllib`
|
||||
- [pymsg](../pymsg/) —— TCP 长度前缀帧协议(`[4字节大端长度][UTF-8 JSON]`)
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
# 安装图片处理依赖
|
||||
pip install Pillow
|
||||
```
|
||||
|
||||
@@ -30,107 +31,121 @@ pip install Pillow
|
||||
## 运行
|
||||
|
||||
```bash
|
||||
# 方式一:通过启动脚本
|
||||
./_run.sh
|
||||
# 方式一:通过启动脚本(推荐,由 core 守护时即使用此方式)
|
||||
cd captcha && ./_run.sh
|
||||
|
||||
# 方式二:直接运行
|
||||
python3 captcha_service.py
|
||||
python3 captcha_service.py # 默认 127.0.0.1:8009
|
||||
python3 captcha_service.py --port 8009 --addr 127.0.0.1
|
||||
```
|
||||
|
||||
服务启动后监听 `http://127.0.0.1:8009`。
|
||||
## 协议与路由
|
||||
|
||||
## 配置项
|
||||
服务基于 pymsg 协议:每条消息为完整 JSON,`_route` 字段指定路由。响应格式与 db 服务一致:
|
||||
|
||||
在 `captcha_service.py` 顶部可调整以下常量:
|
||||
|
||||
| 常量 | 默认值 | 说明 |
|
||||
| ------------- | ------------ | --------------------- |
|
||||
| `CAPTCHA_LEN` | `5` | 验证码字符长度 |
|
||||
| `CAPTCHA_TTL` | `300` | Token 有效期(秒) |
|
||||
| `ADDR` | `127.0.0.1` | 监听地址 |
|
||||
| `PORT` | `8009` | 监听端口 |
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 获取验证码图片
|
||||
|
||||
```
|
||||
GET /captcha/image
|
||||
```json
|
||||
{"success": true, "data": {...}}
|
||||
{"success": false, "error": "..."}
|
||||
```
|
||||
|
||||
返回一张 PNG 验证码图片,响应头 `X-Captcha-Token` 携带本次验证的 Token,需在后续校验时一并提供。
|
||||
| _route | 说明 |
|
||||
| ------- | ------------------------------------------ |
|
||||
| `image` | 生成验证码图片(base64 PNG + Token) |
|
||||
| `verify` | 校验用户输入,一次性消费 Token |
|
||||
| `health` | 健康检查 |
|
||||
|
||||
**示例:**
|
||||
### 1. image — 生成验证码图片
|
||||
|
||||
```bash
|
||||
curl -D - http://127.0.0.1:8009/captcha/image -o captcha.png
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{"_route": "image"}
|
||||
```
|
||||
|
||||
响应头示例:
|
||||
|
||||
```
|
||||
Content-Type: image/png
|
||||
X-Captcha-Token: 3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c
|
||||
```
|
||||
|
||||
### 2. 校验验证码
|
||||
|
||||
```
|
||||
POST /captcha/verify
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体:
|
||||
**响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c",
|
||||
"answer": "AB3XY"
|
||||
"success": true,
|
||||
"data": {
|
||||
"token": "3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c",
|
||||
"image": "<base64 编码的 PNG,160×50>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
响应(校验不区分大小写,Token 校验后即失效):
|
||||
> Token 有效期 300 秒,且**一次性使用**——无论校验成功与否,第一次 `verify` 调用后即被消费。
|
||||
|
||||
### 2. verify — 校验验证码
|
||||
|
||||
**请求**:
|
||||
|
||||
```json
|
||||
{ "valid": true }
|
||||
{"_route": "verify", "token": "3f9a1c2b4d5e6f7a8b9c0d1e2f3a4b5c", "answer": "AB3XY"}
|
||||
```
|
||||
|
||||
错误情况:
|
||||
**响应**(均为 `success: true`,业务结果在 `data` 中区分):
|
||||
|
||||
| HTTP 状态码 | 响应体 | 说明 |
|
||||
| ----------- | --------------------------------------------------- | ----------------------- |
|
||||
| `400` | `{ "valid": false, "error": "invalid json" }` | 请求体非合法 JSON |
|
||||
| `400` | `{ "valid": false, "error": "token and answer required" }` | 缺少 token 或 answer |
|
||||
| `400` | `{ "valid": false, "error": "invalid or expired token" }` | Token 无效或已过期 |
|
||||
| 响应 data | 触发条件 |
|
||||
| ------------------------------------------------------------- | ------------------------------ |
|
||||
| `{ "valid": true }` | Token 有效且答案正确 |
|
||||
| `{ "valid": false }` | Token 有效但**答案错误**(Token 已消费) |
|
||||
| `{ "valid": false, "error": "token and answer required" }` | 缺少 `token` 或 `answer` 字段 |
|
||||
| `{ "valid": false, "error": "invalid or expired token" }` | Token 无效、已消费或已过期 |
|
||||
|
||||
### 3. 健康检查
|
||||
> ⚠️ **注意区分**:答案错误与 Token 错误是两种不同的结果。消费方(如 gateway)应分别处理:前者提示"验证码错误",后者提示"请重新获取验证码"。
|
||||
|
||||
```
|
||||
GET /health
|
||||
### 3. health — 健康检查
|
||||
|
||||
**请求**:`{"_route": "health"}`
|
||||
**响应**:`{"success": true, "data": {"status": "ok", "service": "captcha"}}`
|
||||
|
||||
### 调用示例(Python)
|
||||
|
||||
```python
|
||||
import json, sys
|
||||
sys.path.insert(0, "../pymsg")
|
||||
from pymsg import MsgClient
|
||||
|
||||
c = MsgClient("127.0.0.1", 8009, "my-service")
|
||||
resp = json.loads(c.call(json.dumps({"_route": "image"}), timeout=5))
|
||||
token = resp["data"]["token"]
|
||||
png_b64 = resp["data"]["image"]
|
||||
```
|
||||
|
||||
```json
|
||||
{ "status": "ok", "service": "captcha" }
|
||||
```
|
||||
## 数据模型与 Token 生命周期
|
||||
|
||||
- **存储**:进程内存字典 `captcha_store`,键为 Token,值为 `{ answer: <小写答案>, created_at: <Unix 时间戳> }`。
|
||||
- **生成**:`image` 路由时写入一条记录,并返回对应 Token 与 base64 PNG。
|
||||
- **消费**:`verify` 路由时 `pop` 移除该 Token,无论答案对错,均不可再次使用。
|
||||
- **过期**:超过 `CAPTCHA_TTL`(300 秒)即视为失效。`image` 路由每次生成时顺带清理过期项;`verify` 时若 Token 不在存储中(过期、未生成或已消费),统一返回 `invalid or expired token`。
|
||||
|
||||
## 集成关系
|
||||
|
||||
- 由 `core`(`PMC`)通过 `init.txt` 中的 `bash /opt/micro_servers/captcha/_run.sh` 拉起并守护。
|
||||
- `gateway` 提供 **`GET /captcha/image`** 代理路由:经 pymsg 调本服务的 `image` 路由,将 base64 PNG 解码后以 `image/png` 响应体 + `X-Captcha-Token` 响应头返回给浏览器(`Cache-Control: no-store`)。
|
||||
- `gateway` 在 `/login` 与 `/register` 中可选地启用 CAPTCHA 校验(以 `captcha_token` + `captcha_answer` 字段传入,见 `gateway/README.md`)。
|
||||
|
||||
## 典型使用流程
|
||||
|
||||
1. 客户端调用 `GET /captcha/image`,保存返回的图片与响应头中的 `X-Captcha-Token`。
|
||||
1. 前端请求 `GET https://<host>/captcha/image`(经 waf → gateway → captcha),展示图片并保存 `X-Captcha-Token`。
|
||||
2. 用户根据图片内容输入验证码文本。
|
||||
3. 客户端调用 `POST /captcha/verify`,提交 `token` 与用户输入的 `answer`。
|
||||
4. 服务端比对答案(小写化、恒定时间比较),返回 `valid` 结果,Token 随即作废。
|
||||
3. 前端在登录/注册请求中携带 `captcha_token` + `captcha_answer`。
|
||||
4. 服务端比对答案(小写化、恒定时间比较),Token 随即作废(无论对错)。
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
captcha/
|
||||
├── captcha_service.py # 服务主程序(HTTP 服务 + 验证码生成与校验)
|
||||
├── _run.sh # 启动脚本
|
||||
├── captcha_service.py # 服务主程序(pymsg 服务 + 验证码生成与校验)
|
||||
├── _run.sh # 启动脚本(cd 后 exec python3 captcha_service.py)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 验证码存储在进程内存中,服务重启后所有 Token 失效,不适合多实例水平扩展场景(如需分布式可替换为 Redis 等外部存储)。
|
||||
- 默认仅监听 `127.0.0.1`,仅供本机或内网调用;如需对外暴露请配合反向代理并做好限流。
|
||||
- 答案校验不区分大小写,Token 一经校验即从内存中移除(无论对错)。
|
||||
- 验证码存储在进程内存中,服务重启后所有 Token 失效;**不支持多实例水平扩展**(如需分布式部署,应将 `captcha_store` 替换为 Redis 等外部存储,并相应调整过期清理逻辑)。
|
||||
- 默认仅监听 `127.0.0.1`,仅供本机或内网调用;浏览器侧图片获取统一走 `gateway` 的 `GET /captcha/image` 代理。
|
||||
- 答案校验不区分大小写;Token 一经校验即从内存中移除(一次性使用)。
|
||||
- 字体缺失时自动回退到 PIL 默认字体,可能影响验证码观感,部署前建议确认 DejaVu 字体已安装。
|
||||
- 旧版 HTTP 接口(http.server)已下线,协议与 db 服务统一为 pymsg/TCP。
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
source venv/bin/activate
|
||||
exec python3 captcha_service.py
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")" || exit
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
#nuitka --standalone smtp.py
|
||||
Loaded 100 of 133 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user