Merge pull request 'Dev qing' (#12) from dev_qing into main

Reviewed-on: qing/micro_servers#12
This commit is contained in:
qing committed 2026-07-28 12:46:26 +08:00
commit bcd49f0e81
133 files changed
+43745 -523

No files matched your search

Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+13452
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
Binary file not shown.
+1026
View File
File diff suppressed because it is too large. Load diff
+2476
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+2698
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+2809
View File
File diff suppressed because it is too large. Load diff
+2834
View File
File diff suppressed because it is too large. Load diff
+71 -40
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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
View File
@@ -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 使用条款。
+3 -3
View File
@@ -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
-10
View File
@@ -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
View File
@@ -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的,你可以在定位到报错模块时阅读相关内容。
+38
View File
@@ -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
+21
View File
@@ -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.
+60
View File
@@ -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
+134
View File
@@ -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
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
export cjStackSize=64MB
export cjHeapSize=64MB
exec cjpm run --run-args="$@"
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
export cjHeapSize=16MB
cjpm update
exec cjpm build
+39
View File
@@ -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"]
+11
View File
@@ -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
+325
View File
@@ -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
+10
View File
@@ -0,0 +1,10 @@
-- ADS1256 模数转换器 - SPI接口
function build(data)
end
function destroy(data)
end
function call(data)
end
+223
View File
@@ -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
+163
View File
@@ -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
+86
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
cjpm run --run-args="$($args -join ' ')"
+55
View File
@@ -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
@
+54
View File
@@ -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;
@
+63
View File
@@ -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;
@
+53
View File
@@ -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
+56
View File
@@ -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;
@
+26
View File
@@ -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 - - - - - - - - - -;
+54
View File
@@ -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;
@
+56
View File
@@ -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 - - - - - - - - - -;
+51
View File
@@ -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
@
+57
View File
@@ -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
@
+56
View File
@@ -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
@
+21
View File
@@ -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;
@
+57
View File
@@ -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
@
+57
View File
@@ -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
@
+57
View File
@@ -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
@
+57
View File
@@ -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
@
+29
View File
@@ -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
View File
@@ -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) }
}
+152
View File
@@ -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, &reg, 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, &reg, 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
+516
View File
@@ -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;
}
}
+60
View File
@@ -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;
};
}
+21
View File
@@ -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
+80
View File
@@ -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);
}
}
+29
View File
@@ -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
+231
View File
@@ -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
}
+63
View File
@@ -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)
}
+25
View File
@@ -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
+354
View File
@@ -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"
+12
View File
@@ -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)
+872
View File
@@ -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);
}
}
+339
View File
@@ -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) }
}
+243
View File
@@ -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))) }
}
}
+69
View File
@@ -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)
}
}
}
+30
View File
@@ -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
View File
@@ -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)
}
+138
View File
@@ -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
}
+22
View File
@@ -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
}
}
+177
View File
@@ -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()
}
}
+204
View File
@@ -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
}
}
+273
View File
@@ -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
}
}
+37
View File
@@ -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)) }
}
}
}
+535
View File
@@ -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>
//}
+56
View File
@@ -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)
}
}
+379
View File
@@ -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)
}
}
+11
View File
@@ -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())) }
//}
+136
View File
@@ -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
}
}
+69
View File
@@ -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)
}
}
+331
View File
@@ -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
//}
+164
View File
@@ -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)
// }
//
//}
+418
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+310
View File
@@ -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()
}
+517
View File
@@ -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()
}
+586
View File
@@ -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, &reg, 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;
}
+471
View File
@@ -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;
}
+590
View File
@@ -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()
+82
View File
@@ -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;
}
+21
View File
@@ -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);
}
+3
View File
@@ -0,0 +1,3 @@
print("Hello from script file!")
name = "Lua"
print("Welcome to " .. name)
+22
View File
@@ -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)
+111
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
source venv/bin/activate
exec python3 captcha_service.py
+6
View File
@@ -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