From 78ad3e6d573cd894173ea56d31a2c0178a771f99 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Tue, 28 Jul 2026 12:44:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E6=A4=8Dbot4cj?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot4cj/.gitignore | 38 + bot4cj/LICENSE | 21 + bot4cj/Makefile | 60 + bot4cj/README.md | 134 ++ bot4cj/_run.sh | 5 + bot4cj/_set.sh | 5 + bot4cj/cjpm.toml | 39 + bot4cj/compile.ps1 | 11 + bot4cj/docs/Reports/analysis_20260525_00.html | 1084 +++++++++++++ bot4cj/docs/Reports/analysis_20260525_00.md | 1406 +++++++++++++++++ bot4cj/modules/ADS1115.lua | 325 ++++ bot4cj/modules/ADS1256.lua | 10 + bot4cj/modules/PCA9685.lua | 223 +++ bot4cj/modules/SPI.lua | 163 ++ bot4cj/modules/TEST.lua | 86 + bot4cj/run.ps1 | 1 + bot4cj/scripts/jump.mt | 55 + bot4cj/scripts/left-hand.mt | 54 + bot4cj/scripts/left-turn.mt | 63 + bot4cj/scripts/left.bot | 53 + bot4cj/scripts/lie-down.mt | 56 + bot4cj/scripts/nintyd.bot | 26 + bot4cj/scripts/rght-hand.mt | 54 + bot4cj/scripts/rght.bot | 56 + bot4cj/scripts/std.mt | 51 + bot4cj/scripts/stdd.mt | 57 + bot4cj/scripts/t0.mt | 56 + bot4cj/scripts/t1.mt | 21 + bot4cj/scripts/tilt-b.mt | 57 + bot4cj/scripts/tilt-f.mt | 57 + bot4cj/scripts/tilt-l.mt | 57 + bot4cj/scripts/tilt-r.mt | 57 + bot4cj/scripts/zerod.bot | 29 + bot4cj/src/P.cj | 263 +++ bot4cj/src/cpp/I2C.c | 152 ++ bot4cj/src/cpp/Matrix.cpp | 516 ++++++ bot4cj/src/cpp/Matrix.hh | 60 + bot4cj/src/cpp/bot4cj.h | 21 + bot4cj/src/cpp/fft.cpp | 80 + bot4cj/src/cpp/fft.h | 29 + bot4cj/src/cpp/fft1.c | 231 +++ bot4cj/src/cpp/kalman.c | 63 + bot4cj/src/cpp/kalman.h | 25 + bot4cj/src/cpp/snd.cpp | 354 +++++ bot4cj/src/cuda/error.cuh | 12 + bot4cj/src/cuda/matrix.cu | 872 ++++++++++ bot4cj/src/hd/I2C.cj | 339 ++++ bot4cj/src/hd/MPU6050.cj | 243 +++ bot4cj/src/hd/PCA9685.cj | 69 + bot4cj/src/hd/Servo.cj | 30 + bot4cj/src/hd/extern/macro_ExternHardware.cj | 16 + bot4cj/src/hd/fb.cj | 138 ++ bot4cj/src/hd/snd.cj | 22 + bot4cj/src/main.cj | 177 +++ bot4cj/src/math/Adam.cj | 204 +++ bot4cj/src/math/Af.cj | 273 ++++ bot4cj/src/math/Ini.cj | 37 + bot4cj/src/math/Matrix.cj | 535 +++++++ bot4cj/src/math/Tensor.cj | 56 + bot4cj/src/math/cnn.cj | 379 +++++ bot4cj/src/math/fft.cj | 11 + bot4cj/src/math/flatten.cj | 136 ++ bot4cj/src/math/kalman.cj | 69 + bot4cj/src/math/nn.cj | 331 ++++ bot4cj/src/math/nns.cj | 164 ++ bot4cj/src/math/pooling.cj | 418 +++++ bot4cj/src/r.cj | 1071 +++++++++++++ bot4cj/src/testCNN.cj | 310 ++++ bot4cj/src/testCNN1.cj | 517 ++++++ bot4cj/test/ads1115_linux.c | 586 +++++++ bot4cj/test/ads1256_linux.c | 471 ++++++ bot4cj/test/labeled.py | 590 +++++++ bot4cj/test/lua.c | 82 + bot4cj/test/t.c | 21 + bot4cj/test/t.lua | 3 + bot4cj/test/tt.lua | 22 + 76 files changed, 14468 insertions(+) create mode 100644 bot4cj/.gitignore create mode 100644 bot4cj/LICENSE create mode 100644 bot4cj/Makefile create mode 100644 bot4cj/README.md create mode 100755 bot4cj/_run.sh create mode 100755 bot4cj/_set.sh create mode 100644 bot4cj/cjpm.toml create mode 100644 bot4cj/compile.ps1 create mode 100644 bot4cj/docs/Reports/analysis_20260525_00.html create mode 100644 bot4cj/docs/Reports/analysis_20260525_00.md create mode 100644 bot4cj/modules/ADS1115.lua create mode 100644 bot4cj/modules/ADS1256.lua create mode 100644 bot4cj/modules/PCA9685.lua create mode 100644 bot4cj/modules/SPI.lua create mode 100644 bot4cj/modules/TEST.lua create mode 100644 bot4cj/run.ps1 create mode 100644 bot4cj/scripts/jump.mt create mode 100644 bot4cj/scripts/left-hand.mt create mode 100644 bot4cj/scripts/left-turn.mt create mode 100644 bot4cj/scripts/left.bot create mode 100644 bot4cj/scripts/lie-down.mt create mode 100644 bot4cj/scripts/nintyd.bot create mode 100644 bot4cj/scripts/rght-hand.mt create mode 100644 bot4cj/scripts/rght.bot create mode 100644 bot4cj/scripts/std.mt create mode 100644 bot4cj/scripts/stdd.mt create mode 100644 bot4cj/scripts/t0.mt create mode 100644 bot4cj/scripts/t1.mt create mode 100644 bot4cj/scripts/tilt-b.mt create mode 100644 bot4cj/scripts/tilt-f.mt create mode 100644 bot4cj/scripts/tilt-l.mt create mode 100644 bot4cj/scripts/tilt-r.mt create mode 100644 bot4cj/scripts/zerod.bot create mode 100644 bot4cj/src/P.cj create mode 100644 bot4cj/src/cpp/I2C.c create mode 100644 bot4cj/src/cpp/Matrix.cpp create mode 100644 bot4cj/src/cpp/Matrix.hh create mode 100644 bot4cj/src/cpp/bot4cj.h create mode 100644 bot4cj/src/cpp/fft.cpp create mode 100644 bot4cj/src/cpp/fft.h create mode 100644 bot4cj/src/cpp/fft1.c create mode 100644 bot4cj/src/cpp/kalman.c create mode 100644 bot4cj/src/cpp/kalman.h create mode 100644 bot4cj/src/cpp/snd.cpp create mode 100644 bot4cj/src/cuda/error.cuh create mode 100644 bot4cj/src/cuda/matrix.cu create mode 100644 bot4cj/src/hd/I2C.cj create mode 100644 bot4cj/src/hd/MPU6050.cj create mode 100644 bot4cj/src/hd/PCA9685.cj create mode 100644 bot4cj/src/hd/Servo.cj create mode 100644 bot4cj/src/hd/extern/macro_ExternHardware.cj create mode 100644 bot4cj/src/hd/fb.cj create mode 100644 bot4cj/src/hd/snd.cj create mode 100644 bot4cj/src/main.cj create mode 100644 bot4cj/src/math/Adam.cj create mode 100644 bot4cj/src/math/Af.cj create mode 100644 bot4cj/src/math/Ini.cj create mode 100644 bot4cj/src/math/Matrix.cj create mode 100644 bot4cj/src/math/Tensor.cj create mode 100644 bot4cj/src/math/cnn.cj create mode 100644 bot4cj/src/math/fft.cj create mode 100644 bot4cj/src/math/flatten.cj create mode 100644 bot4cj/src/math/kalman.cj create mode 100644 bot4cj/src/math/nn.cj create mode 100644 bot4cj/src/math/nns.cj create mode 100644 bot4cj/src/math/pooling.cj create mode 100644 bot4cj/src/r.cj create mode 100644 bot4cj/src/testCNN.cj create mode 100644 bot4cj/src/testCNN1.cj create mode 100644 bot4cj/test/ads1115_linux.c create mode 100644 bot4cj/test/ads1256_linux.c create mode 100644 bot4cj/test/labeled.py create mode 100644 bot4cj/test/lua.c create mode 100644 bot4cj/test/t.c create mode 100644 bot4cj/test/t.lua create mode 100644 bot4cj/test/tt.lua diff --git a/bot4cj/.gitignore b/bot4cj/.gitignore new file mode 100644 index 0000000..1961e05 --- /dev/null +++ b/bot4cj/.gitignore @@ -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 diff --git a/bot4cj/LICENSE b/bot4cj/LICENSE new file mode 100644 index 0000000..c58cae3 --- /dev/null +++ b/bot4cj/LICENSE @@ -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. \ No newline at end of file diff --git a/bot4cj/Makefile b/bot4cj/Makefile new file mode 100644 index 0000000..538d2ba --- /dev/null +++ b/bot4cj/Makefile @@ -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 diff --git a/bot4cj/README.md b/bot4cj/README.md new file mode 100644 index 0000000..d7dc83d --- /dev/null +++ b/bot4cj/README.md @@ -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 +@ + +``` diff --git a/bot4cj/_run.sh b/bot4cj/_run.sh new file mode 100755 index 0000000..8d2e8e1 --- /dev/null +++ b/bot4cj/_run.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd "$(dirname "$0")" || exit +export cjStackSize=64MB +export cjHeapSize=64MB +exec cjpm run --run-args="$@" diff --git a/bot4cj/_set.sh b/bot4cj/_set.sh new file mode 100755 index 0000000..73a5625 --- /dev/null +++ b/bot4cj/_set.sh @@ -0,0 +1,5 @@ +#!/bin/bash +cd "$(dirname "$0")" || exit +export cjHeapSize=16MB +cjpm update +exec cjpm build diff --git a/bot4cj/cjpm.toml b/bot4cj/cjpm.toml new file mode 100644 index 0000000..ebf451e --- /dev/null +++ b/bot4cj/cjpm.toml @@ -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"] diff --git a/bot4cj/compile.ps1 b/bot4cj/compile.ps1 new file mode 100644 index 0000000..ed7bed2 --- /dev/null +++ b/bot4cj/compile.ps1 @@ -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 \ No newline at end of file diff --git a/bot4cj/docs/Reports/analysis_20260525_00.html b/bot4cj/docs/Reports/analysis_20260525_00.html new file mode 100644 index 0000000..0f37373 --- /dev/null +++ b/bot4cj/docs/Reports/analysis_20260525_00.html @@ -0,0 +1,1084 @@ + + + + + + bot4cj 项目深度分析文档 + + + + + + + + +
+ +

🤖 bot4cj 项目深度分析文档

+
本文档对 bot4cj 项目进行全面、详细的分析,涵盖项目架构、每个模块的实现细节、算法原理、数据流、缺陷与改进建议。目标读者为零基础学习者。
+ + +

1. 项目总览

+ +

1.1 项目是什么

+

bot4cj 是一个机器人硬件脚本解析器,基于仓颉(Cangjie)编程语言和 Linux I2C 接口。它的核心思想是:

+
用一种自定义的"矩阵式脚本语言"来描述机器人的硬件构成和动作序列,然后解析并驱动真实硬件执行这些动作。
+

简单类比:如果把机器人比作一个演员,那么 .mt 脚本就是剧本,bot4cj 就是导演——它读剧本、安排演员(硬件)、指挥表演(执行动作)。

+ +

1.2 项目能做什么

+
+
📝 解析矩阵式脚本
读取 .mt 文件,解析出硬件定义和动作序列
+
🔌 驱动 PCA9685
通过 I2C 控制最多 16 路舵机
+
📐 读取 MPU6050
实时获取姿态数据,带卡尔曼滤波
+
🔊 控制扬声器
以声音频率替代延时
+
🌐 HTTP 服务器
通过网络接口远程提交脚本并执行
+
🧠 神经网络
全连接前馈网络,支持反向传播训练
+
🎯 深度强化学习
DRL 对机器人动作微调(已注释)
+
👁️ CNN
卷积神经网络用于视觉识别(C++/FFI)
+
📊 FFT 频谱分析
对信号进行快速傅里叶变换
+
⚡ CUDA GPU 加速
矩阵运算和激活函数在 GPU 上执行
+
+ +

1.3 技术栈

+
+ + + + + + + + +
层级技术用途
应用层仓颉 (Cangjie)主程序、解析器、机器人结构、数学库
系统层C/C++I2C 驱动、矩阵运算、FFT、CNN、音频
加速层CUDAGPU 矩阵运算、GPU 激活函数
脚本层.mt 矩阵式脚本硬件定义和动作描述
前端层HTML + TailwindWeb 管理界面
工具层Python图片标注工具、API 测试
+
+ +
+ + +

2. 项目架构与目录结构

+
bot4cj/
+├── src/                          # 源代码根目录
+│   ├── main.cj                   # 程序入口:命令行解析、分支选择
+│   ├── P.cj                      # 解析器:MatrixObject、MatrixParser、MatrixSaver
+│   ├── r.cj                      # 机器人结构:RobotStruct、硬件变量类
+│   ├── math/                     # 数学库
+│   │   ├── Matrix.cj             # FP32 结构体 + Matrix1 矩阵类(仓颉原生)
+│   │   ├── Tensor.cj             # 张量类(多维数组)
+│   │   ├── nn.cj                 # 神经网络层 Nn1 + 训练/推理函数
+│   │   ├── nns.cj                # 多层神经网络训练/推理(旧版,已注释)
+│   │   ├── Af.cj                 # 激活函数枚举
+│   │   ├── Ini.cj                # 权重初始化:Xavier 正态/均匀
+│   │   ├── kalman.cj             # 一维卡尔曼滤波器(仓颉实现)
+│   │   └── fft.cj                # FFT 接口(已注释,改用 C 实现)
+│   ├── hd/                       # 硬件驱动
+│   │   ├── I2C.cj                # I2C 通信:直接调用 glibc + 互斥锁系统
+│   │   ├── PCA9685.cj            # PCA9685 舵机驱动板控制
+│   │   ├── MPU6050.cj            # MPU6050 陀螺仪/加速度计 + 守护线程
+│   │   ├── Servo.cj              # 舵机类:角度限制 + 偏移 + 回调
+│   │   └── snd.cj                # 扬声器类(目前仅延时占位)
+│   ├── th/                       # 线程与同步
+│   │   ├── mutex.cj              # pthread 互斥锁封装
+│   │   └── pthread.cj            # pthread 线程封装
+│   ├── cpp/                      # C/C++ 共享库源码
+│   │   ├── I2C.c                 # 旧版 I2C 驱动(使用 i2c-smbus)
+│   │   ├── Matrix.cpp + .hh      # C++ 模板矩阵类 + C 接口封装
+│   │   ├── kalman.c + .h         # 旧版卡尔曼滤波器(C 实现)
+│   │   ├── fft1.c                # 基-2 FFT 算法(纯 C)
+│   │   ├── fft.cpp + .h          # FFT 封装层(C++ → C 接口)
+│   │   ├── snd.cpp               # 音频播放(Windows WaveAPI / Linux ALSA)
+│   │   ├── bot4cj.h              # C 头文件:I2C 常量和函数声明
+│   │   └── cnn/                  # CNN 卷积神经网络
+│   ├── cuda/                     # CUDA GPU 加速
+│   └── py/                       # 图片标注工具
+├── scripts/                      # 动作脚本
+├── public/                       # Web 前端页面
+├── test/                         # API 测试
+├── Makefile                      # C/C++ 共享库构建
+├── cjpm.toml                     # 仓颉包管理配置
+└── _run.sh / _set.sh             # 运行和构建脚本
+ +
+ + +

3. 核心数据流:从脚本到硬件

+

这是理解整个项目最关键的部分。以下是数据从脚本文件到硬件执行的完整流程:

+
┌─────────────┐
+│  .mt 脚本文件  │  例如:scripts/test.mt
+└──────┬──────┘
+       │ 文件读取 (InputStream)
+       ▼
+┌─────────────┐
+│  MatrixParser │  逐字符解析,输出 MatrixObject 对象
+│  (src/P.cj)  │  每个 @name...@ 块变成一个 MatrixObject
+└──────┬──────┘
+       │ LinkedList<MatrixObject>
+       ▼
+┌─────────────┐
+│  RobotStruct  │  从 MatrixObject 列表构建硬件变量字典
+│  (src/r.cj)  │  HashMap<String, MatrixVars_Hardware>
+└──────┬──────┘
+       │
+       ├── createAliasVars()     从 @Alias 矩阵创建别名变量
+       ├── createI2CVars()       从 @I2C 矩阵创建 I2C 设备变量
+       ├── createMPU6050Bind()   从 @MPU6050_BIND 矩阵创建陀螺仪变量
+       ├── createPCA9685Vars()   从 @PCA9685 矩阵创建舵机控制板变量
+       ├── createServoVars()     从 @Servo 矩阵创建舵机变量
+       └── createSpeakerVars()   从 @Speaker 矩阵创建扬声器变量
+       │
+       ▼
+┌─────────────┐
+│  active()    │  调用所有硬件变量的 build() 方法(初始化硬件)
+└──────┬──────┘
+       │
+       ▼
+┌─────────────┐
+│  run()       │  逐行执行 @Action 矩阵中的动作
+│  run_drl_    │  每行解析出各硬件的参数,调用 play() 执行
+│  action()    │
+└──────┬──────┘
+       │
+       ▼
+┌─────────────┐
+│  deactive()  │  以倒序调用所有硬件变量的 destroy() 方法
+└─────────────┘
+ +

关键概念:MatrixVars_Hardware(硬件变量)

+

这是整个项目的核心抽象。每种硬件(I2C、PCA9685、Servo、Speaker...)都被封装为一个硬件变量,它们:

+
    +
  1. 通过闭包持有真实的硬件对象(如 I2C_Orig、PCA9685、Servo)
  2. +
  3. 通过 JsonValue 进行统一接口通信(输入 JsonValue,输出 JsonValue)
  4. +
  5. 有生命周期:build() 初始化,play() 执行,destroy() 释放
  6. +
+ +
+ + +

4. 矩阵式脚本语言(.mt)详解

+ +

4.1 语法规则

+

矩阵式脚本是一种声明式、表格型的领域特定语言(DSL),用于描述机器人的硬件构成和动作序列。

+

基本结构:

+
@MatrixName          ← 矩阵开始,@ 后跟名称
+ col1  col2  col3;   ← 第 0 行:列名声明
+ val1  val2  val3;   ← 第 1 行起:数据行
+ val4  -    val6;    ← "-" 表示沿用上一行同列的值
+@
+ + +

4.2 各矩阵对象的含义

+
+ + + + + + + + + +
矩阵名用途关键列
@Alias定义参数别名name, alias
@I2C定义 I2C 总线设备name, path
@PCA9685定义舵机控制板name, I2C, addr
@MPU6050_BIND定义陀螺仪name, I2C, addr
@Speaker定义扬声器name, channels, sample_rate, volumn, max_f, min_f
@Servo定义舵机name, max, min, offset, controller, channel
@Action定义动作序列格式为 硬件名.参数名
+
+ +

4.3 @Action 矩阵详解

+

@Action 是最核心的矩阵,它定义了机器人的动作序列:

+
@Action
+s1.A  s2.A  s3.A  ...  spk.F  spk.delay;   ← 列名:硬件变量.参数名
+  45     -    45  ...     10        200;     ← 第1行:站立姿态
+  90     -     -  ...      -         20;     ← 第2行:左后方抬起
+  ...
+@
+ +

4.4 蜘蛛机器人动作示例解读

+

以 scripts/test.mt 为例,这是一个 8 足蜘蛛机器人的行走步态:

+
@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;
+  45     -    45     -    45     -    45     -    90     90     10        200;  # 站立
+  90     -     -     -     -     -     -     -     -      -      -         20;  # 左后方抬起
+  45     -     -     -     -     -     -     -     -      -      0        500;  # 恢复站立
+   -     -    90     -     -     -    45     -     -      -      -         20;  # 右后方抬起
+  ...
+@
+ + +
+ + +

5. 解析器模块详解

+ +

5.1 MatrixObject 类

+

用途:表示解析后的一个矩阵对象,对应脚本中 @name ... @ 之间的内容。

+
MatrixObject {
+    name: String                         ← 矩阵名称
+    values: ArrayList<ArrayList<String>> ← 二维字符串矩阵
+}
+
+ + + + + + + + + + +
方法功能
hasName()是否已命名
[](row, col)像数组一样用下标访问
indexOfCol(row, key)在某行中查找列号
addRune(c)添加字符到缓冲区
submitName()提交名字
submitItem()提交元素
submitLine(count)提交一行,检查列数一致性
save(output)将矩阵对象写回文件流
+
+ +

5.2 MatrixParser 类

+

用途:将输入流逐字符解析为 MatrixObject 对象。

+

算法:有限状态机(FSM)

+
+ + + + + + +
标志含义
flag是否遇到结束的 @
nl是否处于新行起始位置
cm是否处于注释中
nm是否正在解析名称行(@ 后的第一行)
+
+ +
输入字符 c
+├── 如果在注释中 (cm=true)
+│   ├── c == '\n' → 注释结束,nl=true, cm=false
+│   └── 其他 → 忽略
+├── 如果在新行起始 (nl=true)
+│   ├── c 是空白 → 维持现状
+│   ├── c == '\n' → 空行,行号+1
+│   ├── c == '@' 且未命名 → 开始名称行,nm=true
+│   ├── c == '@' 且已命名 → 结束标志,flag=true
+│   └── 其他 → 内容字符,加入缓冲区
+├── 如果不在新行 (nl=false)
+│   ├── 在名称行且遇空白 → 提交名字
+│   ├── 遇空白/制表符 → 提交当前元素
+│   ├── c == ';' → 提交元素并提交行
+│   └── 其他 → 加入缓冲区
+ +

5.4 异常体系

+
+ + + + + + +
异常类含义
ParseExcept解析异常基类
BufOverTheLine缓冲区过长
DoesNotSync矩阵行没对齐(列数不一致)
UnknowExcept未知异常兜底
+
+ +
+ + +

6. 机器人结构模块详解

+ +

6.1 MatrixVars_Hardware 基类

+

这是整个硬件抽象的核心。

+
MatrixVars_Hardware(基类)
+├── things: (JsonValue) → JsonValue    ← 事件回调(核心!)
+├── rise: Option<() → Unit>           ← 初始化回调
+├── free: Option<() → Unit>           ← 释放回调
+├── play(input) → things(input)        ← 执行动作
+├── build() → rise()                   ← 初始化硬件
+└── destroy() → free()                 ← 释放硬件
+ +

为什么用 JsonValue 通信?不同硬件需要不同参数:

+ + +

6.2 各硬件变量类

+ +

MtVars_I2C(I2C 变量)

+

things 支持 4 种模式:

+ + +

MtVars_Servo(舵机变量)

+

Servo.setAngle() 逻辑:

+
角度 = 输入角度 + 偏移值(offset)
+如果 角度 > 最大值(max) → 角度 = max
+如果 角度 < 最小值(min) → 角度 = min
+调用回调函数设置角度
+ +

6.3 RobotStruct 类

+
RobotStruct(input)
+  ├── readMatrix(input)         解析所有矩阵对象
+  └── createHardVars()          创建硬件变量字典
+        ├── createAliasVars()
+        ├── createI2CVars()
+        ├── createMPU6050Bind()
+        ├── createPCA9685Vars()
+        ├── createServoVars()
+        └── createSpeakerVars()
+
+active()                         初始化所有硬件
+run() → run_drl_action()         执行动作
+deactive()                       倒序释放所有硬件
+ +
+ + +

7. 硬件驱动层详解

+ +

7.1 I2C 通信

+

项目直接在仓颉中通过 foreign func 声明并调用 Linux glibc 的原生函数:

+
foreign func open(path: CString, flag: Int32, ...): Int32
+foreign func close(fd: Int32): Int32
+foreign func ioctl(fd: Int32, addr: UInt64, ...): Int32
+foreign func read(fd: Int32, buf: CPointer<Unit>, count: UIntNative): IntNative
+foreign func write(fd: Int32, buf: CPointer<Unit>, count: UIntNative): IntNative
+ +

7.2 I2C 互斥锁系统

+
MAIN_LOCK (主锁)          ← 保护 LOCKS 数组的操作
+  └── LOCKS[0..19] (锁具数组)   ← 每个 I2C 设备一个锁
+        └── I2C_Lock {
+              i2c_fd: Int32      ← 绑定的 I2C 设备文件句柄
+              lock: CPointer     ← pthread_mutex_t 指针
+              is_active: Bool    ← 是否激活
+            }
+ +

7.3 PCA9685 舵机控制板

+

角度转 PWM:

+
脉宽 = 0.5 + (2.5 - 0.5) * (angle / 180.0)    ← 0°→0.5ms, 180°→2.5ms
+OFF值 = 脉宽 * 4096 / 20                        ← 20ms 周期, 12位分辨率
+setPWM(channel, ON=0, OFF=计算值)
+ +

7.4 MPU6050 陀螺仪/加速度计

+

守护线程 mpu6050_main():

+
    +
  1. 读取原始传感器数据
  2. +
  3. 对 6 个轴分别进行卡尔曼滤波
  4. +
  5. 打印滤波后的数据
  6. +
  7. 睡眠 10ms
  8. +
+ +

7.6 Spk 扬声器类

+

当前状态:仅实现了延时功能,实际发声被注释掉。C++ 实现的音频播放(src/cpp/snd.cpp)是完整的,支持 Windows WaveAPI 和 Linux ALSA。

+ +
+ + +

8. 数学库详解

+ +

8.2 Matrix1 矩阵类

+

数据布局:一维数组 Array<FP32>,行优先存储。

+
arr[row * col + col]  ← 元素访问公式
+ +
+ + + + + + + + +
运算符号含义形状要求
矩阵加法A + B逐元素相加同形状
广播加法A + s每个元素加标量-
矩阵减法A - B逐元素相减同形状
逐元素乘A * B逐元素相乘(Hadamard积)同形状
矩阵乘法A.dot(B)矩阵点积(MatMul)A.col == B.row
转置A.T()行列互换-
+
+ +

矩阵乘法算法(dot):

+
对于结果矩阵的每个元素 (i, j):
+    sum = 0
+    对于 k in 0..A.col:
+        sum += A[i, k] * B[k, j]
+    结果[i, j] = sum
+

时间复杂度:O(m × n × k)

+ +
+ + +

9. 神经网络模块详解

+ +

9.1 Nn1 全连接层

+
class Nn1 {
+    inputno: Int64          ← 输入维度
+    outputno: Int64         ← 输出维度
+    weights: Matrix1        ← 权重矩阵 (inputno, outputno)
+    bias: Matrix1           ← 偏置向量 (1, outputno)
+    learning_rate: FP32     ← 学习率
+    f: Af                   ← 激活函数
+}
+ +

forward() 前向传播: a = f(Wx + b)

+

backward() 反向传播:

+
δ = errs * f'(z)             ← 误差 × 激活函数导数
+weights_grad = x^T · δ       ← 权重梯度
+bias_grad = δ                ← 偏置梯度
+errs_up = δ · W^T            ← 传给上一层的误差
+ +

9.2 激活函数

+
+ + + + + + + +
函数公式导数用途
Sigmoidf(x) = 1/(1+e^(-x))f'(x) = f(x)(1-f(x))二分类输出层
ReLUf(x) = max(0, x)f'(x) = 1 if x>0, else 0隐藏层
Leaky_ReLUf(x) = x if x>0, else αxf'(x) = 1 if x>0, else α隐藏层
Tanhf(x) = tanh(x)f'(x) = 1 - tanh²(x)隐藏层
Softmaxf(x_i) = e^(x_i) / Σe^(x_j)雅可比矩阵多分类输出层
+
+ +

9.3 Xavier 权重初始化

+
σ = sqrt(2 / (fan_in + fan_out))      ← Xavier 正态
+limit = sqrt(6 / (fan_in + fan_out))    ← Xavier 均匀(Glorot)
+ +
+ + +

10. 卡尔曼滤波详解

+

卡尔曼滤波是一种最优估计算法,用于从含有噪声的测量数据中估计系统的真实状态。

+ +

一维卡尔曼滤波器 5 个核心公式

+
+ + + + + + + +
步骤公式含义
预测状态X_mid = A · X_last根据上一次最优估计预测当前状态
预测协方差P_mid = A · P_last + Q预测的不确定性增大(+系统噪声Q)
计算卡尔曼增益kg = P_mid / (P_mid + R)测量越可靠(R小),增益越大
更新状态X_now = X_mid + kg · (dat - X_mid)用测量值修正预测值
更新协方差P_now = (1 - kg) · P_mid估计的不确定性减小
+
+ +
+ + +

11. FFT 快速傅里叶变换详解

+

快速傅里叶变换(FFT)是一种高效计算离散傅里叶变换(DFT)的算法。将时域信号转换为频域表示。

+

DFT 公式: X[k] = Σ x[n] · e^(-j·2π·k·n/N)

+

算法:Cooley-Tukey 基-2 FFT —— 将 N 点 DFT 分解为两个 N/2 点 DFT,递归分解,时间复杂度从 O(N²) 降低到 O(N·log N)。

+ +

蝶形运算

+
对于每一级 l:
+    对于每一组 j:
+        对于每个蝶形 k:
+            t = A[jl1k] · W[ij]           ← 旋转因子乘法
+            A[jl1k] = A[jl0k] - t         ← 蝶形运算上支
+            A[jl0k] = A[jl0k] + t         ← 蝶形运算下支
+ +
+ + +

12. CNN 卷积神经网络详解

+ +

12.1 CNN 架构

+

CNN 模块完全用 C++ 实现,通过 C 接口(extern "C")暴露给仓颉调用。

+
+ + + + + + + +
层文件功能
ConvLayerconv.cpp/h2D 卷积层 + 反向传播 + 参数更新
PoolingLayerpooling.cpp/h最大/平均池化
GlobalAveragePoolingga_pooling.cpp/h全局平均池化
FlattenLayerflatten.cpp/h展平(4D → 2D)
ImageLoaderimgl.cpp/h图像加载 + 归一化
+
+ +

卷积层前向传播

+
对于每个输出通道 oc:
+    对于每个空间位置 (oh, ow):
+        sum = bias[oc]
+        对于每个输入通道 ic:
+            对于每个核位置 (kh, kw):
+                sum += weight[oc,ic,kh,kw] * input[ic,oh*stride+kh,ow*stride+kw]
+        output[oc,oh,ow] = sum
+ +
+ + +

13. CUDA GPU 加速详解

+ +

13.1 CUDA 矩阵运算

+
__global__ void _devAdd(float* d1, float* d2, float* r, int64_t n) {
+    int i = blockIdx.x * blockDim.x + threadIdx.x;  ← 计算全局线程ID
+    if (i < n) {
+        r[i] = d1[i] + d2[i];                        ← 每个线程处理一个元素
+    }
+}
+ +
+ + + + + + + +
函数功能
Cu4cj_Orig(n)在 GPU 上分配 n 个 float 的内存
Cu4cj_Zero(n)在 GPU 上分配并清零
Cu4cj_ToDev(h, n)主机 → 设备拷贝
Cu4cj_ToHost(d, h, n)设备 → 主机拷贝
Cu4cj_Free(d)释放 GPU 内存
+
+ +
+ + +

14. 线程与同步模块详解

+

直接封装 pthread 互斥锁和线程:

+
foreign func pthread_mutex_init(mutex, attr): Int32
+foreign func pthread_mutex_destroy(mutex): Int32
+foreign func pthread_mutex_lock(mutex): Int32
+foreign func pthread_mutex_unlock(mutex): Int32
+ +
public class Pth {
+    init(_main, args) → pthread_create(...)   ← 创建线程
+    join(retval) → pthread_join(...)           ← 等待线程结束
+    close() → pthread_cancel(...)              ← 取消线程
+}
+ +
+ + +

15. HTTP 服务器模块详解

+ +
+ + + + + +
路径方法功能
/api/v2/bot_runPOST接收动作脚本并执行
/GET返回前端页面
/api/v1/fsPOST文件系统操作(列表目录)
+
+ +
+ + +

16. 辅助工具详解

+ + +
+ + +

17. 构建系统详解

+
+ + + + + + + +
目标源文件输出用途
libi2c4cj.soI2C.cI2C 驱动旧版(已弃用)
libsnd4cj.sosnd.cpp音频播放WaveAPI / ALSA
libmt4cj.soMatrix.cpp / matrix.cu矩阵运算CPU 或 GPU 版本
libfft4cj.sofft1.c + fft.cppFFT快速傅里叶变换
libcnn4cj.socnn_i.cpp + ...CNN卷积神经网络
+
+ +
+ + +

18. 全局缺陷与改进建议

+ +

18.1 严重缺陷

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
编号缺陷位置详细说明严重程度
D1析构器调用时间不确定r.cj:28仓颉的 GC 不保证析构器调用时间,I2C 文件描述符可能泄漏高
D2扬声器仅延时未发声hd/snd.cj:17play() 只 sleep,C++ 实现的 play_frequency() 未被调用高
D3MPU6050 线程无法安全退出hd/MPU6050.cj:115mpu6050_main() 是无限循环,pthread_cancel 可能在不安全点中断高
D4I2C 锁系统线性扫描hd/I2C.cj:129每次加锁/解锁都遍历 20 个元素的数组,O(n) 复杂度中
D5xavierUniformInit 每个元素新建 Randommath/Ini.cj:30循环内每次 DateTime.now().nanosecond + Random(),种子可能重复中
D6Softmax 雅可比矩阵反向传播未正确使用math/Af.cj:246softmax_jacobian 返回 n×n 矩阵,但 backward 调用者期望与输入同形状高
D8tanh 导数输入错误math/Af.cj:194-206计算 1 - x*x 用的是原始输入 x,应改为 1 - y*y高
D9I2C 读寄存器返回 Int8 而非 UInt8hd/I2C.cj:81负值寄存器数据会出错中
D10MPU6050 ADDR_0 和 ADDR_1 相同hd/MPU6050.cj:14-15两个地址常量都是 0x68,ADDR_1 应为 0x69中
+
+ +

18.2 设计缺陷

+
+ + + + + + + +
编号缺陷详细说明建议
D12JsonValue 通信开销大每次硬件操作都要构造/解析 JSON对高频路径提供直接调用接口
D13硬件变量创建顺序硬编码createHardVars() 中顺序固定使用依赖图自动排序
D14无 batch 训练支持神经网络每次只训练一个样本增加 batch 维度
D15无 Adam 优化器仓颉版 Nn1 只有 SGD实现 Adam 优化器
D20无单元测试数学库、解析器等无自动化测试添加测试框架
+
+ +

18.3 安全缺陷

+
+ + + + + +
编号缺陷详细说明
D21文件系统 API 无认证/api/v1/fs 可列出任意目录内容
D22HTTP 服务器无认证/api/v2/bot_run 可执行任意脚本
D23路径遍历风险文件系统 API 接受用户输入的路径
+
+ +
+ + +

19. 学习路线图

+ +
+
+
📝 第一阶段(1-2天)
+
理解脚本语言
阅读 scripts/test.mt,理解 .mt 语法,手动写一个简单脚本
+
+
+
🔍 第二阶段(2-3天)
+
理解解析器
从 MatrixObject 开始,跟踪 MatrixParser 的状态机逻辑
+
+
+
🔌 第三阶段(3-5天)
+
理解硬件驱动
学习 I2C 通信原理,阅读 PCA9685、MPU6050 驱动代码
+
+
+
🏗️ 第四阶段(2-3天)
+
理解机器人结构
理解闭包设计,跟踪硬件变量创建和 run_drl_action() 流程
+
+
+
🧠 第五阶段(3-5天)
+
理解神经网络
学习矩阵运算、激活函数,手动计算前向/反向传播
+
+
+
🚀 第六阶段(选学)
+
理解高级功能
卡尔曼滤波、FFT 蝶形运算、CNN、CUDA GPU 编程
+
+
+ +
+ +

附录 A:仓颉语言关键语法速查

+
+ + + + + + + + + + + + + + + + + +
语法含义对应语言
package foo声明包Java package
import std.io.*导入Java import
class A <: B继承Java extends
open class允许继承Java 非 final
let / var不可变/可变Kotlin val/var
Option<T>可选类型Java Optional
Some(v) / None有值/无值Optional.of/present
if (let Some(v) <- expr)if-let 解构Swift if-let
match(expr) { case ... }模式匹配Kotlin when / Rust match
foreign funcFFI 声明Java native / Rust extern
unsafe { }不安全块Rust unsafe
CPointer<T>C 指针Rust *mut T
@CC 兼容注解使结构体内存布局兼容 C
operator func []运算符重载C++ operator[]
extend A { }扩展方法Kotlin extension
+
+ +
+ +

附录 B:术语表

+
+ + + + + + + + + + + + + + + + + + +
术语英文含义
舵机Servo一种可以精确控制角度的电机
I2CInter-Integrated Circuit一种两线制串行总线协议
PWMPulse Width Modulation脉宽调制,通过改变脉冲宽度控制输出
陀螺仪Gyroscope测量角速度的传感器
前向传播Forward Propagation神经网络从输入到输出的计算过程
反向传播Backpropagation神经网络从输出到输入的梯度计算过程
梯度下降Gradient Descent沿梯度反方向更新参数以最小化损失
学习率Learning Rate每次参数更新的步长
激活函数Activation Function引入非线性的函数(如 ReLU、Sigmoid)
卡尔曼增益Kalman Gain决定多大程度上相信测量值 vs 预测值
傅里叶变换Fourier Transform将信号从时域转换到频域
蝶形运算Butterfly OperationFFT 中的基本计算单元
卷积Convolution滑动窗口加权求和的运算
池化Pooling降采样操作(取最大值或平均值)
FFIForeign Function Interface调用其他语言函数的接口
DRLDeep Reinforcement Learning深度强化学习
+
+ +
+ + +
↑
+ + + + + \ No newline at end of file diff --git a/bot4cj/docs/Reports/analysis_20260525_00.md b/bot4cj/docs/Reports/analysis_20260525_00.md new file mode 100644 index 0000000..5c1128d --- /dev/null +++ b/bot4cj/docs/Reports/analysis_20260525_00.md @@ -0,0 +1,1406 @@ +# bot4cj 项目深度分析文档 + +> 本文档对 bot4cj 项目进行全面、详细的分析,涵盖项目架构、每个模块的实现细节、算法原理、数据流、缺陷与改进建议。目标读者为零基础学习者。 + +--- + +## 目录 + +1. [项目总览](#1-项目总览) +2. [项目架构与目录结构](#2-项目架构与目录结构) +3. [核心数据流:从脚本到硬件](#3-核心数据流从脚本到硬件) +4. [矩阵式脚本语言(.mt)详解](#4-矩阵式脚本语言mt详解) +5. [解析器模块详解](#5-解析器模块详解) +6. [机器人结构模块详解](#6-机器人结构模块详解) +7. [硬件驱动层详解](#7-硬件驱动层详解) +8. [数学库详解](#8-数学库详解) +9. [神经网络模块详解](#9-神经网络模块详解) +10. [卡尔曼滤波详解](#10-卡尔曼滤波详解) +11. [FFT 快速傅里叶变换详解](#11-fft-快速傅里叶变换详解) +12. [CNN 卷积神经网络详解](#12-cnn-卷积神经网络详解) +13. [CUDA GPU 加速详解](#13-cuda-gpu-加速详解) +14. [线程与同步模块详解](#14-线程与同步模块详解) +15. [HTTP 服务器模块详解](#15-http-服务器模块详解) +16. [辅助工具详解](#16-辅助工具详解) +17. [构建系统详解](#17-构建系统详解) +18. [全局缺陷与改进建议](#18-全局缺陷与改进建议) +19. [学习路线图](#19-学习路线图) + +--- + +## 1. 项目总览 + +### 1.1 项目是什么 + +**bot4cj** 是一个**机器人硬件脚本解析器**,基于仓颉(Cangjie)编程语言和 Linux I2C 接口。它的核心思想是: + +> 用一种自定义的"矩阵式脚本语言"来**描述机器人的硬件构成和动作序列**,然后解析并驱动真实硬件执行这些动作。 + +简单类比:如果把机器人比作一个演员,那么 `.mt` 脚本就是剧本,`bot4cj` 就是导演——它读剧本、安排演员(硬件)、指挥表演(执行动作)。 + +### 1.2 项目能做什么 + + +| 功能 | 说明 | +| ---------------------------- | ------------------------------------------- | +| 解析矩阵式脚本 | 读取`.mt` 文件,解析出硬件定义和动作序列 | +| 驱动 PCA9685 舵机控制板 | 通过 I2C 控制最多 16 路舵机 | +| 读取 MPU6050 陀螺仪/加速度计 | 实时获取姿态数据,带卡尔曼滤波 | +| 控制扬声器发声 | 以声音频率替代延时(或同时发声和延时) | +| HTTP 服务器模式 | 通过网络接口远程提交脚本并执行 | +| 神经网络推理与训练 | 全连接前馈网络,支持反向传播训练 | +| 深度强化学习微调 | 用 DRL 对机器人动作进行微调(旧版,已注释) | +| CNN 卷积神经网络 | 用于视觉识别(C++ 实现,通过 FFI 调用) | +| FFT 频谱分析 | 对信号进行快速傅里叶变换 | +| CUDA GPU 加速 | 矩阵运算和激活函数可在 GPU 上执行 | + +### 1.3 技术栈 + + +| 层级 | 技术 | 用途 | +| ------ | --------------- | ---------------------------------- | +| 应用层 | 仓颉 (Cangjie) | 主程序、解析器、机器人结构、数学库 | +| 系统层 | C/C++ | I2C 驱动、矩阵运算、FFT、CNN、音频 | +| 加速层 | CUDA | GPU 矩阵运算、GPU 激活函数 | +| 脚本层 | .mt 矩阵式脚本 | 硬件定义和动作描述 | +| 前端层 | HTML + Tailwind | Web 管理界面 | +| 工具层 | Python | 图片标注工具、API 测试 | + +--- + +## 2. 项目架构与目录结构 + +``` +bot4cj/ +├── src/ # 源代码根目录 +│ ├── main.cj # 程序入口:命令行解析、分支选择 +│ ├── P.cj # 解析器:MatrixObject、MatrixParser、MatrixSaver +│ ├── r.cj # 机器人结构:RobotStruct、硬件变量类 +│ ├── math/ # 数学库 +│ │ ├── Matrix.cj # FP32 结构体 + Matrix1 矩阵类(仓颉原生) +│ │ ├── Tensor.cj # 张量类(多维数组) +│ │ ├── nn.cj # 神经网络层 Nn1 + 训练/推理函数 +│ │ ├── nns.cj # 多层神经网络训练/推理(旧版,已注释) +│ │ ├── Af.cj # 激活函数枚举:Sigmoid/ReLU/Leaky_ReLU/Tanh/Softmax +│ │ ├── Ini.cj # 权重初始化:Xavier 正态/均匀 +│ │ ├── kalman.cj # 一维卡尔曼滤波器(仓颉实现) +│ │ └── fft.cj # FFT 接口(已注释,改用 C 实现) +│ ├── hd/ # 硬件驱动 +│ │ ├── I2C.cj # I2C 通信:直接调用 glibc + 互斥锁系统 +│ │ ├── PCA9685.cj # PCA9685 舵机驱动板控制 +│ │ ├── MPU6050.cj # MPU6050 陀螺仪/加速度计 + 守护线程 +│ │ ├── Servo.cj # 舵机类:角度限制 + 偏移 + 回调 +│ │ └── snd.cj # 扬声器类(目前仅延时占位) +│ ├── th/ # 线程与同步 +│ │ ├── mutex.cj # pthread 互斥锁封装 +│ │ └── pthread.cj # pthread 线程封装 +│ ├── cpp/ # C/C++ 共享库源码 +│ │ ├── I2C.c # 旧版 I2C 驱动(使用 i2c-smbus) +│ │ ├── Matrix.cpp + .hh # C++ 模板矩阵类 + C 接口封装 +│ │ ├── kalman.c + .h # 旧版卡尔曼滤波器(C 实现) +│ │ ├── fft1.c # 基-2 FFT 算法(纯 C) +│ │ ├── fft.cpp + .h # FFT 封装层(C++ → C 接口) +│ │ ├── snd.cpp # 音频播放(Windows WaveAPI / Linux ALSA) +│ │ ├── bot4cj.h # C 头文件:I2C 常量和函数声明 +│ │ └── cnn/ # CNN 卷积神经网络 +│ │ ├── cnn_i.cpp # CNN C 接口封装层 +│ │ ├── conv.cpp + .h # 卷积层 +│ │ ├── pooling.cpp + .h # 池化层(最大/平均) +│ │ ├── ga_pooling.cpp + .h # 全局平均池化 +│ │ ├── flatten.cpp + .h # 展平层 +│ │ └── imgl.cpp + .h # 图像加载器 +│ ├── cuda/ # CUDA GPU 加速 +│ │ ├── matrix.cu # GPU 矩阵运算 + 激活函数 +│ │ └── error.cuh # CUDA 错误检查宏 +│ └── py/ +│ └── labeled.py # 图片标注工具(Python + Tkinter) +├── scripts/ # 动作脚本 +│ ├── test.mt # 新版矩阵式脚本(蜘蛛机器人) +│ ├── std.bot # 旧版脚本(站立动作) +│ ├── test.bot # 旧版脚本(行走动作) +│ └── ... # 其他旧版脚本 +├── public/ +│ └── index.html # Web 前端页面 +├── test/ +│ └── test_api_fs.py # 文件系统 API 测试 +├── Makefile # C/C++ 共享库构建 +├── cjpm.toml # 仓颉包管理配置 +├── _run.sh / _set.sh # 运行和构建脚本 +└── cangjie.md # 仓颉语言参考文档 +``` + +--- + +## 3. 核心数据流:从脚本到硬件 + +这是理解整个项目最关键的部分。以下是数据从脚本文件到硬件执行的完整流程: + +``` +┌─────────────┐ +│ .mt 脚本文件 │ 例如:scripts/test.mt +└──────┬──────┘ + │ 文件读取 (InputStream) + ▼ +┌─────────────┐ +│ MatrixParser│ 逐字符解析,输出 MatrixObject 对象 +│ (src/P.cj) │ 每个 @name...@ 块变成一个 MatrixObject +└──────┬──────┘ + │ LinkedList + ▼ +┌─────────────┐ +│ RobotStruct │ 从 MatrixObject 列表构建硬件变量字典 +│ (src/r.cj) │ HashMap +└──────┬──────┘ + │ + ├── createAliasVars() 从 @Alias 矩阵创建别名变量 + ├── createI2CVars() 从 @I2C 矩阵创建 I2C 设备变量 + ├── createMPU6050Bind() 从 @MPU6050_BIND 矩阵创建陀螺仪变量 + ├── createPCA9685Vars() 从 @PCA9685 矩阵创建舵机控制板变量 + ├── createServoVars() 从 @Servo 矩阵创建舵机变量 + └── createSpeakerVars() 从 @Speaker 矩阵创建扬声器变量 + │ + ▼ +┌─────────────┐ +│ active() │ 调用所有硬件变量的 build() 方法(初始化硬件) +└──────┬──────┘ + │ + ▼ +┌─────────────┐ +│ run() │ 逐行执行 @Action 矩阵中的动作 +│ run_drl_ │ 每行解析出各硬件的参数,调用 play() 执行 +│ action() │ +└──────┬──────┘ + │ + ▼ +┌─────────────┐ +│ deactive() │ 以倒序调用所有硬件变量的 destroy() 方法(释放资源) +└─────────────┘ +``` + +### 关键概念:MatrixVars_Hardware(硬件变量) + +这是整个项目的核心抽象。每种硬件(I2C、PCA9685、Servo、Speaker...)都被封装为一个**硬件变量**,它们: + +1. **通过闭包持有真实的硬件对象**(如 `I2C_Orig`、`PCA9685`、`Servo`) +2. **通过 JsonValue 进行统一接口通信**(输入 JsonValue,输出 JsonValue) +3. **有生命周期**:`build()` 初始化,`play()` 执行,`destroy()` 释放 + +这种设计使得 `@Action` 矩阵中的每一列(如 `s1.A`)都能通过统一的 `play()` 接口驱动对应的硬件。 + +--- + +## 4. 矩阵式脚本语言(.mt)详解 + +### 4.1 语法规则 + +矩阵式脚本是一种**声明式、表格型**的领域特定语言(DSL),用于描述机器人的硬件构成和动作序列。 + +**基本结构:** + +``` +@MatrixName ← 矩阵开始,@ 后跟名称 + col1 col2 col3; ← 第 0 行:列名声明 + val1 val2 val3; ← 第 1 行起:数据行 + val4 - val6; ← "-" 表示沿用上一行同列的值 +@ +``` + +**注释:** 以 `#` 开头到行末 + +**占位符 `-`:** 在数据行中,`-` 表示"与上一行同一列的值相同"。这是为了减少重复书写。 + +### 4.2 各矩阵对象的含义 + + +| 矩阵名 | 用途 | 关键列 | +| --------------- | ----------------- | ------------------------------------------------- | +| `@Alias` | 定义参数别名 | name, alias | +| `@I2C` | 定义 I2C 总线设备 | name, path | +| `@PCA9685` | 定义舵机控制板 | name, I2C, addr | +| `@MPU6050_BIND` | 定义陀螺仪 | name, I2C, addr | +| `@Speaker` | 定义扬声器 | name, channels, sample_rate, volumn, max_f, min_f | +| `@Servo` | 定义舵机 | name, max, min, offset, controller, channel | +| `@Action` | 定义动作序列 | 格式为`硬件名.参数名`,如 `s1.A` | + +### 4.3 @Action 矩阵详解 + +`@Action` 是最核心的矩阵,它定义了机器人的动作序列: + +``` +@Action +s1.A s2.A s3.A ... spk.F spk.delay; ← 列名:硬件变量.参数名 + 45 - 45 ... 10 200; ← 第1行:站立姿态 + 90 - - ... - 20; ← 第2行:左后方抬起 + ... +@ +``` + +- **列名格式**:`硬件变量名.参数名`,如 `s1.A` 表示舵机 s1 的角度(Angle) +- **参数别名**:`@Alias` 中定义了 `A → angle`、`F → frequency`,所以 `s1.A` 实际传给 s1 的是 `angle` 参数 +- **执行方式**:逐行执行,每行同时设置所有硬件的参数值 + +### 4.4 蜘蛛机器人动作示例解读 + +以 `scripts/test.mt` 为例,这是一个 8 足蜘蛛机器人的行走步态: + +``` +@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; + 45 - 45 - 45 - 45 - 90 90 10 200; # 站立 + 90 - - - - - - - - - - 20; # 左后方抬起 + 45 - - - - - - - - - 0 500; # 恢复站立 + - - 90 - - - 45 - - - - 20; # 右后方抬起 + ... +@ +``` + +**解读:** + +- s1-s8 是 8 个舵机(4 条腿,每腿 2 个关节) +- s9-s10 是云台舵机 +- spk.F 是扬声器频率,spk.delay 是延时 +- 第 1 行:所有舵机设为 45°/90°,这是**站立姿态** +- 第 2 行:s1 设为 90°,其余不变,这是**左后方腿抬起** +- 频率 0 + 延时 500ms = 静默等待 500ms +- 频率 10 + 延时 200ms = 发出 10Hz 声音 200ms(同时作为延时) + +--- + +## 5. 解析器模块详解 + +### 5.1 MatrixObject 类(src/P.cj:11-97) + +**用途:** 表示解析后的一个矩阵对象,对应脚本中 `@name ... @` 之间的内容。 + +**数据结构:** + +``` +MatrixObject { + name: String ← 矩阵名称(如 "I2C"、"Servo") + values: ArrayList> ← 二维字符串矩阵 +} +``` + +**关键方法:** + + +| 方法 | 功能 | 零基础解释 | +| ---------------------- | ---------------- | ---------------------------------------------------- | +| `hasName()` | 是否已命名 | 检查这个矩阵有没有名字(@ 后面的字符串) | +| `[](row, col)` | 取元素 | 像数组一样用下标访问,如`obj[0, 1]` 取第 0 行第 1 列 | +| `indexOfCol(row, key)` | 在某行中查找列号 | 在第 row 行中找值为 key 的列,返回列号 | +| `addRune(c)` | 添加字符到缓冲区 | 一个字符一个字符地积累,最终组成一个完整的元素 | +| `submitName()` | 提交名字 | 把缓冲区里的字符拼成名字字符串 | +| `submitItem()` | 提交元素 | 把缓冲区里的字符拼成一个矩阵元素 | +| `submitLine(count)` | 提交一行 | 检查当前行的列数是否与之前一致,不一致则报错 | +| `save(output)` | 储存对象 | 将矩阵对象写回文件流 | + +**设计要点:** 矩阵的元素是 `String` 类型,不是数值。这是因为矩阵既包含数值(如 `0x40`),也包含字符串引用(如 `pca9685-0`)。数值解析延迟到使用时进行。 + +### 5.2 MatrixParser 类(src/P.cj:112-216) + +**用途:** 将输入流(文件或标准输入)逐字符解析为 `MatrixObject` 对象。 + +**算法:有限状态机(FSM)** + +解析器是一个典型的**有限状态机**,通过一组布尔标志位来跟踪当前状态: + + +| 标志 | 含义 | +| ------ | ---------------------------------- | +| `flag` | 是否遇到结束的`@` | +| `nl` | 是否处于新行起始位置 | +| `cm` | 是否处于注释中 | +| `nm` | 是否正在解析名称行(@ 后的第一行) | + +**状态转换逻辑(`_next` 方法):** + +``` +输入字符 c +├── 如果在注释中 (cm=true) +│ ├── c == '\n' → 注释结束,nl=true, cm=false +│ └── 其他 → 忽略 +├── 如果在新行起始 (nl=true) +│ ├── c 是空白 → 维持现状 +│ ├── c == '\n' → 空行,行号+1 +│ ├── c == '@' 且未命名 → 开始名称行,nm=true +│ ├── c == '@' 且已命名 → 结束标志,flag=true +│ └── 其他 → 内容字符,加入缓冲区 +├── 如果不在新行 (nl=false) +│ ├── 在名称行且遇空白 → 提交名字 +│ ├── 遇空白/制表符 → 提交当前元素 +│ ├── c == ';' → 提交元素并提交行 +│ └── 其他 → 加入缓冲区 +``` + +**循环不变式(在代码注释中声明):** + +- **初始化:** `input` 是一个动作脚本的输入流 +- **保持:** 每次循环后,`input` 剩余部分仍是未解析的脚本 +- **终止:** 当输入流为空或遇到结束 `@` 时,循环终止 + +### 5.3 MatrixSaver 类(src/P.cj:221-231) + +**用途:** 将 `LinkedList` 序列化回文本格式。 + +**格式:** + +``` +@MatrixName +elem elem elem ; +elem elem elem ; +@ + +@NextMatrix +... +@ +``` + +### 5.4 异常体系(src/P.cj:239-260) + + +| 异常类 | 含义 | +| ---------------- | ------------------------------ | +| `ParseExcept` | 解析异常基类 | +| `BufOverTheLine` | 缓冲区过长(单元素字符数超限) | +| `DoesNotSync` | 矩阵行没对齐(列数不一致) | +| `UnknowExcept` | 未知异常兜底 | + +--- + +## 6. 机器人结构模块详解 + +### 6.1 MatrixVars_Hardware 基类(src/r.cj:22-89) + +**这是整个硬件抽象的核心。** 所有硬件变量都继承自这个类。 + +**设计思想:** + +``` +MatrixVars_Hardware(基类) +├── things: (JsonValue) -> JsonValue ← 事件回调(核心!) +├── rise: Option<() -> Unit> ← 初始化回调 +├── free: Option<() -> Unit> ← 释放回调 +├── play(input) → things(input) ← 执行动作 +├── build() → rise() ← 初始化硬件 +└── destroy() → free() ← 释放硬件 +``` + +**为什么用 JsonValue 通信?** + +因为不同硬件需要不同的参数: + +- PCA9685 需要 `{"channel": 0, "angle": 90.0}` +- I2C 需要 `{"mode": "set_addr", "addr": 0x40}` +- Servo 需要 `{"angle": 180.0}` +- Speaker 需要 `{"frequency": 1000.0, "delay": 400}` + +用 JsonValue 作为统一的数据交换格式,避免为每种硬件写不同的接口。 + +**静态工具方法:** + + +| 方法 | 功能 | +| --------------------------- | -------------------------------- | +| `getMt(obj, row, col)` | 获取矩阵元素,`-` 自动沿用上一行 | +| `parseMt(obj, row, col)` | 获取并解析为数值类型 T | + +`parseMt` 的 `-` 处理逻辑: + +- 如果当前值是 `-` 且行号 > 1:递归取上一行同列的值 +- 如果当前值是 `-` 且行号 == 1:返回 0(首行的 `-` 默认为 0) + +### 6.2 各硬件变量类 + +#### MtVars_Alias(别名变量,src/r.cj:92-115) + +**用途:** 定义参数别名,如 `A → angle`、`F → frequency`。 + +**工作方式:** `play()` 时直接返回别名对应的字符串。 + +#### MtVars_I2C(I2C 变量,src/r.cj:118-185) + +**用途:** 封装 I2C 设备的打开、关闭、读写操作。 + +**工厂函数 `Factory()` 流程:** + +1. 从 `@I2C` 矩阵中读取 name 和 path +2. 创建 `I2C_Orig(path)` 对象(打开 I2C 设备文件) +3. 通过闭包将 `I2C_Orig` 保存在 `things` 回调中 +4. `things` 支持 4 种模式: + - `get_fd`:获取文件描述符 + - `set_addr`:设置从机地址 + - `set_val`:写寄存器 + - `get_val`:读寄存器 +5. `free` 回调中关闭 I2C 设备 + +#### MtVars_PCA9685(PCA9685 变量,src/r.cj:244-314) + +**用途:** 封装 PCA9685 舵机控制板。 + +**工厂函数流程:** + +1. 从 `@PCA9685` 矩阵读取 name、I2C 引用名、地址 +2. 从硬件变量字典中找到对应的 I2C 变量 +3. 通过 I2C 变量的 `play({"mode": "get_fd"})` 获取文件描述符 +4. 创建 `PCA9685(fd, addr)` 对象 +5. `things` 回调:接收 `{"channel": n, "angle": a}`,调用 `pca9685.setAngle(n, a)` +6. `rise` 回调:调用 `pca9685.setup()` 初始化 PWM 频率 + +#### MtVars_MPU6050_BIND(MPU6050 变量,src/r.cj:188-241) + +**用途:** 封装 MPU6050 陀螺仪/加速度计。 + +**工厂函数流程:** + +1. 从 `@MPU6050_BIND` 矩阵读取参数 +2. 通过 I2C 变量获取文件描述符 +3. 创建 `MPU6050(fd, addr)` 对象 +4. `rise` 回调:`mpu6050.setup()` + `mpu6050.startDaemon()`(启动采样线程) +5. `free` 回调:`mpu6050.stopDaemon()` + `mpu6050.waitDaemon()`(停止并等待线程) + +#### MtVars_Servo(舵机变量,src/r.cj:320-373) + +**用途:** 封装单个舵机,包含角度限制和偏移。 + +**工厂函数流程:** + +1. 从 `@Servo` 矩阵读取 name、max、min、offset、controller、channel +2. 从字典中找到控制器(如 pca9685-0) +3. 创建 `Servo(max, min, offset, callback_setAngle)` 对象 +4. `callback_setAngle` 闭包:将角度通过控制器的 `play()` 发送给 PCA9685 +5. `things` 回调:接收 `{"angle": a}`,调用 `servo.setAngle(a)` + +**Servo.setAngle() 的逻辑(src/hd/Servo.cj:18-24):** + +``` +角度 = 输入角度 + 偏移值(offset) +如果 角度 > 最大值(max) → 角度 = max +如果 角度 < 最小值(min) → 角度 = min +调用回调函数设置角度 +``` + +#### MtVars_Speaker(扬声器变量,src/r.cj:380-426) + +**用途:** 封装扬声器,支持频率限制。 + +**工厂函数流程:** + +1. 从 `@Speaker` 矩阵读取参数 +2. 创建 `Spk(channels, sample_rate)` 对象 +3. `things` 回调: + - 接收 `{"frequency": f, "delay": d}` + - 频率限制在 `[min_f, max_f]` 范围内 + - 调用 `spk.play(f, volume, d)` + +### 6.3 RobotStruct 类(src/r.cj:435-626) + +**用途:** 机器人的完整结构,从脚本构建并执行。 + +**核心流程:** + +``` +RobotStruct(input) + ├── readMatrix(input) 解析所有矩阵对象 + └── createHardVars() 创建硬件变量字典 + ├── createAliasVars() + ├── createI2CVars() + ├── createMPU6050Bind() + ├── createPCA9685Vars() + ├── createServoVars() + └── createSpeakerVars() + +active() 初始化所有硬件 +run() → run_drl_action() 执行动作 +deactive() 倒序释放所有硬件 +``` + +**run_drl_action() 的执行逻辑(src/r.cj:578-596):** + +```python +对于 @Action 矩阵的每一行 i (从1开始): + 创建空的 JsonObject: set + 对于每一列 j: + 解析列名 "s1.A" → 设备名="s1", 参数名="A" + 检查参数名是否是别名(如 A → angle) + 获取该列的数值(处理 "-" 占位符) + 将参数加入 set: set["s1"]["angle"] = 45.0 + + 对于 set 中的每个设备: + 调用 M[设备名].play(参数) ← 驱动硬件! +``` + +**parse_action_matrixObject() 详解(src/r.cj:599-625):** + +这个方法解析 `@Action` 矩阵的一列: + +1. 取列名(第 0 行),如 `s1.A` +2. 按 `.` 分割为设备名和参数名 +3. 检查参数名是否在 `@Alias` 中定义了别名,如有则替换 +4. 将数值加入对应设备的参数集合 + +--- + +## 7. 硬件驱动层详解 + +### 7.1 I2C 通信(src/hd/I2C.cj) + +**核心设计:直接调用 glibc 函数** + +项目没有使用自定义的 C 共享库来操作 I2C,而是直接在仓颉中通过 `foreign func` 声明并调用 Linux glibc 的原生函数: + +```cangjie +foreign func open(path: CString, flag: Int32, ...): Int32 +foreign func close(fd: Int32): Int32 +foreign func ioctl(fd: Int32, addr: UInt64, ...): Int32 +foreign func read(fd: Int32, buf: CPointer, count: UIntNative): IntNative +foreign func write(fd: Int32, buf: CPointer, count: UIntNative): IntNative +``` + +**I2C 操作流程:** + +1. `openI2cDev(path)` → 打开 `/dev/i2c-N` 设备文件 +2. `setI2cAddr(fd, addr)` → 通过 `ioctl` 设置从机地址 +3. `writeI2cReg(fd, reg, val)` → 写入寄存器:先写寄存器地址,再写值 +4. `readI2cReg(fd, reg)` → 读取寄存器:先写寄存器地址,再读值 + +### 7.2 I2C 互斥锁系统(src/hd/I2C.cj:99-243) + +**为什么需要锁?** + +I2C 总线是共享资源。当多个线程(如 MPU6050 采样线程和主线程)同时访问同一个 I2C 设备时,必须保证操作的原子性。 + +**锁系统架构:** + +``` +MAIN_LOCK (主锁) ← 保护 LOCKS 数组的操作 + └── LOCKS[0..19] (锁具数组) ← 每个 I2C 设备一个锁 + └── I2C_Lock { + i2c_fd: Int32 ← 绑定的 I2C 设备文件句柄 + lock: CPointer ← pthread_mutex_t 指针 + is_active: Bool ← 是否激活 + } +``` + +**操作流程:** + + +| 函数 | 功能 | +| --------------------- | -------------------------------------------------------------------------- | +| `I2cLock_create(fd)` | 为 I2C 设备创建锁:获取主锁 → 找空位 → 初始化互斥锁 → 释放主锁 | +| `I2cLock_destroy(fd)` | 销毁锁:获取主锁 → 找到对应锁 → 销毁互斥锁 → 如无活跃锁则释放整个锁系统 | +| `I2cLock_lock(fd)` | 加锁:获取主锁 → 找到对应锁 → 对该锁加锁 → 释放主锁 | +| `I2cLock_unlock(fd)` | 解锁:获取主锁 → 找到对应锁 → 对该锁解锁 → 释放主锁 | + +### 7.3 PCA9685 舵机控制板(src/hd/PCA9685.cj) + +**PCA9685 是什么?** 一个通过 I2C 控制的 16 通道 PWM 信号发生器,常用于驱动舵机。 + +**setup() 初始化流程:** + +1. 加锁 +2. 设置 I2C 地址 +3. 写寄存器 0x00 = 0x20(开启地址自增,退出睡眠) +4. 等待振荡器启动(5ms) +5. 计算预分频值:`prescale = 25000000 / (4096 * 50) - 1 ≈ 100`(50Hz PWM) +6. 写寄存器 0x00 = 0x10(进入睡眠模式,允许改频率) +7. 写寄存器 0xFE = prescale(设置预分频值) +8. 写寄存器 0x00 = 0x20(退出睡眠) +9. 解锁 + +**setAngle() 角度转 PWM:** + +``` +脉宽 = 0.5 + (2.5 - 0.5) * (angle / 180.0) ← 0°→0.5ms, 180°→2.5ms +OFF值 = 脉宽 * 4096 / 20 ← 20ms 周期, 12位分辨率 +setPWM(channel, ON=0, OFF=计算值) +``` + +### 7.4 MPU6050 陀螺仪/加速度计(src/hd/MPU6050.cj) + +**MPU6050 是什么?** 一个 6 轴运动跟踪设备,包含 3 轴加速度计和 3 轴陀螺仪。 + +**setup() 初始化:** + +1. 退出睡眠模式(写 PWR_MGMT_1 = 0x00) +2. 设置陀螺仪量程 ±500°/s +3. 设置加速度计量程 ±8g +4. 等待 100ms 稳定 + +**readSensor() 读取数据:** + +- 读取 6 个 16 位寄存器(加速度 X/Y/Z + 陀螺仪 X/Y/Z) +- 每个轴需要读高字节和低字节,组合为 16 位有符号整数 + +**守护线程 mpu6050_main():** + +这是一个持续运行的线程,不断采样并滤波: + +1. 读取原始传感器数据 +2. 对 6 个轴分别进行卡尔曼滤波 +3. 打印滤波后的数据 +4. 睡眠 10ms + +### 7.5 Servo 舵机类(src/hd/Servo.cj) + +**设计:** 通过回调函数实现与控制器的解耦。 + +``` +Servo { + max: Float32 ← 最大角度限制 + min: Float32 ← 最小角度限制 + offset: Float32 ← 角度偏移(校准用) + callback_setAngle ← 设置角度的回调函数 +} +``` + +`setAngle(angle)` 的处理顺序:加偏移 → 限制范围 → 调用回调 + +### 7.6 Spk 扬声器类(src/hd/snd.cj) + +**当前状态:** 仅实现了延时功能,实际发声被注释掉。 + +```cangjie +public func play(frequency, volume, durations_ms): Bool { + // 实际发声被注释 + sleep(Duration.millisecond * Int64(durations_ms)) // 仅延时 + return true +} +``` + +**C++ 实现的音频播放(src/cpp/snd.cpp)** 是完整的,支持: + +- **Windows:** 使用 WaveAPI(`waveOutOpen`/`waveOutWrite`)+ 回调通知 +- **Linux:** 使用 ALSA(`snd_pcm_open`/`snd_pcm_writei`) +- 生成正弦波数据,带 5ms 淡入淡出避免咔嗒声 +- 支持多声道、可调采样率和音量 + +--- + +## 8. 数学库详解 + +### 8.1 FP32 结构体(src/math/Matrix.cj:29-55) + +**用途:** 包装 `Float32` 为值类型,支持运算符重载。 + +```cangjie +public struct FP32 { + private var val: Float32 + // 运算符重载:+、-、*、/(支持 FP32 和 Float32 两种参数) +} +``` + +**为什么需要 FP32?** 仓颉的 `Float32` 是内置类型,不能直接重载运算符。通过包装为 `struct FP32`,可以定义 `+`、`-`、`*`、`/` 运算符,使矩阵运算代码更简洁。 + +### 8.2 Matrix1 矩阵类(src/math/Matrix.cj:73-291) + +**用途:** 仓颉原生实现的二维浮点矩阵,是神经网络的基础。 + +**数据布局:** 一维数组 `Array`,行优先存储。 + +``` + ___col___ +/ \ ++---------+ \ +| 0 1 2 3 | | row - 第一维度 +| 4 5 6 7 | | ++---------+ / + +arr[row * col + col] ← 元素访问公式 +``` + +**支持的运算:** + + +| 运算 | 符号 | 含义 | 形状要求 | +| -------- | ----------- | ------------------------ | -------------- | +| 矩阵加法 | `A + B` | 逐元素相加 | 同形状 | +| 广播加法 | `A + s` | 每个元素加标量 | - | +| 矩阵减法 | `A - B` | 逐元素相减 | 同形状 | +| 广播减法 | `A - s` | 每个元素减标量 | - | +| 逐元素乘 | `A * B` | 逐元素相乘(Hadamard积) | 同形状 | +| 广播乘法 | `A * s` | 每个元素乘标量 | - | +| 逐元素除 | `A / B` | 逐元素相除 | 同形状 | +| 广播除法 | `A / s` | 每个元素除标量 | - | +| 矩阵乘法 | `A.dot(B)` | 矩阵点积(MatMul) | A.col == B.row | +| 转置 | `A.T()` | 行列互换 | - | +| 克隆 | `A.clone()` | 深拷贝 | - | + +**矩阵乘法算法(dot):** + +这是最核心的运算,三重循环实现: + +``` +对于结果矩阵的每个元素 (i, j): + sum = 0 + 对于 k in 0..A.col: + sum += A[i, k] * B[k, j] + 结果[i, j] = sum +``` + +时间复杂度:O(m × n × k),其中 A 是 m×k,B 是 k×n。 + +### 8.3 Tensor 张量类(src/math/Tensor.cj) + +**用途:** 多维数组的泛型实现,是 Matrix1 的多维推广。 + +```cangjie +class Tensor { + data: Array ← 一维扁平存储 + dims: Array ← 维度列表(形状) + size: Int64 ← 总元素数 +} +``` + +**oneByOne() 方法:** 逐元素运算,接受另一个张量和一个二元函数,要求两个张量形状相同。 + +**当前状态:** 基础框架,功能有限,未在项目中实际使用。 + +--- + +## 9. 神经网络模块详解 + +### 9.1 Nn1 全连接层(src/math/nn.cj:20-99) + +**用途:** 神经网络的一个全连接层(Dense Layer / Fully Connected Layer)。 + +**数据结构:** + +```cangjie +class Nn1 { + inputno: Int64 ← 输入维度 + outputno: Int64 ← 输出维度 + weights: Matrix1 ← 权重矩阵 (inputno, outputno) + bias: Matrix1 ← 偏置向量 (1, outputno) + learning_rate: FP32 ← 学习率 + f: Af ← 激活函数 + + // 前向传播缓存 + prev_inputs: Option ← 本次输入 + prev_raw_inputs: Option ← 激活前的输出(z = Wx + b) + prev_outputs: Option ← 激活后的输出(a = f(z)) + + // 梯度缓存 + weights_grad: Option ← 权重梯度 + bias_grad: Option ← 偏置梯度 +} +``` + +**forward() 前向传播(src/math/nn.cj:53-61):** + +``` +z = x · weights + bias ← 矩阵乘法 + 广播加法 +a = f(z) ← 激活函数 +缓存 x, z, a +返回 a +``` + +数学公式:**a = f(Wx + b)** + +**backward() 反向传播(src/math/nn.cj:64-72):** + +``` +δ = errs * f'(z) ← 误差 × 激活函数导数(逐元素乘) +weights_grad = x^T · δ ← 权重梯度 +bias_grad = δ ← 偏置梯度 +errs_up = δ · W^T ← 传给上一层的误差 +返回 errs_up +``` + +数学推导: + +- 设损失为 L,本层输出为 a = f(z),z = Wx + b +- δ = ∂L/∂z = (∂L/∂a) · f'(z) = errs · f'(z) +- ∂L/∂W = x^T · δ +- ∂L/∂b = δ +- ∂L/∂x = δ · W^T + +**update() 权重更新(src/math/nn.cj:76-79):** + +``` +weights = weights + weights_grad * learning_rate * discount +bias = bias + bias_grad * learning_rate * discount +``` + +注意:这里是**加号**,不是减号。这意味着 `weights_grad` 已经包含了负号(因为 `errs = t - res`,即目标减预测),或者学习率本身为负值。实际上,`errs = t - res` 确实是 `(target - prediction)`,而梯度下降应该是 `weights -= lr * grad`,所以这里用加号是**正确的**——因为 `grad` 已经是 `(t - res)` 方向的,加上去就是往目标方向移动。 + +### 9.2 Af 激活函数枚举(src/math/Af.cj) + +**支持的激活函数:** + + +| 函数 | 公式 | 导数 | 用途 | +| ---------- | ---------------------------- | ------------------------- | ---------------------------- | +| None | f(x) = x | f'(x) = 1 | 恒等变换 | +| Sigmoid | f(x) = 1/(1+e^(-x)) | f'(x) = f(x)(1-f(x)) | 二分类输出层 | +| ReLU | f(x) = max(0, x) | f'(x) = 1 if x>0, else 0 | 隐藏层(解决梯度消失) | +| Leaky_ReLU | f(x) = x if x>0, else αx | f'(x) = 1 if x>0, else α | 隐藏层(解决 ReLU 死亡问题) | +| Tanh | f(x) = tanh(x) | f'(x) = 1 - tanh²(x) | 隐藏层 | +| Softmax | f(x_i) = e^(x_i) / Σe^(x_j) | 雅可比矩阵 | 多分类输出层 | + +**Softmax 实现(src/math/Af.cj:209-241):** + +使用**数值稳定**的实现方式: + +1. 找到最大值 `max_val` +2. 计算 `exp(x_i - max_val)` 避免指数溢出 +3. 除以总和归一化 + +**Softmax 雅可比矩阵(src/math/Af.cj:246-271):** + +``` +J[i][j] = y_i * (1 - y_i) 如果 i == j(对角线) +J[i][j] = -y_i * y_j 如果 i != j(非对角线) +``` + +### 9.3 Ini 权重初始化(src/math/Ini.cj) + +**Xavier 正态初始化:** + +``` +σ = sqrt(2 / (fan_in + fan_out)) +权重 ~ N(0, σ²) +``` + +**Xavier 均匀初始化(Glorot):** + +``` +limit = sqrt(6 / (fan_in + fan_out)) +权重 ~ U(-limit, limit) +``` + +**原理:** Xavier 初始化的目的是让每一层的输出方差与输入方差相同,避免梯度消失或爆炸。推导基于 Tanh 激活函数的线性近似。 + +### 9.4 训练函数 train1()(src/math/nns.cj:40-75) + +**完整训练流程:** + +``` +1. 正向传播:逐层计算 forward +2. 计算误差:errs = target - output +3. 反向传播:从最后一层到第一层,逐层计算 backward +4. 梯度更新:逐层调用 update +5. 计算 MSE 损失:loss = 0.5 * (t - y)² +``` + +**与门测试 prepare_andDoor()(src/math/nns.cj:11-21):** + +随机生成两个 0/1 输入,目标输出为同或(XNOR): + +- (0,0) → 1 +- (1,1) → 1 +- (0,1) → 0 +- (1,0) → 0 + +--- + +## 10. 卡尔曼滤波详解 + +### 10.1 什么是卡尔曼滤波 + +卡尔曼滤波是一种**最优估计算法**,用于从含有噪声的测量数据中估计系统的真实状态。 + +**直觉理解:** 想象你在开车,GPS 给出位置但有误差,车速表给出速度也有误差。卡尔曼滤波就是综合这两个不完美的信息源,给出最优的位置和速度估计。 + +### 10.2 一维卡尔曼滤波器(src/math/kalman.cj) + +**5 个核心公式:** + + +| 步骤 | 公式 | 含义 | +| -------------- | ----------------------------------- | ------------------------------------- | +| 预测状态 | X_mid = A · X_last | 根据上一次最优估计预测当前状态 | +| 预测协方差 | P_mid = A · P_last + Q | 预测的不确定性增大(+系统噪声Q) | +| 计算卡尔曼增益 | kg = P_mid / (P_mid + R) | 测量越可靠(R小),增益越大,越相信测量 | +| 更新状态 | X_now = X_mid + kg · (dat - X_mid) | 用测量值修正预测值 | +| 更新协方差 | P_now = (1 - kg) · P_mid | 估计的不确定性减小 | + +**参数含义:** + + +| 参数 | 值 | 含义 | +| ---- | --- | -------------------------------------- | +| A | 1 | 状态转移矩阵(恒等,因为假设状态不变) | +| B | 0 | 控制输入矩阵(无控制输入) | +| H | 1 | 观测矩阵(直接观测) | +| Q | 20 | 系统噪声协方差(越大越信任测量值) | +| R | 200 | 测量噪声协方差(越大越信任模型预测) | + +**在 MPU6050 中的应用:** 为加速度计和陀螺仪的每个轴各创建一个卡尔曼滤波器,持续滤波以获得平滑的姿态数据。 + +--- + +## 11. FFT 快速傅里叶变换详解 + +### 11.1 什么是 FFT + +快速傅里叶变换(FFT)是一种高效计算离散傅里叶变换(DFT)的算法。它将时域信号转换为频域表示,让你看到信号中包含哪些频率成分。 + +**DFT 公式:** + +``` +X[k] = Σ x[n] · e^(-j·2π·k·n/N) 对于 k = 0, 1, ..., N-1 +``` + +### 11.2 基-2 FFT 实现(src/cpp/fft1.c) + +**算法:Cooley-Tukey 基-2 FFT** + +**核心思想:分治法** + +- 将 N 点 DFT 分解为两个 N/2 点 DFT(偶数项和奇数项) +- 递归分解直到 1 点 DFT(直接返回) +- 时间复杂度从 O(N²) 降低到 O(N·log N) + +**关键函数:** + + +| 函数 | 功能 | +| -------------------- | ------------------------------------------ | +| `initial()` | 初始化旋转因子 W^k = e^(-j·2π·k/N) | +| `fft1(A, ifft)` | 复序列基-2 FFT(ifft=1 正变换,-1 逆变换) | +| `fft1r2(A, B, ifft)` | 双实序列 FFT(同时变换两个实序列) | +| `fft1r1(A, ifft)` | 单实序列 FFT(利用对称性优化) | +| `inverseq(i, imax)` | 比特逆序(用于蝶形运算的地址重排) | + +**蝶形运算:** + +``` +对于每一级 l: + 对于每一组 j: + 对于每个蝶形 k: + t = A[jl1k] · W[ij] ← 旋转因子乘法 + A[jl1k] = A[jl0k] - t ← 蝶形运算上支 + A[jl0k] = A[jl0k] + t ← 蝶形运算下支 +``` + +### 11.3 FFT 封装层(src/cpp/fft.cpp) + +提供 C 接口给仓颉调用: + +- `gen_wave()` → 生成 10Hz 正弦波测试数据 +- `start_fft(M)` → 对矩阵 M 执行 FFT,返回频谱幅度 + +--- + +## 12. CNN 卷积神经网络详解 + +### 12.1 CNN 架构 + +CNN 模块完全用 C++ 实现,通过 C 接口(`extern "C"`)暴露给仓颉调用。 + +**包含的层:** + + +| 层 | 文件 | 功能 | +| -------------------- | ---------------- | ------------------------------- | +| ConvLayer | conv.cpp/h | 2D 卷积层 + 反向传播 + 参数更新 | +| PoolingLayer | pooling.cpp/h | 最大/平均池化 | +| GlobalAveragePooling | ga_pooling.cpp/h | 全局平均池化 | +| FlattenLayer | flatten.cpp/h | 展平(4D → 2D) | +| DynamicFlattenLayer | flatten.cpp/h | 动态形状展平 | +| ImageLoader | imgl.cpp/h | 图像加载 + 归一化 | + +### 12.2 C 接口设计(src/cpp/cnn/cnn_i.cpp) + +所有函数遵循统一模式: + +```c +void* cnn_create_xxx_layer(...) ← 创建层 +void cnn_destroy_xxx_layer(void*) ← 销毁层 +float* cnn_xxx_forward(void*, ...) ← 前向传播 +float* cnn_xxx_backward(void*, ...) ← 反向传播 +void cnn_xxx_update_parameters(void*, lr, ...) ← 更新参数 +``` + +**内存管理:** 输出数组通过 `new float[]` 分配,调用方负责 `delete[]`。 + +**错误处理:** 使用 `thread_local` 错误缓冲区 + `cnn_get_last_error()` 查询。 + +### 12.3 卷积层 ConvLayer + +**前向传播:** + +``` +对于每个输出通道 oc: + 对于每个空间位置 (oh, ow): + sum = bias[oc] + 对于每个输入通道 ic: + 对于每个核位置 (kh, kw): + sum += weight[oc,ic,kh,kw] * input[ic,oh*stride+kh,ow*stride+kw] + output[oc,oh,ow] = sum +``` + +**支持特性:** + +- 任意 stride 和 padding +- Leaky ReLU 激活(内置,negative_slope 参数) +- SGD + Momentum 优化器 +- Adam 优化器 + +### 12.4 图像加载器 ImageLoader + +**功能:** + +- 加载单张图片或批量图片 +- 支持通道数、尺寸调整 +- 归一化:[0,1] 归一化 或 mean/std 归一化 +- 自动计算数据集统计量(mean/std) +- 从目录批量加载 + +--- + +## 13. CUDA GPU 加速详解 + +### 13.1 CUDA 矩阵运算(src/cuda/matrix.cu) + +**架构:** 两层设计 + +1. **底层 CUDA kernel**:`_devAdd`、`_devSub`、`_devMul`、`_devMatDot` 等 +2. **上层 C 接口**:`Cu4cj_Add`、`Cu4cj_Sub`、`Cu4cj_MatDot` 等 + +**内存管理:** + + +| 函数 | 功能 | +| ----------------------- | ------------------------------- | +| `Cu4cj_Orig(n)` | 在 GPU 上分配 n 个 float 的内存 | +| `Cu4cj_Zero(n)` | 在 GPU 上分配并清零 | +| `Cu4cj_ToDev(h, n)` | 主机 → 设备拷贝 | +| `Cu4cj_ToHost(d, h, n)` | 设备 → 主机拷贝 | +| `Cu4cj_Free(d)` | 释放 GPU 内存 | + +**CUDA kernel 示例(向量加法):** + +```cuda +__global__ void _devAdd(float* d1, float* d2, float* r, int64_t n) { + int i = blockIdx.x * blockDim.x + threadIdx.x; ← 计算全局线程ID + if (i < n) { + r[i] = d1[i] + d2[i]; ← 每个线程处理一个元素 + } +} +``` + +**调用方式:** + +```c +Cu4cj_Add(d1, d2, d_r, n) { + _devAdd<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n); + // ↑ grid大小 ↑ block大小 + // 每个block 256个线程,共 ceil(n/256) 个block +} +``` + +**矩阵乘法 kernel:** + +```cuda +__global__ void _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; + } +} +``` + +使用 2D 线程块 (16×16),每个线程计算结果矩阵的一个元素。 + +### 13.2 GPU 激活函数 + +所有激活函数都在 GPU 上实现: + +- `_devSigmoid` / `_devSigmoidDerivative` +- `_devReLU` / `_devReLUDerivative` +- `_devLeakyReLU` / `_devLeakyReLU_Derivative` +- `_devTanh` / `_devTanhDerivative` + +### 13.3 C++ Matrix 类(CUDA 版) + +在 `matrix.cu` 中还定义了一个 C++ `Matrix` 类,数据存储在 GPU 上,运算通过 CUDA kernel 执行。对外暴露与 CPU 版相同的 C 接口,使得仓颉代码无需修改即可切换 CPU/GPU。 + +--- + +## 14. 线程与同步模块详解 + +### 14.1 mutex 互斥锁(src/th/mutex.cj) + +直接封装 pthread 互斥锁: + +```cangjie +foreign func pthread_mutex_init(mutex, attr): Int32 +foreign func pthread_mutex_destroy(mutex): Int32 +foreign func pthread_mutex_lock(mutex): Int32 +foreign func pthread_mutex_unlock(mutex): Int32 +``` + +**关键常量:** `SIZE_PTHREAD_MUTEX_TYPE_BYTE = 48`(ARM64 上 pthread_mutex_t 的大小) + +**辅助函数:** + +- `_mutex_create()` → malloc 分配 48 字节 + 返回指针 +- `_mutex_free(p)` → free 释放 +- `_mutex_init(mutex)` → pthread_mutex_init +- `_mutex_destroy(mutex)` → pthread_mutex_destroy +- `_mutex_lock(mutex)` / `_mutex_unlock(mutex)` → 加锁/解锁 + +### 14.2 pthread 线程(src/th/pthread.cj) + +```cangjie +public class Pth { + private var pthread: UInt64 = 0 + + init(_main, args) → pthread_create(...) ← 创建线程 + join(retval) → pthread_join(...) ← 等待线程结束 + close() → pthread_cancel(...) ← 取消线程 +} +``` + +--- + +## 15. HTTP 服务器模块详解 + +### 15.1 服务器配置(src/main.cj:192-294) + +**端点:** + + +| 路径 | 方法 | 功能 | +| ----------------- | ---- | ------------------------ | +| `/api/v2/bot_run` | POST | 接收动作脚本并执行 | +| `/` | GET | 返回前端页面 | +| `/api/v1/fs` | POST | 文件系统操作(列表目录) | + +### 15.2 文件系统 API + +请求格式: + +```json +{"mode": "list", "path": "/path/to/directory"} +``` + +响应:返回目录内容的 JSON 数组,每项包含: + +- `name`:文件名 +- `type`:Directory / SymbolicLink / Regular +- `writable` / `readable`:权限 +- `size`:文件大小(仅普通文件) + +--- + +## 16. 辅助工具详解 + +### 16.1 图片标注工具(src/py/labeled.py) + +**用途:** 用于 CNN 训练数据的标注,手动在图片上画矩形框。 + +**技术栈:** Python + Tkinter + PIL + OpenCV + +**功能:** + +- 打开图片、缩放、平移 +- 绘制矩形标注框 +- 保存/加载标注(百分比坐标格式:top right bottom left) +- 删除/编辑标注 + +### 16.2 前端页面(public/index.html) + +一个科技感的展示页面,使用 Tailwind CSS,支持暗色模式、移动端适配。 + +### 16.3 API 测试(test/test_api_fs.py) + +测试文件系统 API 的 Python 脚本,支持不同路径格式。 + +--- + +## 17. 构建系统详解 + +### 17.1 Makefile + +构建 C/C++ 共享库: + + +| 目标 | 源文件 | 输出 | 用途 | +| -------------- | ---------------------- | -------- | ---------------------------- | +| `libi2c4cj.so` | I2C.c | I2C 驱动 | 旧版 I2C(已弃用) | +| `libsnd4cj.so` | snd.cpp | 音频播放 | Windows WaveAPI / Linux ALSA | +| `libmt4cj.so` | Matrix.cpp / matrix.cu | 矩阵运算 | CPU 或 GPU 版本 | +| `libfft4cj.so` | fft1.c + fft.cpp | FFT | 快速傅里叶变换 | +| `libcnn4cj.so` | cnn_i.cpp + ... | CNN | 卷积神经网络 | + +**MODE 变量:** `MODE=cpu` 使用 C++ Matrix,`MODE=cuda` 使用 CUDA Matrix。 + +### 17.2 cjpm.toml + +仓颉包管理配置: + +- 目标平台:`aarch64-unknown-linux-gnu`(树莓派 ARM64) +- 编译选项:`-l c -l pthread`(链接 C 库和 pthread) +- 不支持 Windows(因为依赖 glibc) + +### 17.3 运行脚本 + +```bash +# 构建 +_set.sh → cjpm build + +# 运行(设置大栈空间) +_run.sh → export cjStackSize=10000kb && ./target/release/bin/main $@ +``` + +--- + +## 18. 全局缺陷与改进建议 + +### 18.1 严重缺陷 + + +| 编号 | 缺陷 | 位置 | 详细说明 | 严重程度 | +| ---- | ----------------------------------------- | ------------------- | -------------------------------------------------------------------------------------- | ------------ | +| D1 | **析构器调用时间不确定** | r.cj:28 | 仓颉的 GC 不保证析构器调用时间,I2C 文件描述符可能泄漏 | 高 | +| D2 | **扬声器仅延时未发声** | hd/snd.cj:17 | `play()` 只 sleep,C++ 实现的 `play_frequency()` 未被调用 | 高 | +| D3 | **MPU6050 线程无法安全退出** | hd/MPU6050.cj:115 | `mpu6050_main()` 是无限循环,`pthread_cancel` 可能在不安全点中断 | 高 | +| D4 | **I2C 锁系统线性扫描** | hd/I2C.cj:129 | 每次加锁/解锁都遍历 20 个元素的数组,O(n) 复杂度 | 中 | +| D5 | **xavierUniformInit 每个元素新建 Random** | math/Ini.cj:30 | 循环内每次`DateTime.now().nanosecond` + `Random()`,种子可能重复 | 中 | +| D6 | **Softmax 雅可比矩阵反向传播未正确使用** | math/Af.cj:246 | `softmax_jacobian` 返回 n×n 矩阵,但 `backward` 调用者期望与输入同形状 | 高 | +| D7 | **旧版 Nn 反向传播有错误** | math/nn.cj:186 | 旧版代码注释中标注 "This is Wrong" | 中(已弃用) | +| D8 | **tanh 导数输入错误** | math/Af.cj:194-206 | `tanh_dot` 接收的是激活后的输出 y,但计算 `1 - x*x` 用的是原始输入 x,应改为 `1 - y*y` | 高 | +| D9 | **I2C 读寄存器返回 Int8 而非 UInt8** | hd/I2C.cj:81 | `readI2cReg` 返回 `Int8`,负值寄存器数据会出错 | 中 | +| D10 | **MPU6050 ADDR_0 和 ADDR_1 相同** | hd/MPU6050.cj:14-15 | 两个地址常量都是 0x68,ADDR_1 应为 0x69 | 中 | +| D11 | **矩阵列数不一致时的错误信息不清晰** | P.cj:93 | `DoesNotSync` 异常只报行号,不报期望列数和实际列数 | 低 | + +### 18.2 设计缺陷 + + +| 编号 | 缺陷 | 详细说明 | 建议 | +| ---- | -------------------------- | ------------------------------------------------------- | -------------------------- | +| D12 | **JsonValue 通信开销大** | 每次硬件操作都要构造/解析 JSON,对于高频 I2C 操作性能差 | 对高频路径提供直接调用接口 | +| D13 | **硬件变量创建顺序硬编码** | `createHardVars()` 中顺序固定,无法处理依赖关系变化 | 使用依赖图自动排序 | +| D14 | **无 batch 训练支持** | 神经网络每次只训练一个样本 | 增加 batch 维度 | +| D15 | **无 Adam 优化器** | 仓颉版 Nn1 只有 SGD | 实现 Adam 优化器 | +| D16 | **Tensor 类未使用** | Tensor 类已实现但未集成到神经网络中 | 统一 Matrix1 和 Tensor | +| D17 | **FFT 未集成** | C++ FFT 已实现但仓颉接口被注释 | 恢复 FFT 接口 | +| D18 | **CNN 未集成** | C++ CNN 已实现但仓颉端无调用代码 | 添加仓颉端 CNN 接口 | +| D19 | **DRL 代码全部注释** | 深度强化学习微调功能被注释 | 重构后重新启用 | +| D20 | **无单元测试** | 数学库、解析器等无自动化测试 | 添加测试框架 | + +### 18.3 安全缺陷 + + +| 编号 | 缺陷 | 详细说明 | +| ---- | ----------------------- | ------------------------------------------------- | +| D21 | **文件系统 API 无认证** | `/api/v1/fs` 可列出任意目录内容,无权限检查 | +| D22 | **HTTP 服务器无认证** | `/api/v2/bot_run` 可执行任意脚本,无认证 | +| D23 | **路径遍历风险** | 文件系统 API 接受用户输入的路径,可能遍历敏感目录 | + +### 18.4 代码质量问题 + + +| 编号 | 问题 | 位置 | +| ---- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| D24 | 大量注释掉的旧代码 | P.cj(300+ 行注释)、nn.cj、nns.cj、main.cj | +| D25 | `print(s)` 调试输出未移除 | P.cj:147 | +| D26 | `FIXME` 和 `TODO` 标记散布各处 | 全项目约 30+ 处 | +| D27 | C++ Matrix 的`get()` 索引方式与仓颉版不同 | Matrix.cpp:24 用`col * row + row`,Matrix.cj:91 用 `row * col + col` | +| D28 | CUDA 转置函数`Cu4cj_Transport` 分配内存大小可能错误 | matrix.cu:330 用`Cu4cj_Zero(c * r * sizeof(float))`,但 `Cu4cj_Zero` 参数是元素个数不是字节数 | +| D29 | CUDA`Cu4cj_Zero` 注释说 n=10000 时被杀死 | matrix.cu:74 可能是 GPU 内存不足 | + +--- + +## 19. 学习路线图 + +### 第一阶段:理解脚本语言(1-2天) + +1. 阅读 `scripts/test.mt`,理解 `.mt` 脚本语法 +2. 对照 `README.md` 中的语法说明 +3. 手动写一个简单的 `.mt` 脚本 + +### 第二阶段:理解解析器(2-3天) + +1. 从 `MatrixObject` 开始,理解数据结构 +2. 跟踪 `MatrixParser.parse()` 的状态机逻辑 +3. 用纸笔画出状态转换图 + +### 第三阶段:理解硬件驱动(3-5天) + +1. 学习 I2C 通信原理 +2. 阅读 `I2C.cj`,理解 `open`/`ioctl`/`read`/`write` 系统调用 +3. 阅读 `PCA9685.cj`,理解 PWM 和舵机控制 +4. 阅读 `MPU6050.cj`,理解传感器读取和卡尔曼滤波 + +### 第四阶段:理解机器人结构(2-3天) + +1. 理解 `MatrixVars_Hardware` 的闭包设计 +2. 跟踪一个完整的硬件变量创建流程(如 MtVars_Servo) +3. 跟踪 `run_drl_action()` 的执行流程 + +### 第五阶段:理解神经网络(3-5天) + +1. 学习矩阵运算(Matrix1 类) +2. 学习激活函数(Af 枚举) +3. 手动计算一次前向传播和反向传播 +4. 理解 Xavier 初始化 + +### 第六阶段:理解高级功能(选学) + +1. 卡尔曼滤波:从数学公式到代码实现 +2. FFT:理解蝶形运算 +3. CNN:理解卷积、池化、展平 +4. CUDA:理解 GPU 编程模型 + +--- + +## 附录 A:仓颉语言关键语法速查 + + +| 语法 | 含义 | 对应语言 | +| -------------------------- | ----------- | ------------------------- | +| `package foo` | 声明包 | Java package | +| `import std.io.*` | 导入 | Java import | +| `class A <: B` | 继承 | Java extends | +| `open class` | 允许继承 | Java 非 final | +| `let / var` | 不可变/可变 | Kotlin val/var | +| `Option` | 可选类型 | Java Optional | +| `Some(v) / None` | 有值/无值 | Optional.of/present | +| `if (let Some(v) <- expr)` | if-let 解构 | Swift if-let | +| `match(expr) { case ... }` | 模式匹配 | Kotlin when / Rust match | +| `foreign func` | FFI 声明 | Java native / Rust extern | +| `unsafe { }` | 不安全块 | Rust unsafe | +| `CPointer` | C 指针 | Rust *mut T | +| `@C` | C 兼容注解 | 使结构体内存布局兼容 C | +| `operator func []` | 运算符重载 | C++ operator[] | +| `mut prop` | 可变属性 | Kotlin mutable property | +| `init()` | 构造函数 | Java constructor | +| `~init()` | 终结器 | Java finalize | +| `extend A { }` | 扩展方法 | Kotlin extension | + +--- + +## 附录 B:术语表 + + +| 术语 | 英文 | 含义 | +| ---------- | --------------------------- | ------------------------------------ | +| 舵机 | Servo | 一种可以精确控制角度的电机 | +| I2C | Inter-Integrated Circuit | 一种两线制串行总线协议 | +| PWM | Pulse Width Modulation | 脉宽调制,通过改变脉冲宽度控制输出 | +| 陀螺仪 | Gyroscope | 测量角速度的传感器 | +| 加速度计 | Accelerometer | 测量加速度的传感器 | +| 前向传播 | Forward Propagation | 神经网络从输入到输出的计算过程 | +| 反向传播 | Backpropagation | 神经网络从输出到输入的梯度计算过程 | +| 梯度下降 | Gradient Descent | 沿梯度反方向更新参数以最小化损失 | +| 学习率 | Learning Rate | 每次参数更新的步长 | +| 损失函数 | Loss Function | 衡量预测与真实值差距的函数 | +| 激活函数 | Activation Function | 引入非线性的函数(如 ReLU、Sigmoid) | +| 卡尔曼增益 | Kalman Gain | 决定多大程度上相信测量值 vs 预测值 | +| 协方差 | Covariance | 衡量不确定性的量 | +| 傅里叶变换 | Fourier Transform | 将信号从时域转换到频域 | +| 蝶形运算 | Butterfly Operation | FFT 中的基本计算单元 | +| 卷积 | Convolution | 滑动窗口加权求和的运算 | +| 池化 | Pooling | 降采样操作(取最大值或平均值) | +| 展平 | Flatten | 将多维数据变为一维 | +| FFI | Foreign Function Interface | 调用其他语言函数的接口 | +| DRL | Deep Reinforcement Learning | 深度强化学习 | diff --git a/bot4cj/modules/ADS1115.lua b/bot4cj/modules/ADS1115.lua new file mode 100644 index 0000000..226ef30 --- /dev/null +++ b/bot4cj/modules/ADS1115.lua @@ -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 + diff --git a/bot4cj/modules/ADS1256.lua b/bot4cj/modules/ADS1256.lua new file mode 100644 index 0000000..036eb19 --- /dev/null +++ b/bot4cj/modules/ADS1256.lua @@ -0,0 +1,10 @@ +-- ADS1256 模数转换器 - SPI接口 + +function build(data) +end + +function destroy(data) +end + +function call(data) +end diff --git a/bot4cj/modules/PCA9685.lua b/bot4cj/modules/PCA9685.lua new file mode 100644 index 0000000..4e4cddd --- /dev/null +++ b/bot4cj/modules/PCA9685.lua @@ -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 + diff --git a/bot4cj/modules/SPI.lua b/bot4cj/modules/SPI.lua new file mode 100644 index 0000000..9f2a3e7 --- /dev/null +++ b/bot4cj/modules/SPI.lua @@ -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 diff --git a/bot4cj/modules/TEST.lua b/bot4cj/modules/TEST.lua new file mode 100644 index 0000000..3dc8abd --- /dev/null +++ b/bot4cj/modules/TEST.lua @@ -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 + diff --git a/bot4cj/run.ps1 b/bot4cj/run.ps1 new file mode 100644 index 0000000..27b46c9 --- /dev/null +++ b/bot4cj/run.ps1 @@ -0,0 +1 @@ +cjpm run --run-args="$($args -join ' ')" \ No newline at end of file diff --git a/bot4cj/scripts/jump.mt b/bot4cj/scripts/jump.mt new file mode 100644 index 0000000..409ebda --- /dev/null +++ b/bot4cj/scripts/jump.mt @@ -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 +@ diff --git a/bot4cj/scripts/left-hand.mt b/bot4cj/scripts/left-hand.mt new file mode 100644 index 0000000..2494af0 --- /dev/null +++ b/bot4cj/scripts/left-hand.mt @@ -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; +@ diff --git a/bot4cj/scripts/left-turn.mt b/bot4cj/scripts/left-turn.mt new file mode 100644 index 0000000..5b2b2dd --- /dev/null +++ b/bot4cj/scripts/left-turn.mt @@ -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; +@ diff --git a/bot4cj/scripts/left.bot b/bot4cj/scripts/left.bot new file mode 100644 index 0000000..205c659 --- /dev/null +++ b/bot4cj/scripts/left.bot @@ -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 + + diff --git a/bot4cj/scripts/lie-down.mt b/bot4cj/scripts/lie-down.mt new file mode 100644 index 0000000..6259a21 --- /dev/null +++ b/bot4cj/scripts/lie-down.mt @@ -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; +@ diff --git a/bot4cj/scripts/nintyd.bot b/bot4cj/scripts/nintyd.bot new file mode 100644 index 0000000..836ecd1 --- /dev/null +++ b/bot4cj/scripts/nintyd.bot @@ -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 - - - - - - - - - -; diff --git a/bot4cj/scripts/rght-hand.mt b/bot4cj/scripts/rght-hand.mt new file mode 100644 index 0000000..45d2a42 --- /dev/null +++ b/bot4cj/scripts/rght-hand.mt @@ -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; +@ diff --git a/bot4cj/scripts/rght.bot b/bot4cj/scripts/rght.bot new file mode 100644 index 0000000..203c9ee --- /dev/null +++ b/bot4cj/scripts/rght.bot @@ -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 - - - - - - - - - -; diff --git a/bot4cj/scripts/std.mt b/bot4cj/scripts/std.mt new file mode 100644 index 0000000..3e4f7c2 --- /dev/null +++ b/bot4cj/scripts/std.mt @@ -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 +@ diff --git a/bot4cj/scripts/stdd.mt b/bot4cj/scripts/stdd.mt new file mode 100644 index 0000000..3dd0d66 --- /dev/null +++ b/bot4cj/scripts/stdd.mt @@ -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 +@ diff --git a/bot4cj/scripts/t0.mt b/bot4cj/scripts/t0.mt new file mode 100644 index 0000000..e612ca1 --- /dev/null +++ b/bot4cj/scripts/t0.mt @@ -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 +@ diff --git a/bot4cj/scripts/t1.mt b/bot4cj/scripts/t1.mt new file mode 100644 index 0000000..0babd10 --- /dev/null +++ b/bot4cj/scripts/t1.mt @@ -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; +@ diff --git a/bot4cj/scripts/tilt-b.mt b/bot4cj/scripts/tilt-b.mt new file mode 100644 index 0000000..07bbebb --- /dev/null +++ b/bot4cj/scripts/tilt-b.mt @@ -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 +@ diff --git a/bot4cj/scripts/tilt-f.mt b/bot4cj/scripts/tilt-f.mt new file mode 100644 index 0000000..26a728c --- /dev/null +++ b/bot4cj/scripts/tilt-f.mt @@ -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 +@ diff --git a/bot4cj/scripts/tilt-l.mt b/bot4cj/scripts/tilt-l.mt new file mode 100644 index 0000000..7d0c219 --- /dev/null +++ b/bot4cj/scripts/tilt-l.mt @@ -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 +@ diff --git a/bot4cj/scripts/tilt-r.mt b/bot4cj/scripts/tilt-r.mt new file mode 100644 index 0000000..ffce973 --- /dev/null +++ b/bot4cj/scripts/tilt-r.mt @@ -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 +@ diff --git a/bot4cj/scripts/zerod.bot b/bot4cj/scripts/zerod.bot new file mode 100644 index 0000000..5a66bb7 --- /dev/null +++ b/bot4cj/scripts/zerod.bot @@ -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 - - - - - - - - - -; + diff --git a/bot4cj/src/P.cj b/bot4cj/src/P.cj new file mode 100644 index 0000000..56f4ada --- /dev/null +++ b/bot4cj/src/P.cj @@ -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 buf 字符缓冲区 ---- */ + private var grp = ArrayList() /* ---- one line 整行缓冲区 ---- */ + private var values = ArrayList>() /* ---- 原始矩阵 ---- */ + + /* -------- 是否已命名 -------- */ + 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) { + 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() + 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() + } +} + +/* ---------------- 对Nn1进行扩展 ---------------- */ +extend Nn1 { + /* -------- 转换为MatrixObject对象 -------- */ + public func toMto(): MatrixObject { + let mto = MatrixObject() + return mto + } +} + +/* -------- 矩阵式解析器 -------- + * TODO(20260514-qing): 这个类输入流数据,输出MatrixObject对象 + * 它是与其深度绑定的,我倾向于将其与MatrixObject融合 + * 即持有HashMap>> */ +class MatrixParser { + + /* ---- 静态判断Option是否有值 ---- + - 该方法用于文本文件遍历时判空 */ + private static func hasValue(co: Option): Bool { + match(co) { + case Some(_) => true + case None => false + } + } + + /* -------- 主初始化器(构造函数) -------- + - 设置目标输入流 */ + private var input: StringReader + public init(input: StringReader) { + this.input = input } + + /* -------- 切换目标输入流 -------- */ + public func reset(input: StringReader) { + this.input = input } + + /* -------- 解析器 - 循环不变式 ---------------------- + * -------- 初始化: ss是一个动作脚本的输入流 -------- + * -------- 保持: ss是动作脚本剩余部分的输入流 ------ + * -------- 终止: 当输入流为空时,循环终止 ---------- */ + private var obj = MatrixObject() + public func parse(): Option { + 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) + * - 以及一个输出流(在构造函数)。 */ +class MatrixSaver { + private let stringWriter: StringWriter + public init(output: OutputStream) { + stringWriter = StringWriter(output) } + public func save(C: LinkedList) { + 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) } +} + + diff --git a/bot4cj/src/cpp/I2C.c b/bot4cj/src/cpp/I2C.c new file mode 100644 index 0000000..5de87c6 --- /dev/null +++ b/bot4cj/src/cpp/I2C.c @@ -0,0 +1,152 @@ +/* + * i2c - 用户界面 + * + * 这里已经提供了I2C通用接口,可以将上层代码移动到仓颉下面 + */ + +#include +#include +#include +#ifdef __linux__ +#include +#include +#include /* 其中宏的取值可能具有平台特异性 */ +#include +#endif + +#include "bot4cj.h" /* 有没有可能在仓颉代码中解析C头文件取得宏 */ + + +/* + * 初始化i2c设备 + * + * 动态指定设备文件 + * + * 返回打开的文件句柄或者错误码 + */ +#ifdef __linux__ +int16_t I2C_init(const char* filepath) { + int16_t fd; + if ((fd = open(filepath, O_RDWR)) < 0) { + perror("Failed to open I2C bus"); + return BOT4CJ_I2C_OPENED_FAILED; + } + else { + return fd; + } +} +#endif +#ifdef _WIN64 +int16_t I2C_init(const char* filepath) { + return BOT4CJ_I2C_OPENED_FAILED; +} +#endif + +/* 释放I2C设备文件 */ +#ifdef __linux__ +void I2C_free(int16_t fd) { + close(fd); +} +#endif +#ifdef _WIN64 +void I2C_free(int16_t fd) { + /* Windows平台下无需操作 */ +} +#endif + + + +/* 设置I2C地址 */ +#ifdef __linux__ +int I2C_setAddr(int16_t fd, uint8_t addr) { + if (ioctl(fd, I2C_SLAVE, addr) < 0) { + perror("Failed to set I2C address"); + return BOT4CJ_I2C_ERR; + } + else { + return BOT4CJ_I2C_OK; + } +} +#endif +#ifdef _WIN64 +int I2C_setAddr(int16_t fd, uint8_t addr) { + return BOT4CJ_I2C_ERR; +} +#endif + +/* 写寄存器 */ +#ifdef __linux__ +int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) { + /*uint8_t buf[] = {reg, val}; + if (write(fd, buf, 2) != 2) { + perror("I2C write error"); + return BOT4CJ_I2C_ERR; + } + else { + return BOT4CJ_I2C_OK; + }*/ + + return i2c_smbus_write_byte_data(fd, reg, val); +} +#endif +#ifdef _WIN64 +int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) { + return BOT4CJ_I2C_ERR; +} +#endif + +/* 读寄存器 失败返回0 */ +#ifdef __linux__ +uint8_t I2C_readReg(int16_t fd, uint8_t reg) { + + uint8_t value; + + /*if (write(fd, ®, 1) != 1) { + perror("I2C read error"); + return 0; + } + + if (read(fd, &value, 1)!= 1) { + perror("I2C read error"); + return 0; + }*/ + + //return value; + + int8_t a = i2c_smbus_read_byte_data(fd, reg); + return *(uint8_t *)& a; +} +#endif +#ifdef _WIN64 +uint8_t I2C_readReg(int16_t fd, uint8_t reg) { + return 0; +} +#endif + +/* 读寄存器 - 有符号 + * 失败返回0 */ +#ifdef __linux__ +int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) { + + int8_t value; + + //if (write(fd, ®, 1) != 1) { + //perror("I2C read error"); + //return 0; + //} + + //if (read(fd, &value, 1)!= 1) { + //perror("I2C read error"); + //return 0; + //} + + //return value; + + return i2c_smbus_read_byte_data(fd, reg); +} +#endif +#ifdef _WIN64 +int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) { + return 0; +} +#endif diff --git a/bot4cj/src/cpp/Matrix.cpp b/bot4cj/src/cpp/Matrix.cpp new file mode 100644 index 0000000..cfa0da0 --- /dev/null +++ b/bot4cj/src/cpp/Matrix.cpp @@ -0,0 +1,516 @@ +#include +#include +#include "Matrix.hh" + +namespace qing { + + template + Matrx::Matrx(): row(0), col(0) {} + + template + Matrx::Matrx(const uint64_t row, const uint64_t col) + : row(row), col(col) { /* 该构造函数用于从头开始构造 */ + arr = std::vector(col * row); + } + + template + Matrx::Matrx(const uint64_t row, const uint64_t col, const std::vector& src) + : row(row), col(col) { /* 该构造函数用于通过已有的vector进行构造 */ + arr = src; + } + + template + const T& Matrx::get(uint64_t row, uint64_t col) const { /* 访问器 - 访问矩阵元素 */ + return arr[col * this->row + row]; + } + + template + T& Matrx::witch(uint64_t row, uint64_t col) { /* 访问器 - 访问矩阵元素 */ + return arr[col * this->row + row]; + } + + template + void Matrx::set(uint64_t row, uint64_t col, T v) { /* 设置矩阵元素 */ + arr[col * this->row + row] = v; + } + + template + const std::vector& Matrx::get_data() const{ /* 访问器 - 用于获取原始数据 */ + return arr; + } + + template + const uint64_t Matrx::get_col() const { /* 获取列数 */ + return col; + } + + template + const uint64_t Matrx::get_row() const { /* 获取行数 */ + return row; + } + + template + bool Matrx::has_same_shape(const Matrx& other) { /* 两个矩阵是否具有相同形状 */ + return other.get_col() == this->get_col() && other.get_row() == this->get_row(); + } + + template + bool Matrx::has_dot_shape(const Matrx& other) { /* 具有矩阵乘法的形状 */ + return this->get_col() == other.get_row(); + } + + template + void Matrx::assert_same_shape(const Matrx& other) { /* 断言两个矩阵具有相同形状 */ + if (!has_same_shape(other)) + throw std::invalid_argument("The shapes of matrix are not same."); + } + + template + void Matrx::assert_dot_shape(const Matrx& other) { /* 断言两个矩阵具有点积前置形状 */ + if (!has_dot_shape(other)) { + throw std::invalid_argument("The shapes of matrix can not be dot."); + } + } + + template + Matrx Matrx::operator+(const Matrx& other) { /* 加法操作符重定义 - 矩阵加法 */ + //assert_same_shape(other); /* 检查两个矩阵形状是否一致 */ + std::vector v0 = this->get_data(); + std::vector 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 + Matrx Matrx::operator+(T& item) { /* 矩阵加法广播 */ + auto len = this->get_data().size(); + auto v0 = std::vector (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 + Matrx Matrx::operator-(const Matrx& other) { /* 操作符重载 - 矩阵减法 */ + //assert_same_shape(other); /* 检查两个矩阵形状是否一致 */ + std::vector v0 = this->get_data(); + std::vector 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 + Matrx Matrx::operator-(T& item) { /* 操作符重载 - 矩阵减法广播 */ + auto len = this->get_data().size(); + auto v0 = std::vector (len, item); + auto matrix = (*this) - Matrx(get_row(), get_col(), v0); + + //Matrx matrix = *this; + //for (auto it = arr.begin(); it != arr.end(); ++it) + // *it -= item; + return matrix; + } + + template + Matrx Matrx::dot(const Matrx& other) { /* 操作符重载 - 矩阵乘法 */ + //assert_dot_shape(other); /* 它们的形状是否能够点积 */ + auto matrix = Matrx(this->get_row(), other.get_col()); + for (uint64_t i=0; iget_row(); ++i) /* 点积运算 */ + for (uint64_t j=0; jget_col(); ++k) + sum += this->get(i, k) * other.get(k, j); + matrix.witch(i, j) = sum; + } + return matrix; + } + + template + Matrx Matrx::operator*(T& item) { /* 操作符重载 - 矩阵乘法广播 */ + std::vector vec = this->get_data(); + for (auto it = vec.begin(); it != vec.end(); ++it) + *it *= item; + return Matrx{this->get_row(), this->get_col(), vec}; + } + + template + Matrx Matrx::operator*(const Matrx& other) { /* 加法操作符重定义 - 矩阵加法 */ + //assert_same_shape(other); /* 检查两个矩阵形状是否一致 */ + std::vector v0 = this->get_data(); + std::vector 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 + Matrx Matrx::operator/(T& item) { /* 操作符重载 - 矩阵除法广播 */ + std::vector vec = this->get_data(); + for (auto it = vec.begin(); it != vec.end(); ++it) + *it /= item; + return Matrx(this->get_row(), this->get_col(), vec); + } + + template + Matrx Matrx::operator/(const Matrx& other) { /* 加法操作符重定义 - 矩阵加法 */ + //assert_same_shape(other); /* 检查两个矩阵形状是否一致 */ + std::vector v0 = this->get_data(); + std::vector 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 class Matrx; /* 显式实例化 */ + template class Matrx; + +} + +/* FIXME: 形状不合法时返回空指针 */ +using namespace qing; +extern "C" { + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* createMatrix(uint64_t row, uint64_t col) { + return (void *) new Matrx(row, col); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void freeMatrix(void* M) { + delete (Matrx*) M; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + float* witchMatrix(void *m, uint64_t row, uint64_t col) { + Matrx *M = (Matrx*) m; + return &(M->witch(row, col)); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + float getMatrix(void *m, uint64_t row, uint64_t col) { + Matrx *M = (Matrx*) m; + return M->get(row, col); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void setMatrix(void *m, uint64_t row, uint64_t col, float v) { + Matrx *M = (Matrx*) m; + M->set(row, col, v); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + uint64_t getrowMatrix(void *m) { + Matrx *M = (Matrx*) m; + return M->get_row(); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + uint64_t getcolMatrix(void *m) { + Matrx *M = (Matrx*) m; + return M->get_col(); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + bool sameShapeMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + return M->has_same_shape(*S); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + bool dotShapeMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + return M->has_dot_shape(*S); + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* plusMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + auto a = new Matrx((*M) + (*S)); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* plusBroadcastMatrix(void *m, float t) { + Matrx *M = (Matrx*) m; + auto a = new Matrx ((*M) + t); + return (void*) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* subMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + auto a = new Matrx((*M) - (*S)); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void * subBroadcastMatrix(void *m, float t) { + Matrx *M = (Matrx *) m; + auto a = new Matrx ((*M) - t); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* multiMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + auto a = new Matrx((*M) * (*S)); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* multiBroadcastMatrix(void *m, float t) { + Matrx *M = (Matrx *) m; + auto a = new Matrx((*M) * t); + return (void*) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* divMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + auto a = new Matrx((*M) / (*S)); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* divBroadcastMatrix(void *m, float t) { + Matrx *M = (Matrx *) m; + auto a = new Matrx((*M) / t); + return (void*) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* dotMatrix(void *m, void *s) { + Matrx *M = (Matrx*) m; + Matrx *S = (Matrx*) s; + auto a = new Matrx(M->dot(*S)); + return (void *) a; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* transposeMatrix(void *m) { + Matrx *M = (Matrx *)m; + auto N= new Matrx(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 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 distribution(0.0f, sigma); + + /* 遍历权重矩阵,进行赋值 */ + uint64_t k=0; + for (uint64_t i=0; i(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(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 distribution(-scale, scale); + + /* 遍历权重矩阵,进行赋值 */ + uint64_t k=0; + for (uint64_t i=0; i (fan_in, fan_out, weights); + return (void *)matrix; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Sigmoid_do(void *m) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = 1.0 / (1.0 + std::exp(-x)); + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Sigmoid_dot(void *m) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& o: v) { + o = o * (1.0 - o); + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* ReLU_do(void *m) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = x > 0 ? x : 0; + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* ReLU_dot(void *m) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = x > 0 ? 1.0f : 0.0f; + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Leaky_ReLU_do(void *m, float alpha) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = x > 0 ? x : alpha * x; + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Leaky_ReLU_dot(void *m, float alpha) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = x > 0 ? 1.0 : alpha; + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Tanh_do(void *m) { + Matrx *M = (Matrx*) m; + std::vector v = M->get_data(); + for (float& x: v) { + x = std::tanh(x); + } + auto N = new Matrx (M->get_row(), M->get_col(), v); + return (void *)N; + } + + #ifdef _WIN64 + _declspec(dllexport) + #endif + void* Tanh_dot(void *m) { + Matrx *M = (Matrx*) m; + std::vector 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 (M->get_row(), M->get_col(), v); + return (void *)N; + } +} diff --git a/bot4cj/src/cpp/Matrix.hh b/bot4cj/src/cpp/Matrix.hh new file mode 100644 index 0000000..b32ced1 --- /dev/null +++ b/bot4cj/src/cpp/Matrix.hh @@ -0,0 +1,60 @@ +/* + + ___col___ +/ \ ++---------+ \ +| + + + + | | row - 第一维度 +| + + + + | | ++---------+ / + +*/ + +#pragma once +#include +#include +#include +#include +#include +namespace qing { +template +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& 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& get_data() const; + const uint64_t get_col() const; + const uint64_t get_row() const; + bool has_same_shape(const Matrx& other); + bool has_dot_shape(const Matrx& other); + void assert_same_shape(const Matrx& other); + void assert_dot_shape(const Matrx& other); + Matrx operator+(const Matrx& other); + Matrx operator+(T& item); + Matrx operator-(const Matrx& other); + Matrx operator-(T& item); + Matrx operator*(const Matrx& other); + Matrx operator*(T& item); + Matrx operator/(T& item); + Matrx operator/(const Matrx& other); + Matrx dot(const Matrx& other); + + void save(std::ostream& out) { + out << row << " " << col; + for (const auto& item : arr) { + out << " "; + out << item; + } + } + +private: + uint64_t row, col; + std::vector arr; +}; +} \ No newline at end of file diff --git a/bot4cj/src/cpp/bot4cj.h b/bot4cj/src/cpp/bot4cj.h new file mode 100644 index 0000000..52c098c --- /dev/null +++ b/bot4cj/src/cpp/bot4cj.h @@ -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 diff --git a/bot4cj/src/cpp/fft.cpp b/bot4cj/src/cpp/fft.cpp new file mode 100644 index 0000000..61981a4 --- /dev/null +++ b/bot4cj/src/cpp/fft.cpp @@ -0,0 +1,80 @@ +#define _USE_MATH_DEFINES +#include +#include +#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& 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& 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 A(DN); + functf1d(A.data()); + return (void *)new Matrx(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 R(N); + fft_init(A, *(Matrx *)M); + //fft1(A, 1); // 复序列基2 FFT + fft1r1(A, 1); // 单实序列 + fft_result(R, A); + return (void*) new Matrx(N/2, 1, R); + } + +} + diff --git a/bot4cj/src/cpp/fft.h b/bot4cj/src/cpp/fft.h new file mode 100644 index 0000000..78de2b3 --- /dev/null +++ b/bot4cj/src/cpp/fft.h @@ -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 diff --git a/bot4cj/src/cpp/fft1.c b/bot4cj/src/cpp/fft1.c new file mode 100644 index 0000000..05f7875 --- /dev/null +++ b/bot4cj/src/cpp/fft1.c @@ -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 +#include +#include +#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 %i, ni -> %lu)\n", N, ni); + exit(1); + } + kup[0] = N/2; /* kup[l] = 2 ^ (M - l - 1) */ + jup[0] =1; + for (j=1;jFFT ** 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 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 eps) + { + printf("The IDFT of the series isn't real!\n"); + exit(0); + } + } + for (j=0; j 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 eps) + { + printf("The IDFT of the series {xj} isn't real!\n"); + exit(0); + } + } + for (j = 0; j 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) +} diff --git a/bot4cj/src/cpp/kalman.h b/bot4cj/src/cpp/kalman.h new file mode 100644 index 0000000..8e3db5d --- /dev/null +++ b/bot4cj/src/cpp/kalman.h @@ -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 diff --git a/bot4cj/src/cpp/snd.cpp b/bot4cj/src/cpp/snd.cpp new file mode 100644 index 0000000..df83060 --- /dev/null +++ b/bot4cj/src/cpp/snd.cpp @@ -0,0 +1,354 @@ +#ifdef _WIN32 + #include + #include + #pragma comment(lib, "winmm.lib") + #pragma comment(lib, "user32.lib") + #define _USE_MATH_DEFINES + #include +#else + #include + #include +#endif + +#include +#include +#include +#include + +#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(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( + std::round(static_cast(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(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(num_frames * channels); + std::vector buffer(total_samples); + + // 计算振幅(应用对数音量缩放以获得更自然的音量控制) + int32_t amplitude = static_cast(32767.0 * volume); + + // 生成正弦波数据 + for (int64_t i = 0; i < num_frames; i++) { + // 使用 double 计算以提高精度 + double t = static_cast(i) / sample_rate; + double value = sin(2.0 * M_PI * frequency * t); + + // 应用淡入淡出以避免咔嗒声 + int64_t fade_samples = static_cast(sample_rate * 0.005); // 5ms 淡入淡出 + double fade_factor = 1.0; + + if (i < fade_samples) { + // 淡入:从 0 到 1 + fade_factor = static_cast(i) / fade_samples; + } else if (i >= num_frames - fade_samples) { + // 淡出:从 1 到 0 + fade_factor = static_cast(num_frames - 1 - i) / fade_samples; + } + + int16_t sample = static_cast( + 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(waveOutProc), // 回调函数 + reinterpret_cast(&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(buffer.data()); + header.dwBufferLength = static_cast(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(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(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(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(num_frames)) { + snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params, + &reinterpret_cast(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(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(num_frames); + + // 循环写入直到所有数据都播放完毕 + while (frames_written < static_cast(num_frames)) { + snd_pcm_uframes_t remaining = static_cast(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" \ No newline at end of file diff --git a/bot4cj/src/cuda/error.cuh b/bot4cj/src/cuda/error.cuh new file mode 100644 index 0000000..96aaad1 --- /dev/null +++ b/bot4cj/src/cuda/error.cuh @@ -0,0 +1,12 @@ +#pragma once +#include +#include + +#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) diff --git a/bot4cj/src/cuda/matrix.cu b/bot4cj/src/cuda/matrix.cu new file mode 100644 index 0000000..505ba2a --- /dev/null +++ b/bot4cj/src/cuda/matrix.cu @@ -0,0 +1,872 @@ +#include +#include +#include +#include +#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<<>>(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 distribution(-scale, scale); + + /* 遍历权重矩阵,进行赋值 */ + uint64_t k=0; + for (uint64_t i=0; igetR(); +} +#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); +} + + +} \ No newline at end of file diff --git a/bot4cj/src/hd/I2C.cj b/bot4cj/src/hd/I2C.cj new file mode 100644 index 0000000..6e6b7cd --- /dev/null +++ b/bot4cj/src/hd/I2C.cj @@ -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 = [reg, val] + var p: CPointer = unsafe { CPointer(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(inout ptr), 1) }) { + throw I2cReadRegFailed("?") + } + var value: Int8 = 0 + var p = unsafe { CPointer(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() /* -------- phread_mutex_t 锁对象 -------- */ + var is_active = false /* -------- 该锁具是否被激活 --------*/ +} + + +var MAIN_LOCK = CPointer() /* -------- 主锁,操作锁具时使用 -------- */ +var LOCKS = CPointer() /* -------- 指向锁具内存的指针 -------- */ +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(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() + } + + /* -------- 出口 -------- */ + 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) } +} diff --git a/bot4cj/src/hd/MPU6050.cj b/bot4cj/src/hd/MPU6050.cj new file mode 100644 index 0000000..fd73ca5 --- /dev/null +++ b/bot4cj/src/hd/MPU6050.cj @@ -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): CPointer { + + /* -------- 获取传入线程的参数 -------- */ + let param = unsafe{ CPointer(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() +} + +/* -------- 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() /* 用于辅助别的线程获取值的锁 */ +} + + + +/* -------- 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() + var tmp = mpu6050_th_param() + tmp.i2c_fd = i2c_fd + tmp.addr = addr + tmp.mutex = LibC.malloc() + 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 /* -------- 线程参数必须能够持久保存 -------- */ + var pth: Option = None /* -------- 线程对象 -------- */ + public func startDaemon() { /* -------- 启动线程 -------- */ + pth = Pth(mpu6050_main, CPointer(param)) + } + + /* -------- 停止线程 -------- */ + public func stopDaemon() { + if (let Some(P) <- pth) { + P.close() } + } + + /* -------- 等待线程终止 -------- */ + public func waitDaemon() { + if (let Some(P) <- pth) { + var ptr: CPointer = CPointer() + P.join(CPointer>(CPointer>(ptr))) } + } + +} + diff --git a/bot4cj/src/hd/PCA9685.cj b/bot4cj/src/hd/PCA9685.cj new file mode 100644 index 0000000..a1d80a0 --- /dev/null +++ b/bot4cj/src/hd/PCA9685.cj @@ -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) + } + } +} diff --git a/bot4cj/src/hd/Servo.cj b/bot4cj/src/hd/Servo.cj new file mode 100644 index 0000000..accc4cd --- /dev/null +++ b/bot4cj/src/hd/Servo.cj @@ -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) + } +} diff --git a/bot4cj/src/hd/extern/macro_ExternHardware.cj b/bot4cj/src/hd/extern/macro_ExternHardware.cj new file mode 100644 index 0000000..fe85ae8 --- /dev/null +++ b/bot4cj/src/hd/extern/macro_ExternHardware.cj @@ -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) +} diff --git a/bot4cj/src/hd/fb.cj b/bot4cj/src/hd/fb.cj new file mode 100644 index 0000000..c8edafd --- /dev/null +++ b/bot4cj/src/hd/fb.cj @@ -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, length: UIntNative, prot: C_Int, flags: C_Int, fd: C_Int, offset: off_t): CPointer + +/* -------- 打开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 /* 设备驱动名称字符串,比如"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 /* 保留字段,用于未来扩展,确保兼容 */ + + public init() { + id = VArray (repeat: 0) + reserved = VArray (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/* Reserved for future compatibility */ + + public init() { + red = fb_bitfield() + green = fb_bitfield() + blue = fb_bitfield() + transp = fb_bitfield() + reserved = VArray (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, length: UIntNative, fd: C_Int, offset: off_t): CPointer +{ + let buf = unsafe { mmap(addr, length, PROT_WRITE, MAP_SHARED, fd, offset) } + return buf +} diff --git a/bot4cj/src/hd/snd.cj b/bot4cj/src/hd/snd.cj new file mode 100644 index 0000000..7314d66 --- /dev/null +++ b/bot4cj/src/hd/snd.cj @@ -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 + } +} diff --git a/bot4cj/src/main.cj b/bot4cj/src/main.cj new file mode 100644 index 0000000..aacb768 --- /dev/null +++ b/bot4cj/src/main.cj @@ -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 = None +main(args: Array): 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 = + //if (options.keys().contains("i")) { + // let filename = options['i'] + // load_nns(filename) } + //else { + // let nn_in = ArrayList() + // 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.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() + 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() + } + +} diff --git a/bot4cj/src/math/Adam.cj b/bot4cj/src/math/Adam.cj new file mode 100644 index 0000000..a4ebc4b --- /dev/null +++ b/bot4cj/src/math/Adam.cj @@ -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 + var v: HashMap + 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() + v = HashMap() + t = 0 + } + + // 默认参数构造函数 + public init() { + learningRate = FP32(0.001) + beta1 = FP32(0.9) + beta2 = FP32(0.999) + epsilon = FP32(0.00000001) + m = HashMap() + v = HashMap() + t = 0 + } + + // 仅自定义学习率的构造函数 + public init(lr: FP32) { + learningRate = lr + beta1 = FP32(0.9) + beta2 = FP32(0.999) + epsilon = FP32(0.00000001) + m = HashMap() + v = HashMap() + 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 + } +} \ No newline at end of file diff --git a/bot4cj/src/math/Af.cj b/bot4cj/src/math/Af.cj new file mode 100644 index 0000000..1d0d4b3 --- /dev/null +++ b/bot4cj/src/math/Af.cj @@ -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 + } + +} diff --git a/bot4cj/src/math/Ini.cj b/bot4cj/src/math/Ini.cj new file mode 100644 index 0000000..d62fcc6 --- /dev/null +++ b/bot4cj/src/math/Ini.cj @@ -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)) } + } + + } +} diff --git a/bot4cj/src/math/Matrix.cj b/bot4cj/src/math/Matrix.cj new file mode 100644 index 0000000..62e34c6 --- /dev/null +++ b/bot4cj/src/math/Matrix.cj @@ -0,0 +1,535 @@ +package bot4cj.math +import std.fs.* +import std.io.* +import std.convert.* +//foreign func createMatrix(row: UInt64, col: UInt64): CPointer +//foreign func freeMatrix(M: CPointer): Unit +//foreign func witchMatrix(M: CPointer, row: UInt64, col: UInt64): CPointer +//foreign func getMatrix(M: CPointer, row: UInt64, col: UInt64): Float32 +//foreign func setMatrix(M: CPointer, row: UInt64, col: UInt64, v: Float32): Unit +//foreign func getrowMatrix(M: CPointer): UInt64 +//foreign func getcolMatrix(M: CPointer): UInt64 +//foreign func sameShapeMatrix(M: CPointer, S: CPointer): Bool +//foreign func dotShapeMatrix(M: CPointer, S: CPointer): Bool +//foreign func plusMatrix(M: CPointer, S:CPointer): CPointer +//foreign func plusBroadcastMatrix(M: CPointer, T: Float32): CPointer +//foreign func subMatrix(M: CPointer, S: CPointer): CPointer +//foreign func subBroadcastMatrix(M: CPointer, T: Float32): CPointer +//foreign func multiMatrix(M: CPointer, S: CPointer): CPointer +//foreign func multiBroadcastMatrix(M: CPointer, T: Float32): CPointer +//foreign func divMatrix(M: CPointer, S: CPointer): CPointer +//foreign func divBroadcastMatrix(M: CPointer, T: Float32): CPointer +//foreign func dotMatrix(M: CPointer, S: CPointer): CPointer +//foreign func transposeMatrix(M: CPointer): CPointer +//foreign func xavierNormalInit(fan_in: UInt64, fan_out: UInt64): CPointer +//foreign func xavierUniformInit(fan_in: UInt64, fan_out: UInt64): CPointer + + +/* -------- 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 /* ---- 数据 ---- */ + + /* -------- 构造器,默认值为0 -------- */ + public Matrix1(row: Int64, col: Int64) { + this.row = row + this.col = col + this.arr = Array(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) { 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 { +// // 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 { 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) { +// let s = sr.readUntil({r => r==r' ' || r ==r'\n'}) +// return Float32.parse(s.getOrThrow()) +// } +// +// /* -------- 静态方法,加载矩阵 -------- */ +// public static func load(sr: StringReader, 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 +//} diff --git a/bot4cj/src/math/Tensor.cj b/bot4cj/src/math/Tensor.cj new file mode 100644 index 0000000..037f847 --- /dev/null +++ b/bot4cj/src/math/Tensor.cj @@ -0,0 +1,56 @@ +package bot4cj.math +import std.collection.* + +/* -------- 张量 -------- + * INFO(20260524-qing): 输入一个维度列表 */ +class Tensor { + private let data: Array + private let dims: Array + private let size: Int64 + public init(dims: Array, val: T) { + var size = 1 + for (item in dims) { /* ---- 计算总量 ---- */ + size *= item + } + this.data = Array(size, {_ => val}) + this.dims = dims + this.size = size + } + + /* -------- 私有构造函数,用于内部创建张量 -------- */ + private init(dims: Array, data: Array) { + this.dims = dims + this.data = data + var size = 1 + for (item in dims) { + size *= item + } + this.size = size + } + + /* -------- 获取维度列表(形状) -------- */ + public func shape(): Array { + return dims + } + + /* -------- 逐元素进行某种运算 -------- */ + public func oneByOne(S: Tensor, f: (T, T) -> T): Tensor { + 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(size, {i => f(data[i], S.data[i])}) + return Tensor(dims, res_data) + } +} diff --git a/bot4cj/src/math/cnn.cj b/bot4cj/src/math/cnn.cj new file mode 100644 index 0000000..f0b50ee --- /dev/null +++ b/bot4cj/src/math/cnn.cj @@ -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 + var biases: Matrix1 + + var prevInput: Option = Option.None + var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None + var prevCombination: Option = Option.None + var prevOutput: Option = Option.None + var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None + + var kernelsGrad: Option> = Option.None + var biasGrad: Option = 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) + } +} diff --git a/bot4cj/src/math/fft.cj b/bot4cj/src/math/fft.cj new file mode 100644 index 0000000..abcbd9d --- /dev/null +++ b/bot4cj/src/math/fft.cj @@ -0,0 +1,11 @@ +package bot4cj.math +//foreign func start_fft(M: CPointer): CPointer +//foreign func gen_wave(): CPointer +/*-------- 生成测试用的正弦波 --------*/ +//public func genWave(): Matrix { +// unsafe { return Matrix(gen_wave()) } +//} +/*-------- 执行fft算法 --------*/ +//public func start_fft(M: Matrix): Matrix { +// unsafe { return Matrix(start_fft(M.getData())) } +//} diff --git a/bot4cj/src/math/flatten.cj b/bot4cj/src/math/flatten.cj new file mode 100644 index 0000000..aa97beb --- /dev/null +++ b/bot4cj/src/math/flatten.cj @@ -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 + var prevInputShape: Option<(Int64, Int64, Int64, Int64)> + var prevOutput: Option + 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 + } +} diff --git a/bot4cj/src/math/kalman.cj b/bot4cj/src/math/kalman.cj new file mode 100644 index 0000000..156cce7 --- /dev/null +++ b/bot4cj/src/math/kalman.cj @@ -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) + } +} diff --git a/bot4cj/src/math/nn.cj b/bot4cj/src/math/nn.cj new file mode 100644 index 0000000..24384be --- /dev/null +++ b/bot4cj/src/math/nn.cj @@ -0,0 +1,331 @@ +package bot4cj.math +import std.fs.* +import std.io.* +import std.convert.* +//foreign func Sigmoid_do(m: CPointer): CPointer +//foreign func Sigmoid_dot(m: CPointer): CPointer +//foreign func ReLU_do(m: CPointer): CPointer +//foreign func ReLU_dot(m: CPointer): CPointer +//foreign func Leaky_ReLU_do(m: CPointer, alpha: Float32): CPointer +//foreign func Leaky_ReLU_dot(m: CPointer, alpha: Float32): CPointer +//foreign func Tanh_do(m: CPointer): CPointer +//foreign func Tanh_dot(m: CPointer): CPointer + + +/* -------- 新版的神经网络,使用原生矩阵对象实现 -------- + * TODO(20260517-qing): 增加batch批次处理,使用ArrayList + * 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 = None /* ---- 之前的输入 ---- */ + var prev_raw_inputs: Option = None /* ---- 之前的原始输出 ---- */ + var prev_outputs: Option = None /* ---- 之前的输出 ---- */ + /*------------------*/ + var weights_grad: Option = None /* ---- 权重梯度 ---- */ + var bias_grad: Option = 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(sr: StringReader) where T <: Parsable +// { +// 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) +// { +// let inputno = loadItem(sr) +// let outputno = loadItem(sr) +// let lr = loadItem(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 +//} diff --git a/bot4cj/src/math/nns.cj b/bot4cj/src/math/nns.cj new file mode 100644 index 0000000..fd4a65b --- /dev/null +++ b/bot4cj/src/math/nns.cj @@ -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, 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, 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 { +// let nns = ArrayList() +// let file = File(filename, OpenMode.Read) +// let sr = StringReader(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, 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, 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, m: Matrix, t: Matrix, discount: Float32) { +// +// /* ----------------- */ +// let outs_dof = ArrayList () +// let outs_dotf = ArrayList () +// +// /* -------- 正向传播 -------- */ +// 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) +// } +// +//} diff --git a/bot4cj/src/math/pooling.cj b/bot4cj/src/math/pooling.cj new file mode 100644 index 0000000..7b60c51 --- /dev/null +++ b/bot4cj/src/math/pooling.cj @@ -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 = Option.None + var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None + var prevCombination: Option = Option.None + var prevOutput: Option = Option.None + var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None + var prevMaxIndices: Option = Option.None + + var weightsGrad: Option = Option.None + var biasGrad: Option = 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) + } +} diff --git a/bot4cj/src/r.cj b/bot4cj/src/r.cj new file mode 100644 index 0000000..a4f184b --- /dev/null +++ b/bot4cj/src/r.cj @@ -0,0 +1,1071 @@ +/* -------- 机器人构成模块 + * -------- INFO(20260514-qing): 该模块确定机器人结构,为解耦考虑应该从解析出的矩阵对象中构建 + * -------- TODO(20260527-qing): 使脚本中的数值亦可从运行时确定 + * -------- */ + +package bot4cj +import std.io.* +import std.time.* +import std.random.* +import std.collection.* +import std.convert.* +import stdx.encoding.json.* +import bot4cj.hd.* +//import bot4cj.hd.extern.* +import bot4cj.math.* +import ctype4cj.* +import spi4cj.* +import lua4cj.* + +/* ---------------- 硬件变量基类 ---------------- + * -------- 接收3个lambda表达式,在回调函数中定义行为() + * -------- 通过JsonValue这种通用的数据结构进行传值 + * + * -------- TODO*(20260517-qing): 因为析构器(终结器)的调用时间不确定,需要手动构造RAII机制 + * -------- TODO(20260519-qing): 考虑将@Action矩阵对象置为硬件变量的一种 + * -------- TODO*(20260529-qing): 增加微调器机制,通过不同的微调器对@Action进行微调 + * ---------------------------------------------- */ +open class MatrixVars_Hardware { + + /* ---------------- 主初始化器 ---------------- + * -------- 输入一个回调函数,用json参数能够为多种硬件提供同一接口 + * -------- 在@Action矩阵中的每一列都是一个输入参数 + * -------------------------------------------- */ + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit>, + free!: Option<() -> Unit> ) { + this.things = things + this.rise = rise + this.free = free + } + + + /* -------------------------------- + * -------------------------------- + * -------- 事件回调,接收和返回JsonValue + * -------- 这是整个硬件的主要事件 */ + + private let things: (JsonValue) -> JsonValue + + /* -------- 召集事件,不接受参数 + * -------- 即硬件获取资源 */ + + private let rise: Option<() -> Unit> + + /* -------- 遣返事件,不接受参数 + * -------- 即硬件释放资源*/ + + private let free: Option<() -> Unit> + + /* -------------------------------- * + * -------------------------------- */ + + /* -------- 事件发生器 + * -------- TODO(20260612-qing): 使用某种方式记录变量是否经过了初始化 + * -------- + * -------- 该方法会调用硬件,需要传入1个JsonValue参数 + * -------- 它返回1个JsonValue值 */ + + public func play(input: JsonValue): JsonValue { + return things(input) + } + + /* -------- 伪初始化器 + * -------- 该方法会打开硬件,什么也不传入和返回 */ + + public func build() { + if (let Some(callback) <- rise) { + callback() + } + } + + /* -------- 伪解构器 + * -------- 该方法会关闭硬件,什么也不传入和返回 */ + + public func destroy() { + if (let Some(callback) <- free) { + callback() + } + } + + /* ----------------- */ + /* ----------------- */ + + /* -------- 获得项目值 + * -------- 如果矩阵单元的原始值是占位符且不为标题行,不为首行 + * -------- 就递归地获取上一行的值 */ + public static func getMt(obj: MatrixObject, row: Int64, col: Int64): String + { + let raw = obj[row, col] + if (raw == "-" && row > 1) { + return getMt(obj, row - 1, col) + } + return raw + } + + /* -------- 解析项目值 + * -------- 先获取矩阵单元的项目值(解析过占位符的值) + * -------- 然后解析为需要的格式 + * -------- */ + public static func parseMt(obj: MatrixObject, row: Int64, col: Int64): T where T <: Parsable + { + /* ---- 先获取矩阵单元的原始值 ---- */ + let raw = obj[row, col] + + /* ---- 原始值为占位符且(非首行的)非标题行 ---- */ + if (raw == "-" && row > 1) { + return parseMt(obj, row-1, col) + } + + /* ---- 原始值为占位符且为(非标题行的)首行 ---- */ + else if (raw == "-" && !(row > 1)) { + return T.parse("0") } + + /* ---- 原始值并非占位符 ---- */ + // else if (!raw == "-") + return T.parse(raw) + } + /* ----------------- */ + /* ----------------- */ +} + + +/* ---- 用于注册到Lua的I2C操作C语言函数 ---- + * + * + * ---- 打开一个I2C设备 ---- */ +@C +func Lua_openI2c(L: lua_State): C_Int { + unsafe { + let filename: CString = Lua_checkstring(L, 1) /* 第一个参数 */ + let fd = openI2cDev(filename) + let val: lua_Integer = Int64(fd) /* lua_Integer */ + Lua_pushinteger(L, val) + return 1 /* 返回1,表示有1个返回值 */ + } +} + +/* ---- 关闭I2C设备 ---- */ +@C +func Lua_closeI2c(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + closeI2cDev(fd) + return 0 + } +} + +/* ---- 设置I2C地址 ---- */ +@C +func Lua_setI2cAddr(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let addr = UInt8(Lua_checkinteger(L, 2)) + setI2cAddr(fd, addr) + return 0 + } +} + +/* ---- 写I2C寄存器 ---- */ +@C +func Lua_writeI2cReg(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let reg = UInt8(Lua_checkinteger(L, 2)) + let val = UInt8(Lua_checkinteger(L, 3)) + writeI2cReg(fd, reg, val) + return 0 + } +} + +/* ---- 读I2C寄存器 ---- */ +@C +func Lua_readI2cReg(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let reg = UInt8(Lua_checkinteger(L, 2)) + let res = Int64(readI2cReg(fd, reg)) /* lua_Integer */ + Lua_pushinteger(L, res) + return 1 + } +} + +/* ---- 毫秒级延时 ---- */ +@C +func Lua_milliSleep(L: lua_State): C_Int { + unsafe { + let n = Int64(Lua_checkinteger(L, 1)) + sleep(Duration.millisecond * n) + return 0 + } +} + +/* ---- 打开一个SPI设备 ---- */ +@C +func Lua_openSPI(L: lua_State): C_Int { + unsafe { + let filename: CString = Lua_checkstring(L, 1) /* 第一个参数 */ + let fd = open_spi(filename) + let val: lua_Integer = Int64(fd) /* lua_Integer */ + Lua_pushinteger(L, val) + return 1 /* 返回1,表示有1个返回值 */ + } +} + + +/* ---- 关闭一个SPI设备 ---- */ +@C +func Lua_closeSPI(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + close_spi(fd) + return 0 + } +} + +/* ---- 设置一个SPI设备 ---- */ +@C +func Lua_setupSPI(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let mode = UInt8(Lua_checkinteger(L, 2)) + let bits = UInt8(Lua_checkinteger(L, 3)) + let speed = UInt32(Lua_checkinteger(L, 4)) + let delay = UInt16(Lua_checkinteger(L, 5)) + let res: lua_Integer = Int64(setup_spi(fd, mode, bits, speed, delay)) /* lua_Integer */ + Lua_pushinteger(L, res) + return 1 + } +} + +/* ---- 写SPI设备 ---- */ +@C +func Lua_writeSPI(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let val = UInt8(Lua_checkinteger(L, 2)) + let res: lua_Integer = Int64(write_spi(fd, val)) /* lua_Integer */ + Lua_pushinteger(L, res) + return 1 + } +} + +/* ---- 读SPI设备 ---- */ +@C +func Lua_readSPI(L: lua_State): C_Int { + unsafe { + let fd = Int32(Lua_checkinteger(L, 1)) /* C_Int */ + let res: lua_Integer = Int64(read_spi(fd)) /* lua_Integer */ + Lua_pushinteger(L, res) + return 1 + } +} + + +/* ---- Lua状态机 - 硬件变量 + * ---- + * ---- TODO(20260612-qing): 提前创建Lua状态机,利用闭包进行引用储存 + * ---- 在rise中申请的资源将在free中被释放。 + * ---- + * ---- 我们将Lua状态机做成类成员存储,在原本的召集函数中进行初始化 + * ---- 在解构函数中释放资源。 + * ---- + * ---- */ +class MtVars_Lua <: MatrixVars_Hardware { + + /* ---- 构造函数 => 传入事件函数,召集函数以及解构函数 + * ---- + * ---- */ + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None ) + + { + super(things: things, rise: rise, free: free) + } + + + /* ---- 工厂函数 => 运行指定Lua脚本,传入矩阵中的所有参数(元数据) + * ---- + * ---- */ + public static func Factory(luapath: String, metadata: JsonValue): MtVars_Lua + { + + /* ---- LUA状态机对象 ---- */ + let L: lua_State = unsafe {Lua_init()} + unsafe { + if (Lua_check(L) == LUA_ERR) { + throw IllegalMemoryException("Lua_check()") + } + } + + let mtVars_Lua = MtVars_Lua( + + /* ---- 主要事件:调用该硬件的驱动脚本 + * ---- 合并两张表(json_obj),一张来自创建该硬件时的设置(元数据) + * ---- 另一张是调用该硬件事件时传入的参数 + * ---- + * ---- 其中前者是一些诸如I2C地址、SPI速率的硬件参数 + * ---- 后者是一些类似舵机旋转角度的实时参数 + * ---- */ + things: { json: JsonValue => + + /* ---- 从元数据(json_obj)进行深拷贝 ---- */ + let obj = JsonValue.fromStr(metadata.toJsonString()).asObject() + obj.put("mode", JsonString("call")) + + /* --- 合并两张(json_obj)表(元数据和参数) --- */ + let fields: HashMap = json.asObject().getFields() + for (k in fields.keys()) { + let val = fields.get(k).getOrThrow() + obj.put(k, val) + } + println("Lua scripts call parameters: ${obj.toString()}") + + let res = call_lua(L, obj) + + println("Lua script call result: ${res.toString()}") + + return res + + }, + + /* ---- 召集方法:提供元数据(json_obj) + * ---- 并对该硬件进行初始化 + * ---- */ + rise: { => + + /* ---- 在这里构建Lua状态机进行构建 ---- + * ---- 使用专用的异常 ---- */ + if (build_lua(L, luapath) == false) { + throw Exception("build_lua") + } + + + /* ---- 从元数据进行深拷贝 ---- */ + let obj = JsonValue.fromStr(metadata.toJsonString()).asObject() + + /* ---- 插入模式指令 ---- */ + obj.put("mode", JsonString("build")) + + println("Lua scripts build parameters: ${obj.toString()}") + + let res = call_lua(L, obj) + + println("Lua script build result: ${res.toString()}") + + }, + + /* ---- 解构方法:传入元数据(json_obj|json_data) + * ---- 然后使用Lua状态机对设备进行关闭(或者重置) + * ---- */ + free: { => + + /* ---- 从元数据进行深拷贝 ---- */ + let obj = JsonValue.fromStr(metadata.toJsonString()).asObject() + + /* ---- 插入模式指令 ---- */ + obj.put("mode", JsonString("destroy")) + + println("Lua scripts destroy parameters: ${obj.toString()}") + + let res = call_lua(L, obj) + + println("Lua script destroy result: ${res.toString()}") + + + + /* ---- 在这里写就Lua状态机的解构 ---- */ + destroy_lua(L) + + } + + ) + + + /* ---- 返回构造的硬件变量 + * ---- */ + return mtVars_Lua + } + + /* ---- 构造Lua状态机 ---- + * ---- NOTE(20260618-qing): 构建完毕会运行一次 */ + private static func build_lua(L: lua_State, luapath: String): Bool { + /* ---- 申请资源 + * ---- */ + unsafe { + let filename_c = LibC.mallocCString(luapath) + if (filename_c.isEmpty()) { + throw IllegalMemoryException("LibC.mallocCString()") + } + + /* ---- 加载Lua库、注册Lua函数 + * ---- */ + Lua_loadlib(L) + + /* ---- TH ---- */ + + /* ---- milli_sleep() ---- */ + var cstr = LibC.mallocCString("milli_sleep") + Lua_register(L, cstr, Lua_milliSleep) + LibC.free(cstr) + + /* ---- I2C设备 ---- */ + + /* ---- i2c_open() ---- */ + cstr = LibC.mallocCString("i2c_open") + Lua_register(L, cstr, Lua_openI2c) + LibC.free(cstr) + + /* ---- i2c_close() ---- */ + cstr = LibC.mallocCString("i2c_close") + Lua_register(L, cstr, Lua_closeI2c) + LibC.free(cstr) + + /* ---- i2c_set_addr() ---- */ + cstr = LibC.mallocCString("i2c_set_addr") + Lua_register(L, cstr, Lua_setI2cAddr) + LibC.free(cstr) + + /* ---- i2c_write_reg() ---- */ + cstr = LibC.mallocCString("i2c_write_reg") + Lua_register(L, cstr, Lua_writeI2cReg) + LibC.free(cstr) + + /* ---- i2c_read_reg() ---- */ + cstr = LibC.mallocCString("i2c_read_reg") + Lua_register(L, cstr, Lua_readI2cReg) + LibC.free(cstr) + + /* ---- SPI ----*/ + + /* ---- spi_open() ---- */ + cstr = LibC.mallocCString("spi_open") + Lua_register(L, cstr, Lua_openSPI) + LibC.free(cstr) + + /* ---- spi_close() ---- */ + cstr = LibC.mallocCString("spi_close") + Lua_register(L, cstr, Lua_closeSPI) + LibC.free(cstr) + + /* ---- spi_setsup() ---- */ + cstr = LibC.mallocCString("spi_setup") + Lua_register(L, cstr, Lua_setupSPI) + LibC.free(cstr) + + /* ---- spi_write() ---- */ + cstr = LibC.mallocCString("spi_write") + Lua_register(L, cstr, Lua_writeSPI) + LibC.free(cstr) + + /* ---- spi_read() ---- */ + cstr = LibC.mallocCString("spi_read") + Lua_register(L, cstr, Lua_readSPI) + LibC.free(cstr) + + + /* ---- 设置Lua状态机 + * ---- */ + var status = Lua_loadfilex(L, filename_c, CPointer()) + LibC.free(filename_c) + + if (status == LUA_OK) { + var status = Lua_pcallk(L, 0, LUA_MULTRET, 0, 0, CPointer()) + println("lua build status: ${status}") + if (status == LUA_OK) { + return true + } + } + + /* ---- 从栈顶获取错误信息 + * ---- */ + let error_msg = Lua_tostring(L, -1); + println(error_msg.toString()); + + return false + } + } + + /* ---- 释放LUA状态机 + * ---- NOTE(20260618-qing): 销毁之后该变量即废弃 */ + private static func destroy_lua(L: lua_State) { + unsafe { Lua_free(L) } + } + + /* ---- 调用Lua脚本的方法 + * ---- NOTE(20260618-qing): 每次调用之后会重置栈指针 */ + private static func call_lua(L: lua_State, json: JsonValue): JsonValue { + let obj = json.asObject() + let ret = JsonObject() + unsafe { + + /* ---- 获取栈顶指针 ----*/ + let top = Lua_gettop(L) + + /* ---- ---- */ + let mode = obj.get("mode").getOrThrow().asString().getValue() + let cstr = LibC.mallocCString(mode) + Lua_getglobal(L, cstr) + LibC.free(cstr) + + /* ----在Lua状态机里 创建Lua表 + * ---- */ + Lua_createtable(L, 0, 0) + + + /* ---- 向Lua表中推入数据 + * ---- */ + let fields: HashMap = obj.getFields() + for (k in fields.keys()) { + let val = fields.get(k).getOrThrow() + if (val is JsonString) { + let v = val.asString().getValue() + let sk = LibC.mallocCString(k) + let sv = LibC.mallocCString(v) + Lua_pushstring(L, sv) + Lua_setfield(L, -2, sk) + LibC.free(sk) + LibC.free(sv) + } + else if (val is JsonInt) { + let v = val.asInt().getValue() + let sk = LibC.mallocCString(k) + let sv = Int64(v) + Lua_pushinteger(L, sv) + Lua_setfield(L, -2, sk) + LibC.free(sk) + } + } + + var status = Lua_pcallk(L, 1, LUA_MULTRET, 0, 0, CPointer()) + println("lua status: ${status}") + if (status == LUA_OK) { + if (Lua_type(L, -1) == LUA_TTABLE) { + /* ---- 栈顶是表 + * ---- 压入 nil 作为遍历的起始键 + * ---- */ + Lua_pushnil(L) + while(Lua_next(L, -2) != 0) { + /* ---- 此时栈:[表, 键, 值] ---- + * ---- 检查键值是否为空指针 ---- + * ---- FIXME(20260531-qing) ---- */ + let key = Lua_tostring(L, -2) + let val = Lua_tostring(L, -1) + if (key.isEmpty() || val.isEmpty()) { + throw NoneValueException("Lua_tostring()") + } + println("${key}: ${val}") + ret.put(key.toString(), JsonString(val.toString())) + Lua_pop(L, 1) + } + } + //Lua_pop(L, 1) + } + + else if (status != LUA_OK) { + let err = Lua_tostring(L, -1) + if (!err.isEmpty()) { + println("Lua 错误详情: ${err.toString()}") + } + //Lua_pop(L, 1) + } + + /* ---- 恢复Lua状态机 ----*/ + Lua_settop(L, top) + //Lua_pop(L, 1) + } + return ret + } +} + +/* -------- 别名变量 + * -------- 这种变量用于给某个参数取一个更简单的别名 + * -------- 它的事件返回(JsonString)将作为新的矩阵项目值 + * -------- + * -------- */ +class MtVars_Alias <: MatrixVars_Hardware { + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None ) + { super(things: things, rise: rise, free: free) } + + public static func Factory(AliasDefs: MatrixObject, i: Int64, + _: HashMap): MtVars_Alias + { + let idxName = AliasDefs.indexOfCol(0, "name") + let idxAlias= AliasDefs.indexOfCol(0, "alias") + + let name = AliasDefs[i, idxName] + let alias= getMt(AliasDefs, i, idxAlias) + + println("Create a Alias vars: ${name}, witch means ${alias}") + let mtvars_alias = MtVars_Alias(things: {_: JsonValue => + let str = JsonString(alias) /* 直接返回JsonString(alias)*/ + return str + }) + + return mtvars_alias + } +} + +/* -------- I2C变量类 这种硬件变量是特殊且固定的 + * -------- TODO(20260617-qing): 等LUA状态机被闭包保存之后,将其优化为Lua驱动 + * -------- */ +class MtVars_I2C <: MatrixVars_Hardware { + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None ) + + { super(things: things, rise: rise, free: free) } + + /* @I2C + * name path; + * i2c-0 /dev/i2c-0; + * @ + */ + public static func Factory( /* 工厂模式,根据矩阵对象构造 */ + I2CDefs: MatrixObject, i: Int64, _: HashMap): MtVars_I2C + { + let idxName = I2CDefs.indexOfCol(0, "name") + let idxPath = I2CDefs.indexOfCol(0, "path") + + let name = I2CDefs[i, idxName] + let path = getMt(I2CDefs, i, idxPath) + + println("Create a I2C hard-Var: ${name},\tpath: ${path}") /* ---- debug ---- */ + let i2c = I2C_Orig(path) /* 在这里创建传统I2C类对象,通过闭包保存在MtVars_I2C里 */ + let mtvars_i2c = MtVars_I2C( + things: { json: JsonValue => + let fd = i2c.getFd() + let ret = JsonObject() + ret.put("fd",JsonInt(Int64(fd))) + return ret + }, + + free: {=> + println("I2C HVar: ${name} is closing now") + i2c._close() + }) + + return mtvars_i2c + } +} + +/* -------- MPU6050_BIND硬件变量 + * -------- INFO(20260612-qing): 自从引入了Lua状态机扩展硬件,该硬件变量不知道何去何从。 + * -------- TODO(20260612-qing): 在Lua脚本中定义好BIND函数后,在pthread线程中反复调用 + * -------- */ +class MtVars_MPU6050_BIND <: MatrixVars_Hardware { + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None) { + super(things: things, rise: rise, free: free) } + + /* 工厂函数,通过矩阵对象构建硬件变量 + * @MPU6050_BIND + * name I2C addr; + * mpu6050-0 i2c-1 0x69; + */ + public static func Factory ( + MPU6050_BIND_Defs: MatrixObject, i: Int64, + map: HashMap)//: MtVars_MPU6050_BIND + { + let idxName = MPU6050_BIND_Defs.indexOfCol(0, "name") + let idxI2c = MPU6050_BIND_Defs.indexOfCol(0, "I2C") + let idxAddr = MPU6050_BIND_Defs.indexOfCol(0, "addr") + + let name = MPU6050_BIND_Defs[i, idxName] + let i2c = getMt(MPU6050_BIND_Defs, i, idxI2c) + let addr = getMt(MPU6050_BIND_Defs, i, idxAddr) + + println("Create a MPU6050_BIND HVar: ${name}, use i2c interf: ${i2c}, i2c addr: ${addr}") + let i2c_HardVar = map[i2c] + let param = JsonObject() + param.put("mode", JsonString("get_fd")) + let ret = i2c_HardVar.play(param) + let fd = Int32(ret.asObject().get("fd").getOrThrow().asInt().getValue()) + let _addr = UInt8.parse(addr) + + let mpu6050 = MPU6050(fd, _addr) + + let mtvars_mpu6050bind = MtVars_MPU6050_BIND( + things: {json: JsonValue => + let ret = JsonObject() + ret.put("status", JsonString("OK")) + return ret + }, + rise: {=> + println("MPU6050_BIND HVar: ${name} is starting now") + mpu6050.setup() + mpu6050.startDaemon() + }, + free: {=> + println("MPU6050_BIND HVar: ${name} is closing now") + mpu6050.stopDaemon() + mpu6050.waitDaemon() + } + ) + + return mtvars_mpu6050bind + } +} + +/* -------- 舵机变量类 + * -------- + * -------- TODO(20260515-qing): 现在普通的舵机类也是传入回调函数的, + * -------- 但是我们考虑到该硬件变量调用别的硬件变量,所以还是要专门做一个舵机硬件变量 + * -------- + * -------- TODO(20260612-qing): 根据Lua硬件脚本的返回结果,调用别的硬件变量 + * -------- */ +class MtVars_Servo <: MatrixVars_Hardware { + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None ) + { super(things: things, rise: rise, free: free) } + + /* + * @Servo + * name max min offset controller channel; + * s1 90 - - pca9685-0 0; + * @ + */ + public static func Factory( /* 工厂模式:使用矩阵对象构造 */ + ServoDefs: MatrixObject, i: Int64, map: HashMap)//: MtVars_Servo + { + let idxName = ServoDefs.indexOfCol(0, "name") + let idxMax = ServoDefs.indexOfCol(0, "max") + let idxMin = ServoDefs.indexOfCol(0, "min") + let idxOffset = ServoDefs.indexOfCol(0,"offset") + let idxCtrl = ServoDefs.indexOfCol(0, "controller") + let idxChannel = ServoDefs.indexOfCol(0, "channel") + + let name = ServoDefs[i, idxName] + let max = parseMt(ServoDefs, i, idxMax) + let min = parseMt (ServoDefs,i, idxMin) + let offset = parseMt(ServoDefs, i, idxOffset) + let ctrl = getMt(ServoDefs, i, idxCtrl) + let channel = parseMt(ServoDefs, i, idxChannel) + + print("Create a Servo hard-Var: ${name}, max: ${max}, min: ${min}, offset: ${offset}") + println(", controller: ${ctrl}, channel: ${channel}") + + let controller = map[ctrl] /* like pca9685 */ + let servo = Servo(max: max, min: min, offset: offset, + callback_setAngle: {angle: Float32 => + let obj = JsonObject() + obj.put("channel", JsonInt(Int64(channel))) + //obj.put("angle", JsonFloat(Float64(angle))) + obj.put("angle", JsonInt(Int64(angle))) + controller.play(obj) + }) + + /* + * {"angle": 180.0} + */ + let mtvars_servo = MtVars_Servo(things: {json=> + let angle = json.asObject().get("angle").getOrThrow().asFloat().getValue() + servo.setAngle(Float32(angle)) + let ret = JsonObject() + ret.put("status", JsonString("OK")) + return ret + }) + return mtvars_servo + } +} + +/* -------- 扬声器类 + * -------- + * -------- 使用工厂函数 + * -------- + * -------- TODO: 输入扬声器设备名,扬声器又是一个复杂的功能 + * -------- + */ +class MtVars_Speaker <: MatrixVars_Hardware { + public init(things!: (JsonValue) -> JsonValue, + rise!: Option<() -> Unit> = None, + free!: Option<() -> Unit> = None ) + { super(things: things, rise: rise, free: free) } + + /* --------| 工厂函数,根据矩阵对象进行构造 + * --------| @Speaker + * --------| name channels sample_rate volumn max_f min_f; + * --------| spk 2 48000 0.6 5000 -; + * --------| @ + */ + public static func Factory( + SpeakerDefs: MatrixObject, i: Int64, map: HashMap): MtVars_Speaker { + + let idxName = SpeakerDefs.indexOfCol(0, "name") + let idxChannels = SpeakerDefs.indexOfCol(0, "channels") + let idxSampleRate = SpeakerDefs.indexOfCol(0, "sample_rate") + let idxVolumn = SpeakerDefs.indexOfCol(0, "volumn") + let idxMax_f = SpeakerDefs.indexOfCol(0, "max_f") + let idxMin_f = SpeakerDefs.indexOfCol(0, "min_f") + + let name = SpeakerDefs[i, idxName] + let channels = parseMt(SpeakerDefs, i, idxChannels) + let sample_rate = parseMt(SpeakerDefs, i, idxSampleRate) + let volume = parseMt(SpeakerDefs, i, idxVolumn) + let max_f = parseMt(SpeakerDefs, i, idxMax_f) + let min_f = parseMt(SpeakerDefs, i, idxMin_f) + + println("Create a Speaker: ${name}, channels: ${channels}, sample_rate: ${sample_rate}, volume: $(volume), max_f: ${max_f}, min_f: ${min_f}") + let spk = Spk(channels: channels, sample_rate: sample_rate) + let mtvar_spk = MtVars_Speaker(things: {json => // {"frequency": 1000.0, "delay": 400} + let json_obj = json.asObject() + var freq = Float32(json_obj.get("frequency").getOrThrow().asFloat().getValue()) + if (freq > max_f) { freq = max_f } + if (freq < min_f) { freq = min_f } + let delay = Int32(json_obj.get("delay").getOrThrow().asFloat().getValue()) + spk.play(freq, volume, delay) + let ret = JsonObject() /* ---- FIXME: 处理异常情况 */ + ret.put("status", JsonString("OK")) + return ret + }) + + return mtvar_spk + } +} + + + +/* -------- 机器人结构类 + * -------- + * -------- 机器人结构是动态的,从脚本中解析的 + * -------- TODO(20260514-qing): 调整为输入MatrixObject + * -------- TODO(20260515-qing): 需要重写一遍硬件类(done) + * -------- TODO(20260518-qing): 使用神经网络RNN直接根据动作+传感器脚本生成下一步动作 + * -------- TODO(20260527-qing): 增加硬件模块行为脚本 + * -------- TODO*(20260527-qing): 为@Action表演矩阵对象增加位置指示器,以增量行动 + * -------- */ +class RobotStruct { + + /* -------- 存放解析结果和创建的变量 + * -------- */ + private let C = LinkedList() + private let M = HashMap() + + /* -------- 使用矩阵脚本驱动机器人 + * -------- + * -------- 不行就将矩阵脚本转化为bot脚本吧 + * -------- */ + public init(input: InputStream) { + readMatrix(input) + createHardVars() + } + + /* -------- 激活所有硬件变量 + * -------- */ + public func active() { + for (item in M.values()) { + item.build() + } + } + + /* -------- 消除所有硬件变量 + * -------- + * -------- INFO(20260517-qing): 它需要以倒序方式进行清除 + * -------- */ + public func deactive() { + let items = M.values().toArray() + for (i in items.size-1..=0:-1) { + items[i].destroy() + } + } + + /* -------- 执行@Action矩阵中的内容 + * -------- */ + public func run() { + run_drl_action() + } + + /* -------- 储存整个矩阵变量表 + * -------- + * -------- NOTE(20260517-qing): 使用完毕要关闭输出流 + * -------- */ + public func save(out: OutputStream) { + let ms = MatrixSaver(out) + ms.save(C) + } + + /* -------- 阅读矩阵脚本 + * -------- + * -------- TODO(20260504-qing): 使用矩阵脚本存储神经网络模型 + * -------- */ + private func readMatrix(file: InputStream){ + let sr = StringReader(file) + let parser = MatrixParser(sr) + while (true) { + if (let Some(O) <- parser.parse()) { + let n = O.getName(); + println(n) + C.addLast(O) } + else { break } + } + } + + /* -------- 创建硬件变量字典 + * -------- + * -------- TODO(20260515-qing): 解耦合 + * -------- FIXME(20260518-qing): 在插入哈希表之前如果有已经存在的键值,要释放它的资源 + * -------- */ + private func createHardVars() + { + + for (matrix in C) { + let MtName = matrix.getName() + + /* ---- 【特殊的】I2C硬件 + * ---- */ + if (MtName == "I2C") { + let idxName = matrix.indexOfCol(0,"name") + for (i in 1..matrix.size) { + let name = matrix[i, idxName] + M[name] = MtVars_I2C.Factory(matrix, i, M) + } + } + + /* ---- 【特殊的】Alias别名硬件 + * ---- */ + else if (MtName == "Alias") { + let idxName = matrix.indexOfCol(0, "name") + for (i in 1..matrix.size) { + let name = matrix[i, idxName] + M[name] = MtVars_Alias.Factory(matrix, i, M) + } + } + + /* ---- 【特殊的】Servo舵机硬件 + * ---- */ + else if (MtName == "Servo") { + let idxName = matrix.indexOfCol(0, "name") + for (i in 1..matrix.size) { + let name = matrix[i, idxName] + M[name] = MtVars_Servo.Factory(matrix, i, M) + } + } + + /* ---- [特殊的] Action矩阵 + * ---- */ + else if (MtName == "Action") { + ; + } + + /* ---- [特殊的] Speaker扬声器硬件 + * ---- */ + else if (MtName == "Speaker") { + let idxName = matrix.indexOfCol(0, "name") + for (i in 1..matrix.size) { + let name = matrix[i, idxName] + M[name] = MtVars_Speaker.Factory(matrix, i, M) + } + } + + /* ---- 【通用的】Lua脚本硬件 + * ---- */ + else if (matrix.size > 0) { + let idxName = matrix.indexOfCol(0, "name") + for (i in 1..matrix.size ) { + let obj = JsonObject() + let name = matrix[i, idxName] + for (j in 0..matrix.col) { + let colName = matrix[0, j] + let value = MatrixVars_Hardware.getMt(matrix, i, j) + /* ---- ---- + * ---- FIXME: 加入点号别名的检索逻辑 ---- + * ---- ---- */ + if (let Some(mt) <- M.get(value)) { + /* ---- ---- + * ---- 存在该名称的变量,传入空对象参数,并求取返回值 + * ---- ---- */ + let json = JsonObject() + let res = mt.play(json).asObject() + let fields: HashMap = res.getFields() + for (k in fields.keys()) { + let val = fields.get(k).getOrThrow() + obj.put(k, val) + } + + } + else { + obj.put(colName, JsonString(value)) + } + } + + /* ---- fixme: 这里的脚本路径是写死的,随后我们需要设计一个机制,从环境变量中查找 + * ---- */ + M[name] = MtVars_Lua.Factory("./modules/${MtName}.lua", obj) + } + } + } + } + + + /* -------- 在矩阵对象的容器中寻找目标 + * -------- */ + private static func findMt(ll: LinkedList, name: String): Option + { + for (item in ll) { + var target = item.getName() + target = target.trimAscii() // ??? + if (name == target) { + return item } + } + + return None + } + + /* -------- 使用drl微调动作,使用RNN生成动作流 + * -------- */ + private func run_drl_action() { + let ActionDefs = findMt(C, "Action").getOrThrow() /* MatrixObject */ + for (i in 1..ActionDefs.size) { + let set =JsonObject() /* 每个硬件变量的参数集合 */ + for (j in 0..ActionDefs.col) { + parse_action_matrixObject(set, ActionDefs, i, j) + } + + /* -------- 执行一行 -------- */ + let keys = set.getFields().keys() + for (name_dev in keys) { + let hv = M[name_dev] + let params: JsonValue = set.get(name_dev).getOrThrow() + let _: JsonValue = hv.play(params) + } + + + } + } + + /* -------- 解析@Action矩阵对象 -------- */ + private func parse_action_matrixObject(set: JsonObject, ActionDefs: MatrixObject, + i: Int64, j: Int64) { + let name_HV = ActionDefs[0, j] /* 获取硬件变量名 FIXME: 该名称不得使用占位符 且不重复 */ + let arr = name_HV.split(".") + if (arr.size != 2) { + throw IllegalArgumentException("Action column is not valid") } + let name_dev = arr[0] + var name_param = arr[1] + + var val_fp64 = MatrixVars_Hardware.parseMt(ActionDefs, i, j) + + let AliasHV = M.get(name_param) /* 可能的参数别名转换 */ + if (let Some(aliasHV) <- AliasHV) { + let ret = aliasHV.play(JsonObject()) + let alias = ret.asString().getValue() + name_param = alias + } + + var set_params: JsonObject /* 设置或者取得属于该硬件变量的参数 */ + if (let Some(obj_) <- set.get(name_dev)) { + set_params = obj_.asObject() } + else { + set_params = JsonObject() + set.put(name_dev, set_params) } + + set_params.put(name_param, JsonFloat(val_fp64)) /* 设置一个该硬件变量的参数 */ + } +} + + diff --git a/bot4cj/src/testCNN.cj b/bot4cj/src/testCNN.cj new file mode 100644 index 0000000..8b91fef --- /dev/null +++ b/bot4cj/src/testCNN.cj @@ -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, Array) { + 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() +} \ No newline at end of file diff --git a/bot4cj/src/testCNN1.cj b/bot4cj/src/testCNN1.cj new file mode 100644 index 0000000..0b4fe8c --- /dev/null +++ b/bot4cj/src/testCNN1.cj @@ -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, Array) { + 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() +} \ No newline at end of file diff --git a/bot4cj/test/ads1115_linux.c b/bot4cj/test/ads1115_linux.c new file mode 100644 index 0000000..cea9521 --- /dev/null +++ b/bot4cj/test/ads1115_linux.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================ +// I2C 配置 +// ============================================================ +#define I2C_DEVICE "/dev/i2c-1" // I2C 总线 (树莓派是 i2c-1) +#define ADS1115_ADDR 0x48 // ADDR 接 GND 时默认地址 + +// ============================================================ +// ADS1115 寄存器 +// ============================================================ +#define ADS1115_REG_CONVERSION 0x00 // 转换结果 (只读) +#define ADS1115_REG_CONFIG 0x01 // 配置寄存器 (读/写) +#define ADS1115_REG_LO_THRESH 0x02 // 低阈值比较 +#define ADS1115_REG_HI_THRESH 0x03 // 高阈值比较 + +// ============================================================ +// ADS1115 配置寄存器 bit 定义 (16 位) +// ============================================================ +// BIT15: OS[0] — 单次转换启动位 (1=开始) +// BIT14-12: MUX[2:0] — 输入多路选择 +// BIT11-9: PGA[2:0] — 可编程增益放大器 +// BIT8: MODE — 0=连续转换, 1=单次(省电) +// BIT7-5: DR[2:0] — 数据速率 +// BIT4: COMP_MODE — 比较模式 (0=传统) +// BIT3: COMP_POL — 比较极性 (0=低有效) +// BIT2: COMP_LAT — 比较锁存 (0=不锁存) +// BIT1-0: COMP_QUE — 比较器队列 (11=禁用) + +// --- MUX 输入通道 --- +#define ADS1115_MUX_AIN0_AIN1 0x0000 // 差分: P=AIN0, N=AIN1 (默认) +#define ADS1115_MUX_AIN0_AIN3 0x1000 // 差分: P=AIN0, N=AIN3 +#define ADS1115_MUX_AIN1_AIN3 0x2000 // 差分: P=AIN1, N=AIN3 +#define ADS1115_MUX_AIN2_AIN3 0x3000 // 差分: P=AIN2, N=AIN3 +#define ADS1115_MUX_AIN0_GND 0x4000 // 单端: AIN0 +#define ADS1115_MUX_AIN1_GND 0x5000 // 单端: AIN1 +#define ADS1115_MUX_AIN2_GND 0x6000 // 单端: AIN2 +#define ADS1115_MUX_AIN3_GND 0x7000 // 单端: AIN3 + +// --- PGA 增益 (满量程范围) --- +#define ADS1115_PGA_6_144V 0x0000 // ±6.144V (1LSB=0.1875mV) +#define ADS1115_PGA_4_096V 0x0200 // ±4.096V (1LSB=0.125mV) +#define ADS1115_PGA_2_048V 0x0400 // ±2.048V (1LSB=0.0625mV) (默认) +#define ADS1115_PGA_1_024V 0x0600 // ±1.024V (1LSB=0.03125mV) +#define ADS1115_PGA_0_512V 0x0800 // ±0.512V (1LSB=0.015625mV) +#define ADS1115_PGA_0_256V 0x0A00 // ±0.256V (1LSB=0.0078125mV) + +// --- 数据速率 --- +#define ADS1115_DR_8SPS 0x0000 // 8 SPS +#define ADS1115_DR_16SPS 0x0020 // 16 SPS +#define ADS1115_DR_32SPS 0x0040 // 32 SPS +#define ADS1115_DR_64SPS 0x0060 // 64 SPS +#define ADS1115_DR_128SPS 0x0080 // 128 SPS (默认) +#define ADS1115_DR_250SPS 0x00A0 // 250 SPS +#define ADS1115_DR_475SPS 0x00C0 // 475 SPS +#define ADS1115_DR_860SPS 0x00E0 // 860 SPS + +// --- 比较器队列 (COMP_QUE) --- +#define ADS1115_COMP_QUE_1 0x0000 // 1 次后触发 +#define ADS1115_COMP_QUE_2 0x0001 // 2 次后触发 +#define ADS1115_COMP_QUE_4 0x0002 // 4 次后触发 +#define ADS1115_COMP_QUE_DISABLE 0x0003 // 禁用比较器 (默认) + +// --- 操作模式 --- +#define ADS1115_MODE_CONTINUOUS 0x0000 // 连续转换 +#define ADS1115_MODE_SINGLE 0x0100 // 单次转换 (默认, 省电) + +// --- 启动转换 --- +#define ADS1115_OS_START 0x8000 // 启动单次转换 + +// ============================================================ +// 结构体: ADS1115 配置上下文 +// ============================================================ +typedef struct { + int fd; // I2C 文件描述符 + uint8_t addr; // I2C 设备地址 + uint16_t config; // 当前配置值 + double fs_voltage; // 满量程电压 (取决于 PGA) +} ads1115_t; + +// ============================================================ +// PGA 对应的满量程电压 (单位: V) +// ============================================================ +static const double pga_fs[] = { + [0] = 6.144, // ADS1115_PGA_6_144V + [1] = 4.096, // ADS1115_PGA_4_096V + [2] = 2.048, // ADS1115_PGA_2_048V + [3] = 1.024, // ADS1115_PGA_1_024V + [4] = 0.512, // ADS1115_PGA_0_512V + [5] = 0.256, // ADS1115_PGA_0_256V +}; + +// ============================================================ +// PGA 增益对应的满量程电压名 +// ============================================================ +static const char *pga_names[] = { + [0] = "±6.144V", + [1] = "±4.096V", + [2] = "±2.048V", + [3] = "±1.024V", + [4] = "±0.512V", + [5] = "±0.256V", +}; + +// ============================================================ +// 数据速率字符串 +// ============================================================ +static const char *dr_names[] = { + [0] = "8 SPS", + [1] = "16 SPS", + [2] = "32 SPS", + [3] = "64 SPS", + [4] = "128 SPS", + [5] = "250 SPS", + [6] = "475 SPS", + [7] = "860 SPS", +}; + +// ============================================================ +// 毫秒延时 +// ============================================================ +static void delay_ms(int ms) +{ + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + nanosleep(&ts, NULL); +} + +// ============================================================ +// 微秒延时 +// ============================================================ +static void delay_us(int us) +{ + struct timespec ts; + ts.tv_sec = us / 1000000; + ts.tv_nsec = (us % 1000000) * 1000L; + nanosleep(&ts, NULL); +} + +// ============================================================ +// I2C 初始化 +// ============================================================ +static int i2c_init(ads1115_t *dev, const char *i2c_dev, uint8_t addr) +{ + dev->fd = open(i2c_dev, O_RDWR); + if (dev->fd < 0) { + perror("打开 I2C 设备失败"); + return -1; + } + + if (ioctl(dev->fd, I2C_SLAVE, addr) < 0) { + perror("设置 I2C 从机地址失败"); + close(dev->fd); + dev->fd = -1; + return -1; + } + + dev->addr = addr; + dev->config = 0; + printf("I2C 初始化完成: 设备=%s, 地址=0x%02X\n", i2c_dev, addr); + return 0; +} + +// ============================================================ +// I2C 写 2 字节 (大端) +// ============================================================ +static int i2c_write_word(ads1115_t *dev, uint8_t reg, uint16_t value) +{ + uint8_t buf[3]; + buf[0] = reg; + buf[1] = (value >> 8) & 0xFF; // 高字节 + buf[2] = value & 0xFF; // 低字节 + + if (write(dev->fd, buf, 3) != 3) { + perror("I2C 写失败"); + return -1; + } + return 0; +} + +// ============================================================ +// I2C 读 2 字节 (大端) +// ============================================================ +static int i2c_read_word(ads1115_t *dev, uint8_t reg, uint16_t *value) +{ + // 先写入寄存器地址 + if (write(dev->fd, ®, 1) != 1) { + perror("I2C 设置寄存器地址失败"); + return -1; + } + + // 读取 2 字节 + uint8_t buf[2]; + if (read(dev->fd, buf, 2) != 2) { + perror("I2C 读失败"); + return -1; + } + + *value = ((uint16_t)buf[0] << 8) | buf[1]; + return 0; +} + +// ============================================================ +// 写配置寄存器 +// ============================================================ +static int ads1115_write_config(ads1115_t *dev, uint16_t config) +{ + dev->config = config; + return i2c_write_word(dev, ADS1115_REG_CONFIG, config); +} + +// ============================================================ +// 读配置寄存器 +// ============================================================ +static int ads1115_read_config(ads1115_t *dev, uint16_t *config) +{ + return i2c_read_word(dev, ADS1115_REG_CONFIG, config); +} + +// ============================================================ +// 读转换结果寄存器 +// ============================================================ +static int ads1115_read_conversion(ads1115_t *dev, int16_t *value) +{ + uint16_t raw; + int ret = i2c_read_word(dev, ADS1115_REG_CONVERSION, &raw); + if (ret == 0) { + *value = (int16_t)raw; + } + return ret; +} + +// ============================================================ +// 读阈值寄存器 +// ============================================================ +static int ads1115_read_thresh(ads1115_t *dev, int16_t *lo, int16_t *hi) +{ + uint16_t raw_lo, raw_hi; + if (i2c_read_word(dev, ADS1115_REG_LO_THRESH, &raw_lo) < 0) return -1; + if (i2c_read_word(dev, ADS1115_REG_HI_THRESH, &raw_hi) < 0) return -1; + *lo = (int16_t)raw_lo; + *hi = (int16_t)raw_hi; + return 0; +} + +// ============================================================ +// 写阈值寄存器 +// ============================================================ +static int ads1115_write_thresh(ads1115_t *dev, int16_t lo, int16_t hi) +{ + if (i2c_write_word(dev, ADS1115_REG_LO_THRESH, (uint16_t)lo) < 0) return -1; + if (i2c_write_word(dev, ADS1115_REG_HI_THRESH, (uint16_t)hi) < 0) return -1; + return 0; +} + +// ============================================================ +// 初始化 ADS1115 +// +// pga_idx: 0~5 对应 ADS1115_PGA_xxx +// dr_idx: 0~7 对应 ADS1115_DR_xxx +// ============================================================ +static int ads1115_init(ads1115_t *dev, int pga_idx, int dr_idx) +{ + if (pga_idx < 0 || pga_idx > 5) pga_idx = 2; // 默认 ±2.048V + if (dr_idx < 0 || dr_idx > 7) dr_idx = 4; // 默认 128SPS + + dev->fs_voltage = pga_fs[pga_idx]; + + // 构建配置: + // - 单次转换模式 + // - 禁用比较器 + // - 默认通道 AIN0-GND + uint16_t config = ADS1115_MUX_AIN0_GND // 单端 AIN0 + | (pga_idx << 9) // PGA + | ADS1115_MODE_SINGLE // 单次模式 + | (dr_idx << 5) // 数据速率 + | ADS1115_COMP_QUE_DISABLE; // 禁用比较器 + + int ret = ads1115_write_config(dev, config); + if (ret < 0) return ret; + + printf("ADS1115 初始化完成: "); + printf("PGA=%s, 速率=%s, 满量程=%.3fV\n", + pga_names[pga_idx], dr_names[dr_idx], dev->fs_voltage); + return 0; +} + +// ============================================================ +// 读取单端通道 (单次转换) +// ch: 0~3 对应 AIN0~AIN3 +// ============================================================ +static double ads1115_read_single(ads1115_t *dev, int ch) +{ + if (ch < 0 || ch > 3) { + fprintf(stderr, "通道号无效: %d (有效: 0~3)\n", ch); + return 0.0; + } + + // 1. 重写配置: 设置通道 + 启动转换 + uint16_t mux = ADS1115_MUX_AIN0_GND | (ch << 12); // AINch-GND + uint16_t config = (dev->config & ~0x7000) // 清除 MUX 位 + | mux + | ADS1115_OS_START; // 启动单次转换 + + ads1115_write_config(dev, config); + + // 2. 等待转换完成 (OS 位被硬件清 0) + // 根据数据速率等待足够时间 + uint16_t dr_idx = (config >> 5) & 0x07; + int wait_ms = 1000 / (8 << dr_idx) + 1; // 保守估计 + delay_ms(wait_ms + 2); + + // 3. 读转换值 + int16_t raw = 0; + if (ads1115_read_conversion(dev, &raw) < 0) { + return 0.0; + } + + // 4. 转换为电压 + // ADS1115 16位有符号, 满量程 = FS_VOLTAGE + // V = raw * FS_VOLTAGE / 32768 + double voltage = (double)raw * dev->fs_voltage / 32768.0; + return voltage; +} + +// ============================================================ +// 读取差分通道 (单次转换) +// pos: 正极通道 (0~3) +// neg: 负极通道 (0~3) +// ============================================================ +static double ads1115_read_diff(ads1115_t *dev, int pos, int neg) +{ + if (pos < 0 || pos > 3 || neg < 0 || neg > 3) { + fprintf(stderr, "通道号无效\n"); + return 0.0; + } + + // 构建 MUX 值 (差分) + // AIN0-AIN1: 0, AIN0-AIN3: 1, AIN1-AIN3: 2, AIN2-AIN3: 3 + uint16_t mux_val; + if (pos == 0 && neg == 1) mux_val = 0; + else if (pos == 0 && neg == 3) mux_val = 1; + else if (pos == 1 && neg == 3) mux_val = 2; + else if (pos == 2 && neg == 3) mux_val = 3; + else { + fprintf(stderr, "不支持的差分组合: AIN%d - AIN%d\n", pos, neg); + fprintf(stderr, "支持: 0-1, 0-3, 1-3, 2-3\n"); + return 0.0; + } + + uint16_t mux = mux_val << 12; + uint16_t config = (dev->config & ~0x7000) + | mux + | ADS1115_OS_START; + + ads1115_write_config(dev, config); + + uint16_t dr_idx = (config >> 5) & 0x07; + int wait_ms = 1000 / (8 << dr_idx) + 1; + delay_ms(wait_ms + 2); + + int16_t raw = 0; + if (ads1115_read_conversion(dev, &raw) < 0) { + return 0.0; + } + + double voltage = (double)raw * dev->fs_voltage / 32768.0; + return voltage; +} + +// ============================================================ +// 连续模式读数 (需先配置为连续模式) +// ============================================================ +static int ads1115_start_continuous(ads1115_t *dev, int ch) +{ + if (ch < 0 || ch > 3) return -1; + + uint16_t config = (dev->config & ~0x7100) // 清除 MUX+MODE 位 + | (ADS1115_MUX_AIN0_GND | (ch << 12)) // 通道 + | ADS1115_MODE_CONTINUOUS // 连续模式 + | ADS1115_COMP_QUE_DISABLE; + + return ads1115_write_config(dev, config); +} + +// ============================================================ +// 连续模式读数 +// ============================================================ +static double ads1115_read_continuous(ads1115_t *dev) +{ + int16_t raw = 0; + if (ads1115_read_conversion(dev, &raw) < 0) { + return 0.0; + } + return (double)raw * dev->fs_voltage / 32768.0; +} + +// ============================================================ +// 扫描所有单端通道 +// ============================================================ +static void ads1115_scan_all(ads1115_t *dev) +{ + printf("\n===== 单端通道扫描 =====\n"); + const char *ch_names[] = {"AIN0", "AIN1", "AIN2", "AIN3"}; + for (int ch = 0; ch < 4; ch++) { + double v = ads1115_read_single(dev, ch); + printf(" %s: %+.6f V\n", ch_names[ch], v); + } +} + +// ============================================================ +// 打印当前配置 +// ============================================================ +static void ads1115_print_config(ads1115_t *dev) +{ + uint16_t config; + if (ads1115_read_config(dev, &config) < 0) return; + + int mux = (config >> 12) & 0x07; + int pga = (config >> 9) & 0x07; + int mode = (config >> 8) & 0x01; + int dr = (config >> 5) & 0x07; + int comp = config & 0x03; + + printf("\n===== ADS1115 寄存器 =====\n"); + printf(" 配置寄存器: 0x%04X\n", config); + printf(" MUX: %d (", mux); + switch (mux) { + case 0: printf("AIN0-AIN1"); break; + case 1: printf("AIN0-AIN3"); break; + case 2: printf("AIN1-AIN3"); break; + case 3: printf("AIN2-AIN3"); break; + case 4: printf("AIN0-GND"); break; + case 5: printf("AIN1-GND"); break; + case 6: printf("AIN2-GND"); break; + case 7: printf("AIN3-GND"); break; + } + printf(")\n"); + printf(" PGA: %d -> %s (满量程 %.3fV)\n", pga, + (pga <= 5) ? pga_names[pga] : "未知", + (pga <= 5) ? pga_fs[pga] : 0); + printf(" 模式: %s\n", mode ? "单次" : "连续"); + printf(" 速率: %d -> %s\n", dr, dr_names[dr & 0x07]); + printf(" 比较器: %s\n", comp == 3 ? "禁用" : "启用"); + + // 读阈值 + int16_t lo, hi; + if (ads1115_read_thresh(dev, &lo, &hi) == 0) { + printf(" 阈值: LO=%d (%.6fV), HI=%d (%.6fV)\n", + lo, (double)lo * dev->fs_voltage / 32768.0, + hi, (double)hi * dev->fs_voltage / 32768.0); + } +} + +// ============================================================ +// 打印转换结果 +// ============================================================ +static void print_reading(const char *label, double voltage) +{ + printf(" %s: %+.6f V (%.3f mV)\n", label, voltage, voltage * 1000.0); +} + +// ============================================================ +// 打印帮助 +// ============================================================ +static void print_help(const char *prog) +{ + printf("\n用法: %s [I2C地址] [PGA] [数据速率]\n", prog); + printf("\n参数 (可选):\n"); + printf(" I2C地址 0x48~0x4B (默认 0x48)\n"); + printf(" PGA 0~5 (默认 2 = ±2.048V)\n"); + printf(" 0=±6.144V, 1=±4.096V, 2=±2.048V\n"); + printf(" 3=±1.024V, 4=±0.512V, 5=±0.256V\n"); + printf(" 数据速率 0~7 (默认 4 = 128SPS)\n"); + printf(" 0=8, 1=16, 2=32, 3=64\n"); + printf(" 4=128, 5=250, 6=475, 7=860 (SPS)\n"); + printf("\n示例:\n"); + printf(" %s\n", prog); + printf(" %s 0x49\n", prog); + printf(" %s 0x48 2 6\n", prog); +} + +// ============================================================ +// 主函数 +// ============================================================ +int main(int argc, char *argv[]) +{ + ads1115_t dev; + uint8_t addr = ADS1115_ADDR; + int pga = 2; // ±2.048V + int dr = 4; // 128 SPS + + // 解析命令行参数 + if (argc >= 2) { + if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + print_help(argv[0]); + return 0; + } + addr = (uint8_t)strtol(argv[1], NULL, 0); + } + if (argc >= 3) pga = atoi(argv[2]); + if (argc >= 4) dr = atoi(argv[3]); + + printf("ADS1115 Linux I2C 采集示例\n"); + printf("============================\n\n"); + + // 1. 初始化 I2C + if (i2c_init(&dev, I2C_DEVICE, addr) < 0) { + return 1; + } + + // 2. 初始化 ADS1115 + if (ads1115_init(&dev, pga, dr) < 0) { + close(dev.fd); + return 1; + } + + // 3. 单通道测试 (AIN0) + printf("\n===== 单通道测试 (AIN0, 连续读 10 次) =====\n"); + for (int i = 0; i < 10; i++) { + double v = ads1115_read_single(&dev, 0); + char label[32]; + snprintf(label, sizeof(label), "[%d]", i); + print_reading(label, v); + delay_ms(10); + } + + // 4. 扫描所有单端通道 + ads1115_scan_all(&dev); + + // 5. 差分模式示例 + printf("\n===== 差分测试 (AIN0 - AIN1) =====\n"); + for (int i = 0; i < 5; i++) { + double v = ads1115_read_diff(&dev, 0, 1); + char label[32]; + snprintf(label, sizeof(label), "[%d]", i); + print_reading(label, v); + delay_ms(10); + } + + // 6. 连续模式测试 + printf("\n===== 连续模式测试 (AIN0, 5 次) =====\n"); + if (ads1115_start_continuous(&dev, 0) == 0) { + delay_ms(50); // 等第一次转换完成 + for (int i = 0; i < 5; i++) { + double v = ads1115_read_continuous(&dev); + char label[32]; + snprintf(label, sizeof(label), "[%d] 连续", i); + print_reading(label, v); + delay_ms(10); + } + } else { + printf(" 启动连续模式失败\n"); + } + + // 7. 打印配置信息 + ads1115_print_config(&dev); + + // 8. 清理 + close(dev.fd); + printf("\n程序结束.\n"); + + return 0; +} diff --git a/bot4cj/test/ads1256_linux.c b/bot4cj/test/ads1256_linux.c new file mode 100644 index 0000000..41a0c64 --- /dev/null +++ b/bot4cj/test/ads1256_linux.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================ +// 配置参数 - 按需修改 +// ============================================================ +#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; +} diff --git a/bot4cj/test/labeled.py b/bot4cj/test/labeled.py new file mode 100644 index 0000000..bd82f9e --- /dev/null +++ b/bot4cj/test/labeled.py @@ -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('<>', 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('', self.on_mouse_down) + self.canvas.bind('', self.on_mouse_move) + self.canvas.bind('', self.on_mouse_up) + self.canvas.bind('', self.on_pan_start) + self.canvas.bind('', self.on_pan_move) + self.canvas.bind('', self.on_pan_end) + self.root.bind('', lambda e: self.delete_selected_annotation()) + self.root.bind('', lambda e: self.save_annotations()) + self.root.bind('', 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() \ No newline at end of file diff --git a/bot4cj/test/lua.c b/bot4cj/test/lua.c new file mode 100644 index 0000000..126d98f --- /dev/null +++ b/bot4cj/test/lua.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +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; +} diff --git a/bot4cj/test/t.c b/bot4cj/test/t.c new file mode 100644 index 0000000..5866753 --- /dev/null +++ b/bot4cj/test/t.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +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); +} diff --git a/bot4cj/test/t.lua b/bot4cj/test/t.lua new file mode 100644 index 0000000..0b293bc --- /dev/null +++ b/bot4cj/test/t.lua @@ -0,0 +1,3 @@ +print("Hello from script file!") +name = "Lua" +print("Welcome to " .. name) diff --git a/bot4cj/test/tt.lua b/bot4cj/test/tt.lua new file mode 100644 index 0000000..86ebd7c --- /dev/null +++ b/bot4cj/test/tt.lua @@ -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)