移植bot4cj

This commit is contained in:
qing committed 2026-07-28 12:44:07 +08:00
1 parent 7803843155
commit 78ad3e6d57
76 files changed
+14468

No files matched your search

+38
View File
@@ -0,0 +1,38 @@
# prjs
nn/
bot
bot?
test_*
.git/
.vscode/
.idea/
.cache/
#
*.so
*.o
*.cjo
*.dll
*.exe
*.lib
*.exp
main
# cangjie
build/
target/
output/
module-resolve.json
module-lock.json
cjpm.lock
.cjpm-history
*.macrocall
*.gcda
*.gcno
# 其他
log
report
tmp
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 卿松
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+60
View File
@@ -0,0 +1,60 @@
MODE ?= cpu
VERSION := 0.0.2
APP_NAME := bot
CC:= gcc
CXX:= g++
NVCC:= nvcc
LIBS := -lpthread
FLAGS := -O3 -pipe #-Wall -Werror
#-----------
all: libi2c4cj.so libsnd4cj.so libmt4cj.so libfft4cj.so #libcnn4cj.so
# I2C
libi2c4cj.so: src/cpp/I2C.c
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
# SOUND
libsnd4cj.so: src/cpp/snd.cpp
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
# cpu nn
ifeq ($(MODE), cpu)
libmt4cj.so: src/cpp/Matrix.cpp
$(CC) $(FLAGS) -shared -fPIC $^ -o $@
endif
ifeq ($(MODE), cuda)# GPU矩阵
libmt4cj.so: src/cuda/matrix.cu
#$(NVCC) -arch=compute_61 -code=sm_61 -O3 -Xcompiler -fPIC -shared src/cuda/matrix.cu -o libmt4cj.so
$(NVCC) -fPIC -shared src/cuda/matrix.cu -o libmt4cj.so
endif
# 快速傅里叶变换fft
libfft4cj.so: src/cpp/fft1.c src/cpp/fft.cpp
$(CC) $(FLAGS) -shared -fPIC $^ -lm -o $@
# 卷积神经网络cnn
libcnn4cj.so: src/cpp/cnn/cnn_i.cpp conv.o pooling.o ga_pooling.o flatten.o imgl.o
$(CC) $(FLAGS) -std=c++17 -shared -fPIC $^ -lm -o $@
conv.o: src/cpp/cnn/conv.cpp
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
pooling.o: src/cpp/cnn/pooling.cpp
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
ga_pooling.o: src/cpp/cnn/ga_pooling.cpp
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
flatten.o: src/cpp/cnn/flatten.cpp
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
imgl.o: src/cpp/cnn/imgl.cpp
$(CC) $(FLAGS) -shared -fPIC -c $^ -o $@
clear:
rm -f *.o
rm -f *.so
+134
View File
@@ -0,0 +1,134 @@
# bot4cj
bot4cj是基于仓颉和 Linux I2C 接口的机器人硬件脚本解析器。
## 依赖库
libc、libpthread、liblua-5.4
You may need this
``` bash
sudo ln -s /usr/lib/aarch64-linux-gnu/liblua5.4.so /usr/lib/aarch64-linux-gnu/liblua.so
sudo ln -s /usr/lib/x86_64-linux-gnu/liblua5.4.so /usr/lib/x86_64-linux-gnu/liblua.so
```
## 项目介绍
想要得到一个机器人软件通常比一个普通的软件更为困难,这是因为这个世界上的机器人五花八门,所以它们的软件往往都是独特的,所以我想写一种用于描述机器人硬件的通用的脚本。
`bot4cj` 基于仓颉编程语言和 Linux I2C 接口,提供了便捷的硬件控制能力,支持pca9685、mpu6050,我通常使用树莓派作为宿主机。
## 解析器
树莓派通过I2C接口连接pca9685后使用如下方法使用:
```bash
cat ss/test.mt | cjpm run # 蜘蛛机器人脚本
```
## 动作脚本
项目中包含的动作脚本在ss文件夹下面,这些动作脚本的适配机器人型号如下
```textplain
# PCA9685
1号舵机(s1) => 左下水平关节
2号舵机(s2) => 左下垂直关节
3号舵机(s3) => 右下水平关节
4号舵机(s4) => 右下垂直关节
5号舵机(s5) => 右上水平关节
6号舵机(s6) => 右上垂直关节
7号舵机(s7) => 左上水平关节
8号舵机(s8) => 左上垂直关节
9号舵机(s9) => 云台1
10号舵机(s10) => 云台2
扬声器(spk) => 仅负责延时
```
适用的机器人建模文件[仓库链接](https://github.com/gopsc/Dashun)
### 机器人脚本语法(.mt)
**.bot文件是旧版动作脚本**
机器人脚本是行列式表格,每列之间以一个或多个空格、制表符分隔,每行以分号(;)结尾。
每个矩阵对象以@name 行开头,@ 行结束。
示例:
```mt
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
# 定义I2C硬件
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
# 定义pca9685硬件
@PCA9685
name I2C path;
pca9685-0 i2c-1 0x40;
@
# 定义mpu6050硬件
@MPU6050
name I2C path;
mpu6050-0 i2c-1 0x69;
mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
# 定义扬声器硬件
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
# 定义舵机硬件
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵为硬件变量输入参数
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
90 - - - - - - - - - - 20; # left-behind
45 - - - - - - - - - 0 500; # stand
- - 90 - - - 45 - - - - 20; # right-behind
- - 45 - - - - - - - 200 500; # stand
- - - - 90 - - - - - 0 20; # right-front
- - - - 45 - - - - - - 500; # stand
- - - - - - 90 - - - - 20; # left-front
- - - - - - 45 - - - - 500; # stand
- - - - - - - - - - - 20; # stand
@
```
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
export cjStackSize=64MB
export cjHeapSize=64MB
exec cjpm run --run-args="$@"
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
cd "$(dirname "$0")" || exit
export cjHeapSize=16MB
cjpm update
exec cjpm build
+39
View File
@@ -0,0 +1,39 @@
[package]
cjc-version = "1.1.3"
name = "bot4cj"
description = "nothing here"
version = "0.0.3"
target-dir = ""
src-dir = ""
output-type = "executable"
compile-option = "-l c -l pthread -l lua" # Linux
override-compile-option = ""
link-option = ""
package-configuration = {}
[dependencies]
#ctype4cj = { git = "https://gitcode.com/qingss0/ctype4cj.git", branch="main" }
ctype4cj = "1.0.3"
#pthread4cj = { git = "https://gitcode.com/qingss0/pthread4cj.git", branch="main"}
pthread4cj = "1.0.0"
#spi4cj = { git = "https://gitcode.com/qingss0/spi4cj.git", branch="main"}
spi4cj = "1.0.0"
#lua4cj = { git = "https://gitcode.com/qingss0/lua4cj.git", branch="main" }
lua4cj = "1.0.0"
# 这种x86机器人开发板感觉不经常使用,我没用过,不知道里面的i2c调用是不是参数是一样的
[target.x86_64-unknown-linux-gnu]
[target.x86_64-unknown-linux-gnu.dependencies]
[target.x86_64-unknown-linux-gnu.bin-dependencies]
path-option = ["${CANGJIE_STDX_PATH}/linux_x86_64_cjnative/static/stdx"]
[target.aarch64-unknown-linux-gnu]
[target.aarch64-unknown-linux-gnu.dependencies]
[target.aarch64-unknown-linux-gnu.bin-dependencies]
path-option = ["${CANGJIE_STDX_PATH}/linux_aarch64_cjnative/static/stdx"]
# 为什么不支持Windows了?
# 因为新版我们加载了Linux的glibc,通常Windows中没有这个
#[target.x86_64-w64-mingw32]
# [target.x86_64-w64-mingw32.bin-dependencies]
# path-option = ["${CANGJIE_STDX_PATH}\\windows_x86_64_cjnative\\dynamic\\stdx"]
+11
View File
@@ -0,0 +1,11 @@
nvcc -shared -o libmt4cj.dll src/cuda/matrix.cu -allow-unsupported-compiler
#nvcc -shared -o libmt4cj.dll src/cpp/Matrix.cpp -allow-unsupported-compiler
nvcc -shared -o libi2c4cj.dll src/cpp/I2C.c -allow-unsupported-compiler
nvcc -shared -o libfft4cj.dll src/cpp/fft.cpp src/cpp/Matrix.cpp src/cpp/fft1.c -allow-unsupported-compiler
nvcc -shared -o conv.lib -c src/cpp/cnn/conv.cpp -allow-unsupported-compiler
nvcc -shared -o imgl.lib -c src/cpp/cnn/imgl.cpp -allow-unsupported-compiler
nvcc -shared -o pooling.lib -c src/cpp/cnn/pooling.cpp -allow-unsupported-compiler
nvcc -shared -o ga_pooling.lib -c src/cpp/cnn/ga_pooling.cpp -allow-unsupported-compiler
nvcc -shared -o flatten.lib -c src/cpp/cnn/flatten.cpp -allow-unsupported-compiler
nvcc -shared --std=c++17 -o libcnn4cj.dll src/cpp/cnn/cnn_i.cpp conv.lib imgl.lib pooling.lib ga_pooling.lib flatten.lib -allow-unsupported-compiler
nvcc -shared -o libsnd4cj.dll src/cpp/snd.cpp -allow-unsupported-compiler
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+325
View File
@@ -0,0 +1,325 @@
-- ADS1115模数转换器 硬件脚本(测试)
-- 这个函数是辅助用的,它能够打印一张表出来
function print_table(t, indent)
indent = indent or 0
for k, v in pairs(t) do
if type(v) == 'table' then
print(string.rep(' ', indent) .. k .. ":")
print_table(v, indent + 2)
else
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
end
end
end
ADS1115_ADDR = 0x48 -- ADS1115的I2C地址
ADS1115_REG_CONVERSION = 0x00 -- 转换结果(只读)
ADS1115_REG_CONFIG = 0x01 -- 配置寄存器(读/写)
ADS1115_REG_LO_THRESH = 0x02 -- 低阈值比较
ADS1115_REG_HI_THRESH = 0x03 -- 高阈值比较
-- ====================================
-- ADS1115 配置寄存器 bit 定义(16位)
-- ====================================
-- BIT15: OS[0] => 单次转换启动位(1=开始)
-- BIT14-12: MUX[2:0] => 输入多路选择
-- BIT11-9: PGA[2:0] => 可编程增益放大器
-- BIT8: MODE => 0=连续转换,1=单次(省电)
-- BIT7-5: DR[2:0] => 数据速率
-- BIT4: COMP_MODE => 比较模式(0=传统)
-- BIT3: COMP_POL => 比较极性(0=低有效)
-- BIT2: COMP_LAT => 比较锁存(0=不锁存)
-- BIT1-0: COMP_QUE => 比较器队列(11=禁用)
--
-- ==== MUX 输入通道 ====
ADS1115_MUX_AIN0_AIN1 = 0x0000 -- 差分:P=AIN0,N=AIN1(默认)
ADS1115_MUX_AIN0_AIN3 = 0x1000 -- 差分:P=AIN0,N=AIN3
ADS1115_MUX_AIN1_AIN3 = 0x2000 -- 差分:P=AIN1,N=AIN3
ADS1115_MUX_AIN2_AIN3 = 0x3000 -- 差分:P=AIN2,N=AIN3
ADS1115_MUX_AIN0_GND = 0x4000 -- 单端:AIN0
ADS1115_MUX_AIN1_GND = 0x5000 -- 单端:AIN1
ADS1115_MUX_AIN2_GND = 0x6000 -- 单端:AIN2
ADS1115_MUX_AIN3_GND = 0x7000 -- 单端:AIN3
-- ==== PGA 增益(满量程范围)====
ADS1115_PGA_6_144V = 0x0000 -- ±6.144V (1LSB=0.1875mV)
ADS1115_PGA_4_096V = 0x0200 -- ±4.096V (1LSB=0.125mV)
ADS1115_PGA_2_048V = 0x0400 -- ±2.048V (1LSB=0.0625mV)(默认)
ADS1115_PGA_1_024V = 0x0600 -- ±1.024V (1LSB=0.03125mV)
ADS1115_PGA_0_512V = 0x0800 -- ±0.512V (1LSB=0.015625mV)
ADS1115_PGA_0_256V = 0x0A00 -- ±0.256V (1LSB=0.0078125mV)
-- ==== 数据速率 ====
ADS1115_DR_8SPS = 0x0000 -- 8 SPS
ADS1115_DR_16SPS = 0x0020 -- 16 SPS
ADS1115_DR_32SPS = 0x0040 -- 32 SPS
ADS1115_DR_64SPS = 0x0060 -- 64 SPS
ADS1115_DR_128SPS = 0x0080 -- 128 SPS (默认)
ADS1115_DR_250SPS = 0x00A0 -- 250 SPS
ADS1115_DR_475SPS = 0x00C0 -- 475 SPS
ADS1115_DR_860SPS = 0x00E0 -- 860 SPS
-- ==== 比较器队列(COMP_QUE)====
ADS1115_COMP_QUE_1 = 0x0000 -- 1次后触发
ADS1115_COMP_QUE_2 = 0x0001 -- 2次后触发
ADS1115_COMP_QUE_4 = 0x0002 -- 4次后触发
ADS1115_COMP_QUE_DISABLE = 0x0003 -- 禁用比较器(默认)
-- ==== 操作模式 ====
ADS1115_MODE_CONTINUOUS = 0x0000 -- 连续转换
ADS1115_MODE_SINGLE = 0x0100 -- 单次转换(默认,省电)
-- ==== 启动转换 ====
ADS1115_OS_START = 0x8000 -- 启动单次转换
-- ADS1115 配置上下文
ads1115_t = {
addr = 0x00,
config = 0x00, -- 16bits
fs_valtage = 0.0 -- 满量程电压(取决于PGA)
}
-- PGA对应的满量程电压(单位:V)
pga_fs = {
6.144, -- ADS1115_PGA_6_144V [0]
4.096, -- ADS1115_PGA_4_096V [1]
2.048, -- ADS1115_PGA_2_048V [2]
1.024, -- ADS1115_PGA_1_024V [3]
0.512, -- ADS1115_PGA_0_512V [4]
0.256 -- ADS1115_PGA_0_256V [5]
}
-- PGA 增益对应的满量程电压名
pga_names = {
"±6.144V", -- [0]
"±4.096V", -- [1]
"±2.048V", -- [2]
"±1.024V", -- [3]
"±0.512V", -- [4]
"±0.256V" -- [5]
}
-- 数据速率字符串
dr_names = {
"8 SPS", -- [0]
"16 SPS", -- [1]
"32 SPS", -- [2]
"64 SPS", -- [3]
"128 SPS", -- [4]
"250 SPS", -- [5]
"475 SPS", -- [6]
"860 SPS" -- [7]
}
-- I2C写2字节(大端)
function i2c_write_word(fd, reg, val)
i2c_write_reg(fd, reg + 0, (val >> 8) | 0xFF)
i2c_write_reg(fd, reg + 1, val & 0xFF)
end
--
-- 【配置脚本】
-- FIXME(20260602-qing): 异常处理
function build(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua builder func get a table --------")
print_table(data)
print("----------------------------------------------")
local fd = data.fd
-- 检查文件句柄参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Arguments: fd"
}
end
local addr = data.address
-- 检查地址参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Arguments: addr"
}
end
addr = tonumber(addr)
-- 写进配置表
ads1115.addr = addr
-- 开始配置PCA9685
i2c_set_addr(fd, addr)
-- 重启PCA9685
i2c_write_reg(fd, 0x00, 0x80)
-- 等待振荡器启动
milli_sleep(5)
i2c_write_reg(fd, 0x00, 0x10) -- 进入SLEEP模式(允许改频率)
i2c_write_reg(fd, 0xFE, math.floor(prescale))
i2c_write_reg(fd, 0x00, 0x20) -- 退出SLEEP模式
-- 等待振荡器启动
milli_sleep(5)
-- 返回成功信号
return {
result = "OK"
}
end
-- 【销毁PCA9685的脚本】
-- 重置PCA9685
-- NOTE(20260612-qing): 其实可以不需要,因为我们只是清空设置
function destroy(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua ender func get a table --------")
print_table(data)
print("--------------------------------------------")
local fd = data.fd
-- 检查fd参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Arguments: fd"
}
end
local addr = data.address
-- 检查address参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Arguments: addr"
}
end
addr = tonumber(addr)
-- 准备配置
i2c_set_addr(fd, addr)
-- 重启PCA9685
i2c_write_reg(fd, 0x00, 0x80)
-- 返回成功信号
return {
result = "OK"
}
end
-- 【调用PCA9685】
-- 将一个舵机通道转动到一个角度
-- NOTE(20260612-qing): 接收一个参数(表),返回一个结果(表)
function call(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua caller func get a table --------")
print_table(data)
print("---------------------------------------------")
local fd = data.fd
-- 检查fd参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Argument: fd"
}
end
local addr = data.address
-- 检查address参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Argument: addr"
}
end
addr = tonumber(addr)
local channel = data.channel
-- 检查channel参数是否存在
if channel == nil then
return {
result = "ERR",
message = "Invalid Argument: channel"
}
end
local angle = data.angle
-- 检查angle参数是否存在
if angle == nil then
return {
result = "ERR",
message = "Invalid Argument: angle"
}
end
print(string.format("Lua hardware drive set angle: %d", angle))
-- 设置I2C地址
i2c_set_addr(fd, addr)
-- 计算需要的参数
local min = 0.5 -- 0度对应0.5ms
local max = 2.5 -- 180度对应2.5ms
local width = min + (max - min) * (angle / 180.0)
local offval = math.floor(width * 4096.0 / 20.0)
local reg = 0x06 + 4 * channel
-- 写I2C寄存器
i2c_write_reg(fd, reg + 0, (0 >> 0) & 0xFF) -- 1 * 8
i2c_write_reg(fd, reg + 1, (0 >> 8) & 0xFF) -- 2 * 8
i2c_write_reg(fd, reg + 2, (offval >> 0) & 0xFF) -- 3 * 8
i2c_write_reg(fd, reg + 3, (offval >> 8) & 0xFF) -- 4 * 8
-- 返回字典
return {
result = "OK"
}
end
+10
View File
@@ -0,0 +1,10 @@
-- ADS1256 模数转换器 - SPI接口
function build(data)
end
function destroy(data)
end
function call(data)
end
+223
View File
@@ -0,0 +1,223 @@
-- PCA9685 PWM控制板 硬件脚本(测试)
-- 这个函数是辅助用的,它能够打印一张表出来
function print_table(t, indent)
indent = indent or 0
for k, v in pairs(t) do
if type(v) == 'table' then
print(string.rep(' ', indent) .. k .. ":")
print_table(v, indent + 2)
else
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
end
end
end
-- 【配置脚本】
-- 该脚本负责初始化PCA9685
-- FIXME(20260602-qing): 异常处理
function build(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua builder func get a table --------")
print_table(data)
print("----------------------------------------------")
local fd = data.fd
-- 检查文件句柄参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Arguments: fd"
}
end
local addr = data.address
-- 检查地址参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Arguments: addr"
}
end
addr = tonumber(addr)
-- 设置PWM频率
local freq = 50.0
local prescale_val = 25000000.0 / (4096.0 * freq) - 1.0 -- 25MHz主频
local prescale = prescale_val + 0.5
-- 开始配置PCA9685
i2c_set_addr(fd, addr)
-- 重启PCA9685
i2c_write_reg(fd, 0x00, 0x80)
-- 等待振荡器启动
milli_sleep(5)
i2c_write_reg(fd, 0x00, 0x10) -- 进入SLEEP模式(允许改频率)
i2c_write_reg(fd, 0xFE, math.floor(prescale))
i2c_write_reg(fd, 0x00, 0x20) -- 退出SLEEP模式
-- 等待振荡器启动
milli_sleep(5)
-- 返回成功信号
return {
result = "OK"
}
end
-- 【销毁PCA9685的脚本】
-- 重置PCA9685
-- NOTE(20260612-qing): 其实可以不需要,因为我们只是清空设置
function destroy(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua ender func get a table --------")
print_table(data)
print("--------------------------------------------")
local fd = data.fd
-- 检查fd参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Arguments: fd"
}
end
local addr = data.address
-- 检查address参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Arguments: addr"
}
end
addr = tonumber(addr)
-- 准备配置
i2c_set_addr(fd, addr)
-- 重启PCA9685
i2c_write_reg(fd, 0x00, 0x80)
-- 返回成功信号
return {
result = "OK"
}
end
-- 【调用PCA9685】
-- 将一个舵机通道转动到一个角度
-- NOTE(20260612-qing): 接收一个参数(表),返回一个结果(表)
function call(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua caller func get a table --------")
print_table(data)
print("---------------------------------------------")
local fd = data.fd
-- 检查fd参数是否存在
if fd == nil then
return {
result = "ERR",
message = "Invalid Argument: fd"
}
end
local addr = data.address
-- 检查address参数是否存在
if addr == nil then
return {
result = "ERR",
message = "Invalid Argument: addr"
}
end
addr = tonumber(addr)
local channel = data.channel
-- 检查channel参数是否存在
if channel == nil then
return {
result = "ERR",
message = "Invalid Argument: channel"
}
end
local angle = data.angle
-- 检查angle参数是否存在
if angle == nil then
return {
result = "ERR",
message = "Invalid Argument: angle"
}
end
print(string.format("Lua hardware drive set angle: %d", angle))
-- 设置I2C地址
i2c_set_addr(fd, addr)
-- 计算需要的参数
local min = 0.5 -- 0度对应0.5ms
local max = 2.5 -- 180度对应2.5ms
local width = min + (max - min) * (angle / 180.0)
local offval = math.floor(width * 4096.0 / 20.0)
local reg = 0x06 + 4 * channel
-- 写I2C寄存器
i2c_write_reg(fd, reg + 0, (0 >> 0) & 0xFF) -- 1 * 8
i2c_write_reg(fd, reg + 1, (0 >> 8) & 0xFF) -- 2 * 8
i2c_write_reg(fd, reg + 2, (offval >> 0) & 0xFF) -- 3 * 8
i2c_write_reg(fd, reg + 3, (offval >> 8) & 0xFF) -- 4 * 8
-- 返回字典
return {
result = "OK"
}
end
+163
View File
@@ -0,0 +1,163 @@
-- SPI 硬件
--
SPI_SUCCESS = 0
SPI_FAILED = 1
-- 全局文件句柄
fd = 0
-- 这个函数是辅助用的,它能够打印一张表出来
function print_table(t, indent)
indent = indent or 0
for k, v in pairs(t) do
if type(v) == 'table' then
print(string.rep(' ', indent) .. k .. ":")
print_table(v, indent + 2)
else
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
end
end
end
function build(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- SPI.Lua builder func get a table --------")
print_table(data)
print("--------------------------------------------------")
local path = data.path
-- 检查文件句柄参数是否存在
if path == nil then
return {
result = "ERR",
message = "Invalid Arguments: path"
}
end
-- 模式
local spi_mode = data.spi_mode
if spi_mode == nil then
return {
result = "ERR",
message = "Invalid Arguments: spi_mode"
}
end
print(spi_mode)
spi_mode = tonumber(spi_mode)
-- 比特
local bits = data.bits
if bits == nil then
return {
result = "ERR",
message = "Invalid Arguments: bits"
}
end
bits = tonumber(bits)
-- 速率
local speed = data.speed
if speed == nil then
return {
result = "ERR",
message = "Invalid Arguments: speed"
}
end
speed = tonumber(speed)
--
local delay = data.delay
if delay == nil then
return {
result = "ERR",
message = "Invalid Arguments: delay"
}
end
delay = tonumber(delay)
-- 开始执行
fd = spi_open(path)
if fd <= 0 then
return {
result = "ERR",
message = "Failed to open SPI file"
}
end
local res = spi_setup(fd, spi_mode, bits, speed, delay)
if res ~= 0 then
return {
result = "ERR",
message = "Failed to setup SPI device"
}
end
return {
result = "OK",
fd = fd
}
end
function destroy(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- SPI.Lua ender func get a table --------")
print_table(data)
print(string.format("now fd: %i", fd))
print("------------------------------------------------")
spi_close(fd)
fd = -1
return {
result = "OK"
}
end
function call(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- SPI.Lua caller func get a table --------")
print_table(data)
print("-------------------------------------------------")
return {
fd = fd
}
end
+86
View File
@@ -0,0 +1,86 @@
-- PCA9685 PWM控制板 硬件脚本(测试)
-- 这个函数是辅助用的,它能够打印一张表出来
function print_table(t, indent)
indent = indent or 0
for k, v in pairs(t) do
if type(v) == 'table' then
print(string.rep(' ', indent) .. k .. ":")
print_table(v, indent + 2)
else
print(string.rep(' ', indent) .. k .. ": " .. tostring(v))
end
end
end
-- 【配置脚本】
function build(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua builder func get a table --------")
print_table(data)
print("----------------------------------------------")
-- 返回成功信号
return {
result = "OK"
}
end
-- 【销毁用的脚本】
function destroy(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua ender func get a table --------")
print_table(data)
print("--------------------------------------------")
-- 返回成功信号
return {
result = "OK"
}
end
-- 【调用的脚本】
function call(data)
-- 检查该参数是否是一张表
if type(data) ~= "table" then
return {
result = "ERR",
message = "Invalid Arguments: table"
}
end
print("-------- Lua caller func get a table --------")
print_table(data)
print("---------------------------------------------")
-- 返回字典
return {
result = "OK"
}
end
+1
View File
@@ -0,0 +1 @@
cjpm run --run-args="$($args -join ' ')"
+55
View File
@@ -0,0 +1,55 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 10 - 10 - 10 - 10 - - - 20;
- 20 - 20 - 20 - 20 - - - 20;
- 30 - 30 - 30 - 30 - - - 20;
45 0 45 0 45 0 45 0 90 90 10 200; # stand
@
+54
View File
@@ -0,0 +1,54 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- - - - - - - - - - 400 200;
0 - - - - - - 150 - - 1000 1000;
45 - - - - - - 0 - - 10 20;
@
+63
View File
@@ -0,0 +1,63 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 45 - - - 45 - - - - - 100;
90 - - - 90 - - - - - - 50; # stand
#
- 0 - - - 0 - - - - - 50;
- - - 45 - - - 45 - - - 100;
45 - - - 45 - - - - - - 10;
- - 0 - - - 0 - - - - 50;
#
- - - 0 - - - 0 - - - 50;
- 45 - - - 45 - - - - - 100;
- - 45 - - - 45 - - - - 10;
- 0 - - - 0 - - - - - 50;
@
+53
View File
@@ -0,0 +1,53 @@
# ONE STEP
#
# legs | neck | free | speaker | delay
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
!- - - - - - - - - 90 - - - - - - - -; # 4-legs spider robot
!- - - - - - - - - - - - - - - - - -; # offset
45 - 45 - 45 - 45 - 90 90 - - - - - - 1000 100; # stand
- - - - - - - - - - - - - - - - 1000 -; # stand
- - - 30 - - - 30 - - - - - - - - 0 50; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
0 - 90 - 90 - 0 - - - - - - - - - - -; # right front forward & left behind forward
- 0 - - - 0 - - - - - - - - - - - -; # right front down & left behind down
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
0 - 90 - 90 - 0 - - - - - - - - - - -; #
- 0 - - - 0 - - - - - - - - - - - -; #
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
0 - 90 - 90 - 0 - - - - - - - - - - -; #
- 0 - - - 0 - - - - - - - - - - - -; #
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
0 - 90 - 90 - 0 - - - - - - - - - - -; #
- 0 - - - 0 - - - - - - - - - - - -; #
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
0 - 90 - 90 - 0 - - - - - - - - - - -; #
- 0 - - - 0 - - - - - - - - - - - -; #
90 - - 30 40 - - 30 - - - - - - - - - -; # right behind up & left front up
- - 40 - - - 90 - - - - - - - - - - -; # right behind forward & left front forward
- - - 0 - - - 0 - - - - - - - - - -; # right behind down & left front down
- 30 - - - 30 - - - - - - - - - - - -; # right front up & left behind up
45 - 45 - 45 - 45 - - - - - - - - - - -; # right front forward & left behind forward
- 0 - - - 0 - - - - - - - - - - - -; # right front down & left behind down
+56
View File
@@ -0,0 +1,56 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 10 - 10 - 10 - 10 - - - 20;
- 15 - 15 - 15 - 15 - - - 20;
- 20 - 20 - 20 - 20 - - - 20;
- 25 - 25 - 25 - 25 - - - 20;
- 30 - 30 - 30 - 30 - - - 20;
@
+26
View File
@@ -0,0 +1,26 @@
# SNNEAK
# legs | neck | free | delay
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -10; # offset
# 俯卧撑
#
90 0 90 0 90 0 90 0 90 90 - - - - - - 1000 200;
- 5 - 5 - 5 - 5 - - - - - - - - 0 50;
- 10 - 10 - 10 - 10 - - - - - - - - - -;
- 15 - 15 - 15 - 15 - - - - - - - - - -;
- 20 - 20 - 20 - 20 - - - - - - - - - -;
- 25 - 25 - 25 - 25 - - - - - - - - - -;
- 30 - 30 - 30 - 30 - - - - - - - - - -;
- 35 - 35 - 35 - 35 - - - - - - - - - -;
#r
- 35 - 35 - 35 - 35 - - - - - - - - 1000 200;
- 30 - 30 - 30 - 30 - - - - - - - - 0 50;
- 25 - 25 - 25 - 25 - - - - - - - - - -;
- 20 - 20 - 20 - 20 - - - - - - - - - -;
- 15 - 15 - 15 - 15 - - - - - - - - - -;
- 10 - 10 - 10 - 10 - - - - - - - - - -;
- 5 - 5 - 5 - 5 - - - - - - - - - -;
- 0 - 0 - 0 - 0 - - - - - - - - - -;
+54
View File
@@ -0,0 +1,54 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- - - - - 0 - - - - 400 200;
- - 0 - - 150 - - - - 1000 1000;
- - 45 - - 0 - - - - 10 20;
@
+56
View File
@@ -0,0 +1,56 @@
# ONE STEP
# legs | neck | free | delay
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -; # offset
45 - 45 - 45 - 45 - 90 90 - - - - - - 1000 100; # stand
- - - - - - - - - - - - - - - - - -; # stand
#----------------------------------------------------------------
- 30 - - - 25 40 - - - - - - - - - 0 50;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
50 - 0 - 0 - 90 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
#----------------------------------------------------------------
- 30 90 - - 25 40 - - - - - - - - - - -;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
50 - 0 - 0 - 90 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
- 30 90 - - 25 40 - - - - - - - - - - -;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
50 - 0 - 0 - 90 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
- 30 90 - - 25 40 - - - - - - - - - - -;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
50 - 0 - 0 - 90 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
- 30 90 - - 25 40 - - - - - - - - - - -;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
50 - 0 - 0 - 90 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
#----------------------------------------------------------------
- 30 90 - - 25 40 - - - - - - - - - - -;
0 - - - 90 - - - - - - - - - - - - -;
- 0 - - - 0 - - - - - - - - - - - -;
- - - 25 - - - 30 - - - - - - - - - -;
45 - 45 - 45 - 45 - - - - - - - - - - -;
- - - 0 - - - 0 - - - - - - - - - -;
+51
View File
@@ -0,0 +1,51 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
@
+57
View File
@@ -0,0 +1,57 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 10 - 10 - 10 - 10 - - - 50;
- 20 - 20 - 20 - 20 - - - -;
- 30 - 30 - 30 - 30 - - - -;
- 20 - 20 - 20 - 20 - - - -;
- 10 - 10 - 10 - 10 - - - -;
- 0 - 0 - 0 - 0 - - - 200; # stand
@
+56
View File
@@ -0,0 +1,56 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
#i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
90 - - - - - - - - - - 20; # left-behind
45 - - - - - - - - - 0 500; # stand
- - 90 - - - 45 - - - - 20; # right-behind
- - 45 - - - - - - 45 200 500; # stand
- - - - 90 - - - - - 0 20; # right-front
- - - - 45 - - - - - - 500; # stand
- - - - - - 90 - - - - 20; # left-front
- - - - - - 45 - - - - 500; # stand
- - - - - - - - - 90 - 20; # stand
@
+21
View File
@@ -0,0 +1,21 @@
# 无硬件情况下的测试脚本
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@TEST
name parameter0 patameter1;
t0 p0 p1;
t1 p2 p3;
@
@Action
t0.A t1.F;
10 20;
@
+57
View File
@@ -0,0 +1,57 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 10 - 10 - - - - - - - 50;
- 20 - 20 - - - - - - - -;
- 30 - 30 - - - - - - - -;
- 20 - 20 - - - - - - - -;
- 10 - 10 - - - - - - - -;
- 0 - 0 - - - - - - - 200; # stand
@
+57
View File
@@ -0,0 +1,57 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- - - - - 10 - 10 - - - 50;
- - - - - 20 - 20 - - - -;
- - - - - 30 - 30 - - - -;
- - - - - 20 - 20 - - - -;
- - - - - 10 - 10 - - - -;
- - - - - 0 - 0 - - - 200; # stand
@
+57
View File
@@ -0,0 +1,57 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- 10 - - - - - 10 - - - 50;
- 20 - - - - - 20 - - - -;
- 30 - - - - - 30 - - - -;
- 20 - - - - - 20 - - - -;
- 10 - - - - - 10 - - - -;
- 0 - - - - - 0 - - - 200; # stand
@
+57
View File
@@ -0,0 +1,57 @@
# 在 @Action 矩阵中,硬件变量的参数可以使用别名
@Alias
name alias;
A angle;
F frequency;
@
@I2C
name path;
i2c-0 /dev/i2c-0;
i2c-1 /dev/i2c-1;
@
@PCA9685
name I2C address;
pca9685-0 i2c-1 0x40;
@
@MPU6050_BIND
name I2C addr;
#mpu6050-0 i2c-1 0x69;
#mpu6050-1 i2c-1 0x68;
#mpu6050-2 i2c-0 0x68;
#mpu6050-3 i2c-0 0x69;
@
@Speaker
name channels sample_rate volumn max_f min_f;
spk 2 48000 0.6 5000 -;
@
@Servo
name max min offset controller channel;
s1 90 - - pca9685-0 0; # rb0
s2 180 - - - 1; # rb1
s3 -90 - - - 2; # lb0
s4 -180 - - - 3; # lb1
s5 90 - - - 4; # lf0
s6 180 - - - 5; # lf1
s7 -90 - - - 6; # rf0
s8 -180 - - - 7; # rf1
s9 180 - - - 8; # cam0
s10 180 - - - 9; # cam1
@
# 行动矩阵
@Action
s1.A s2.A s3.A s4.A s5.A s6.A s7.A s8.A s9.A s10.A spk.F spk.delay; # column name
45 - 45 - 45 - 45 - 90 90 10 200; # stand
- - - 10 - 10 - - - - - 50;
- - - 20 - 20 - - - - - -;
- - - 30 - 30 - - - - - -;
- - - 20 - 20 - - - - - -;
- - - 10 - 10 - - - - - -;
- - - 0 - 0 - - - - - 200; # stand
@
+29
View File
@@ -0,0 +1,29 @@
# SNNEAK
# legs | neck | free | delay
#lb rb rf lf
~s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 - - - - - - spk delay; # column name
!90 180 -90 -180 90 180 -90 -180 180 180 - - - - - - 5000 1000; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
! - - - - - - - - - - - - - - - - - -; # 4-legs spider robot
# 俯卧撑
#
- 0 - 0 - 0 - 0 90 90 - - - - - - 1000 200;
- 5 - 5 - 5 - 5 - - - - - - - - 0 50;
- 10 - 10 - 10 - 10 - - - - - - - - - -;
- 15 - 15 - 15 - 15 - - - - - - - - - -;
- 20 - 20 - 20 - 20 - - - - - - - - - -;
- 25 - 25 - 25 - 25 - - - - - - - - - -;
- 30 - 30 - 30 - 30 - - - - - - - - - -;
- 35 - 35 - 35 - 35 - - - - - - - - - -;
#r
- 35 - 35 - 35 - 35 - - - - - - - - 1000 200;
- 30 - 30 - 30 - 30 - - - - - - - - 0 50;
- 25 - 25 - 25 - 25 - - - - - - - - - -;
- 20 - 20 - 20 - 20 - - - - - - - - - -;
- 15 - 15 - 15 - 15 - - - - - - - - - -;
- 10 - 10 - 10 - 10 - - - - - - - - - -;
- 5 - 5 - 5 - 5 - - - - - - - - - -;
- 0 - 0 - 0 - 0 - - - - - - - - - -;
+263
View File
@@ -0,0 +1,263 @@
package bot4cj
import std.io.*
import std.math.*
import std.convert.*
import std.collection.*
import bot4cj.math.*
/* -------- 矩阵编程语言 - 基本对象 --------
* TODO:增加储存到文件系统的功能
* TODO:神经网络层转换为矩阵对象
-------- */
class MatrixObject {
private var name: String = "" /* -------- 名字 -------- */
private var tmp = ArrayList<Rune>() /* ---- rune buf 字符缓冲区 ---- */
private var grp = ArrayList<String>() /* ---- one line 整行缓冲区 ---- */
private var values = ArrayList<ArrayList<String>>() /* ---- 原始矩阵 ---- */
/* -------- 是否已命名 -------- */
public func hasName(): Bool { !(name == "") }
/* --------- 获得名字 --------- */
public func getName(): String { return name }
/* ------- 取动作的长度 ------- */
public mut prop size: Int64 {
get() { values.size }
set(_) {}
}
/* -------- 取动作的列数 -------- */
public mut prop col: Int64 {
get() {
if (this.size > 0) {
return values[0].size }
else { return 0 }
}
set(_) {}
}
/* ------- 获取矩阵元素,只能取,不能修改 ------- */
public operator func [](row: Int64, col: Int64): String {
return values[row][col]
}
/** -- 在某一行中寻找某一个字符串的列号 -- */
public func indexOfCol(row: Int64, key: String): Int64 {
for (col in 0..values[row].size) {
if (values[row][col] == key) {
return col }
}
/* ---- 没找到 ---- */
throw IllegalArgumentException("The key does not found.")
}
/* ----- 储存对象 ----- */
public func save(output: StringWriter<OutputStream>) {
output.writeln("@${getName()}")
for (vec in values) {
for (item in vec) {
output.write(item)
output.write(" ")
}
output.writeln(";")
}
output.writeln("@")
}
/* -------- 添加字符 -------- */
public func addRune(c: Rune) { tmp.add(c) }
/* -------- 提交名字 -------- */
public func submitName() {
if (tmp.size == 0) { return }
let s = String(tmp)
tmp.clear()
name = s
}
/* -------- 提交元素 -------- */
public func submitItem() {
if (tmp.size == 0) { return }
let s = String(tmp)
tmp = ArrayList<Rune>()
grp.add(s)
}
/* -------- 提交行 -------- */
public func submitLine(count: Int64) {
if (grp.size == 0) { return }
if (!values.size == 0 && !grp.size == values[values.size - 1].size)
{ throw DoesNotSync(count) }
values.add(grp)
grp = ArrayList<String>()
}
}
/* ---------------- 对Nn1进行扩展 ---------------- */
extend Nn1 {
/* -------- 转换为MatrixObject对象 -------- */
public func toMto(): MatrixObject {
let mto = MatrixObject()
return mto
}
}
/* -------- 矩阵式解析器 --------
* TODO(20260514-qing): 这个类输入流数据,输出MatrixObject对象
* 它是与其深度绑定的,我倾向于将其与MatrixObject融合
* 即持有HashMap<String, ArrayList<ArrayList<String>>> */
class MatrixParser {
/* ---- 静态判断Option<Rune>是否有值 ----
- 该方法用于文本文件遍历时判空 */
private static func hasValue(co: Option<Rune>): Bool {
match(co) {
case Some(_) => true
case None => false
}
}
/* -------- 主初始化器(构造函数) --------
- 设置目标输入流 */
private var input: StringReader<InputStream>
public init(input: StringReader<InputStream>) {
this.input = input }
/* -------- 切换目标输入流 -------- */
public func reset(input: StringReader<InputStream>) {
this.input = input }
/* -------- 解析器 - 循环不变式 ----------------------
* -------- 初始化: ss是一个动作脚本的输入流 --------
* -------- 保持: ss是动作脚本剩余部分的输入流 ------
* -------- 终止: 当输入流为空时,循环终止 ---------- */
private var obj = MatrixObject()
public func parse(): Option<MatrixObject> {
var count = 1i64 /* 行计数器始于1 */
flag = false /* 重置结束标志 */
this.obj = MatrixObject() /* 创建一个新的对象,一次只解析一个对象 */
var so = input.read() /* 获取初始状态 */
while (!flag && hasValue(so)) {
try {
let s = so.getOrThrow()
print(s) /* debug */
count = _next(s, count) /* 进行解析解析 */
so = input.read() /* 为下一次循环提供条件 */
}
catch (exp: BufOverTheLine) { throw exp }
catch (exp: IllegalArgumentException) { throw IllegalArgumentException("Parse failed at line ${count}") }
catch (exp: Exception) { throw UnknowExcept(count) } /* FIXME: 还是暴露出原来的错误位置来更好? */
}
/* 如果没有新的矩阵式对象(意外终止)则返回空 */
if (!obj.hasName()) { return None }
return obj /* 否则为解析成功正常退出,返回解析到的矩阵式对象 */
}
private var flag = false /* 结束标志 */
private var nl: Bool = true /* new line */
private var cm: Bool = false /* comment */
private var nm: Bool = false /* name-line */
private func _next(c: Rune, ct: Int64): Int64 {
var count = ct
/* ------------ COMMENTS ------------ */
if (!cm && c == r'#') { cm = true } /* 注释开始时 */
else if (cm && c == r'\n') { /* 注释结束时 */
nl = true
cm = false
count += 1
}
else if (cm && c != r'\n') { ; } /* 注释进行时 */
/* FIXME: EOF情况 */
/* ------------ new line ------------ */
else if (nl && (c == r' ' || c == r'\t' || c == r'\r')) { ; } /* 新行空字符,维持现状 */
else if (nl && c == r'\n') { /* 新行空行 */
count += 1
}
else if (nl && c == r'@' && !obj.hasName()) {
nl = false
nm = true
}
else if (nl && c == r'@' && obj.hasName()) {
flag =true
nl = false
nm = true
}
else if (nl && c != r'@') { /* 新行内容 */
nl = false
obj.addRune(c)
}
/* ------------- not nl ------------- */
else if (!nl && nm && (c == r' ' || c == r'\t' || c == r'\n')) { /* 添加名字 */
obj.submitName()
nl = true
nm = false
}
else if (!nl && !nm && (c == r' ' || c == r'\t' || c == r"\n")) { /* 添加命名项 */
obj.submitItem()
if (c == r"\n") { count += 1 }
}
else if (!nl && c == r';') { /* 普通行结束 */
obj.submitItem()
obj.submitLine(count)
nl = true
}
else if (!nl) { // 非空格、非制表符、非分号
obj.addRune(c)
}
/* -------- 返回行号 -------- */
return count
}
}
/* -------- 矩阵式储存器 --------
* - 接收矩阵式的哈希表(HashMap<String, MatrixObject>)
* - 以及一个输出流(在构造函数)。 */
class MatrixSaver {
private let stringWriter: StringWriter<OutputStream>
public init(output: OutputStream) {
stringWriter = StringWriter(output) }
public func save(C: LinkedList<MatrixObject>) {
for (item in C) {
item.save(stringWriter)
stringWriter.writeln() /* 对象之间空一行 */
stringWriter.flush() }
}
}
/* --------------------------------------------------------- */
/* --------------------------------------------------------- */
/* TODO(20260516-qing): 继续完善异常种类 */
/* -------- 解析异常 -------- */
open class ParseExcept <: Exception {
public ParseExcept(msg: String, line: Int64) {
super('${msg}. we got line: ${line}') }
}
/* -------- 缓冲区过长 -------- */
class BufOverTheLine <: ParseExcept {
BufOverTheLine(count: Int64) {
super('Buffer length over the line', count) }
}
/* -------- 行没对齐 -------- */
class DoesNotSync <: ParseExcept {
DoesNotSync(count: Int64) {
super("Matrix Line Does Not Sync", count) }
}
/* -------- 未知异常 -------- */
class UnknowExcept <: ParseExcept {
UnknowExcept(count: Int64) {
super("I don't understand what happened", count) }
}
+152
View File
@@ -0,0 +1,152 @@
/*
* i2c - 用户界面
*
* 这里已经提供了I2C通用接口,可以将上层代码移动到仓颉下面
*/
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#ifdef __linux__
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h> /* 其中宏的取值可能具有平台特异性 */
#include <i2c/smbus.h>
#endif
#include "bot4cj.h" /* 有没有可能在仓颉代码中解析C头文件取得宏 */
/*
* 初始化i2c设备
*
* 动态指定设备文件
*
* 返回打开的文件句柄或者错误码
*/
#ifdef __linux__
int16_t I2C_init(const char* filepath) {
int16_t fd;
if ((fd = open(filepath, O_RDWR)) < 0) {
perror("Failed to open I2C bus");
return BOT4CJ_I2C_OPENED_FAILED;
}
else {
return fd;
}
}
#endif
#ifdef _WIN64
int16_t I2C_init(const char* filepath) {
return BOT4CJ_I2C_OPENED_FAILED;
}
#endif
/* 释放I2C设备文件 */
#ifdef __linux__
void I2C_free(int16_t fd) {
close(fd);
}
#endif
#ifdef _WIN64
void I2C_free(int16_t fd) {
/* Windows平台下无需操作 */
}
#endif
/* 设置I2C地址 */
#ifdef __linux__
int I2C_setAddr(int16_t fd, uint8_t addr) {
if (ioctl(fd, I2C_SLAVE, addr) < 0) {
perror("Failed to set I2C address");
return BOT4CJ_I2C_ERR;
}
else {
return BOT4CJ_I2C_OK;
}
}
#endif
#ifdef _WIN64
int I2C_setAddr(int16_t fd, uint8_t addr) {
return BOT4CJ_I2C_ERR;
}
#endif
/* 写寄存器 */
#ifdef __linux__
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
/*uint8_t buf[] = {reg, val};
if (write(fd, buf, 2) != 2) {
perror("I2C write error");
return BOT4CJ_I2C_ERR;
}
else {
return BOT4CJ_I2C_OK;
}*/
return i2c_smbus_write_byte_data(fd, reg, val);
}
#endif
#ifdef _WIN64
int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val) {
return BOT4CJ_I2C_ERR;
}
#endif
/* 读寄存器 失败返回0 */
#ifdef __linux__
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
uint8_t value;
/*if (write(fd, &reg, 1) != 1) {
perror("I2C read error");
return 0;
}
if (read(fd, &value, 1)!= 1) {
perror("I2C read error");
return 0;
}*/
//return value;
int8_t a = i2c_smbus_read_byte_data(fd, reg);
return *(uint8_t *)& a;
}
#endif
#ifdef _WIN64
uint8_t I2C_readReg(int16_t fd, uint8_t reg) {
return 0;
}
#endif
/* 读寄存器 - 有符号
* 失败返回0 */
#ifdef __linux__
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
int8_t value;
//if (write(fd, &reg, 1) != 1) {
//perror("I2C read error");
//return 0;
//}
//if (read(fd, &value, 1)!= 1) {
//perror("I2C read error");
//return 0;
//}
//return value;
return i2c_smbus_read_byte_data(fd, reg);
}
#endif
#ifdef _WIN64
int8_t I2C_readRegFlag(int16_t fd, uint8_t reg) {
return 0;
}
#endif
+516
View File
@@ -0,0 +1,516 @@
#include <random>
#include <algorithm>
#include "Matrix.hh"
namespace qing {
template <typename T>
Matrx<T>::Matrx(): row(0), col(0) {}
template <typename T>
Matrx<T>::Matrx(const uint64_t row, const uint64_t col)
: row(row), col(col) { /* 该构造函数用于从头开始构造 */
arr = std::vector<T>(col * row);
}
template<typename T>
Matrx<T>::Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src)
: row(row), col(col) { /* 该构造函数用于通过已有的vector进行构造 */
arr = src;
}
template<typename T>
const T& Matrx<T>::get(uint64_t row, uint64_t col) const { /* 访问器 - 访问矩阵元素 */
return arr[col * this->row + row];
}
template<typename T>
T& Matrx<T>::witch(uint64_t row, uint64_t col) { /* 访问器 - 访问矩阵元素 */
return arr[col * this->row + row];
}
template<typename T>
void Matrx<T>::set(uint64_t row, uint64_t col, T v) { /* 设置矩阵元素 */
arr[col * this->row + row] = v;
}
template<typename T>
const std::vector<T>& Matrx<T>::get_data() const{ /* 访问器 - 用于获取原始数据 */
return arr;
}
template<typename T>
const uint64_t Matrx<T>::get_col() const { /* 获取列数 */
return col;
}
template<typename T>
const uint64_t Matrx<T>::get_row() const { /* 获取行数 */
return row;
}
template<typename T>
bool Matrx<T>::has_same_shape(const Matrx<T>& other) { /* 两个矩阵是否具有相同形状 */
return other.get_col() == this->get_col() && other.get_row() == this->get_row();
}
template<typename T>
bool Matrx<T>::has_dot_shape(const Matrx<T>& other) { /* 具有矩阵乘法的形状 */
return this->get_col() == other.get_row();
}
template<typename T>
void Matrx<T>::assert_same_shape(const Matrx<T>& other) { /* 断言两个矩阵具有相同形状 */
if (!has_same_shape(other))
throw std::invalid_argument("The shapes of matrix are not same.");
}
template<typename T>
void Matrx<T>::assert_dot_shape(const Matrx<T>& other) { /* 断言两个矩阵具有点积前置形状 */
if (!has_dot_shape(other)) {
throw std::invalid_argument("The shapes of matrix can not be dot.");
}
}
template<typename T>
Matrx<T> Matrx<T>::operator+(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
std::vector<T> v0 = this->get_data();
std::vector<T> vec = other.get_data();
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
*it += *it_or;
return Matrx<T>{this->get_row(), this->get_col(), v0};
}
template<typename T>
Matrx<T> Matrx<T>::operator+(T& item) { /* 矩阵加法广播 */
auto len = this->get_data().size();
auto v0 = std::vector<T> (len, item);
auto matrix = (*this) + Matrx(get_row(), get_col(), v0);
//auto matrix = *this;
//for (auto it = arr.begin(); it != arr.end(); ++it)
// *it += item;
return matrix;
}
template<typename T>
Matrx<T> Matrx<T>::operator-(const Matrx<T>& other) { /* 操作符重载 - 矩阵减法 */
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
std::vector<T> v0 = this->get_data();
std::vector <T> vec = other.get_data();
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
*it -= *it_or;
return Matrx{this->get_row(), this->get_col(), v0};
}
template<typename T>
Matrx<T> Matrx<T>::operator-(T& item) { /* 操作符重载 - 矩阵减法广播 */
auto len = this->get_data().size();
auto v0 = std::vector<T> (len, item);
auto matrix = (*this) - Matrx(get_row(), get_col(), v0);
//Matrx<T> matrix = *this;
//for (auto it = arr.begin(); it != arr.end(); ++it)
// *it -= item;
return matrix;
}
template<typename T>
Matrx<T> Matrx<T>::dot(const Matrx<T>& other) { /* 操作符重载 - 矩阵乘法 */
//assert_dot_shape(other); /* 它们的形状是否能够点积 */
auto matrix = Matrx<T>(this->get_row(), other.get_col());
for (uint64_t i=0; i<this->get_row(); ++i) /* 点积运算 */
for (uint64_t j=0; j<other.get_col(); ++j) {
T sum{0};
for (uint64_t k=0; k<this->get_col(); ++k)
sum += this->get(i, k) * other.get(k, j);
matrix.witch(i, j) = sum;
}
return matrix;
}
template<typename T>
Matrx<T> Matrx<T>::operator*(T& item) { /* 操作符重载 - 矩阵乘法广播 */
std::vector<T> vec = this->get_data();
for (auto it = vec.begin(); it != vec.end(); ++it)
*it *= item;
return Matrx<T>{this->get_row(), this->get_col(), vec};
}
template<typename T>
Matrx<T> Matrx<T>::operator*(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
std::vector<T> v0 = this->get_data();
std::vector<T> vec = other.get_data();
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
*it *= *it_or;
return Matrx<T>{this->get_row(), this->get_col(), v0};
}
template<typename T>
Matrx<T> Matrx<T>::operator/(T& item) { /* 操作符重载 - 矩阵除法广播 */
std::vector<T> vec = this->get_data();
for (auto it = vec.begin(); it != vec.end(); ++it)
*it /= item;
return Matrx<T>(this->get_row(), this->get_col(), vec);
}
template<typename T>
Matrx<T> Matrx<T>::operator/(const Matrx<T>& other) { /* 加法操作符重定义 - 矩阵加法 */
//assert_same_shape(other); /* 检查两个矩阵形状是否一致 */
std::vector<T> v0 = this->get_data();
std::vector<T> vec = other.get_data();
for (auto it = v0.begin(), it_or = vec.begin(); it != v0.end(); ++it, ++it_or )
*it /= *it_or;
return Matrx<T>{this->get_row(), this->get_col(), v0};
}
template class Matrx<float>; /* 显式实例化 */
template class Matrx<double>;
}
/* FIXME: 形状不合法时返回空指针 */
using namespace qing;
extern "C" {
#ifdef _WIN64
_declspec(dllexport)
#endif
void* createMatrix(uint64_t row, uint64_t col) {
return (void *) new Matrx<float>(row, col);
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void freeMatrix(void* M) {
delete (Matrx<float>*) M;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
float* witchMatrix(void *m, uint64_t row, uint64_t col) {
Matrx<float> *M = (Matrx<float>*) m;
return &(M->witch(row, col));
}
#ifdef _WIN64
_declspec(dllexport)
#endif
float getMatrix(void *m, uint64_t row, uint64_t col) {
Matrx<float> *M = (Matrx<float>*) m;
return M->get(row, col);
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void setMatrix(void *m, uint64_t row, uint64_t col, float v) {
Matrx<float> *M = (Matrx<float>*) m;
M->set(row, col, v);
}
#ifdef _WIN64
_declspec(dllexport)
#endif
uint64_t getrowMatrix(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
return M->get_row();
}
#ifdef _WIN64
_declspec(dllexport)
#endif
uint64_t getcolMatrix(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
return M->get_col();
}
#ifdef _WIN64
_declspec(dllexport)
#endif
bool sameShapeMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
return M->has_same_shape(*S);
}
#ifdef _WIN64
_declspec(dllexport)
#endif
bool dotShapeMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
return M->has_dot_shape(*S);
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* plusMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
auto a = new Matrx<float>((*M) + (*S));
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* plusBroadcastMatrix(void *m, float t) {
Matrx<float> *M = (Matrx<float>*) m;
auto a = new Matrx<float> ((*M) + t);
return (void*) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* subMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
auto a = new Matrx<float>((*M) - (*S));
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void * subBroadcastMatrix(void *m, float t) {
Matrx<float> *M = (Matrx<float> *) m;
auto a = new Matrx<float> ((*M) - t);
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* multiMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
auto a = new Matrx<float>((*M) * (*S));
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* multiBroadcastMatrix(void *m, float t) {
Matrx<float> *M = (Matrx<float> *) m;
auto a = new Matrx<float>((*M) * t);
return (void*) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* divMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
auto a = new Matrx<float>((*M) / (*S));
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* divBroadcastMatrix(void *m, float t) {
Matrx<float> *M = (Matrx<float> *) m;
auto a = new Matrx<float>((*M) / t);
return (void*) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* dotMatrix(void *m, void *s) {
Matrx<float> *M = (Matrx<float>*) m;
Matrx<float> *S = (Matrx<float>*) s;
auto a = new Matrx<float>(M->dot(*S));
return (void *) a;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* transposeMatrix(void *m) {
Matrx<float> *M = (Matrx<float> *)m;
auto N= new Matrx<float>(M->get_col(), M->get_row());
for (uint64_t row = 0; row< N->get_row(); row++) {
for (uint64_t col = 0; col < N->get_col(); col++) {
N->witch(row, col) = M->get(col, row);
}
}
return (void *)N;
}
/* 正态分布Xavier初始化 */
#ifdef _WIN64
_declspec(dllexport)
#endif
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
{
/* 构建空数组 */
std::vector<float> weights(fan_in * fan_out);
/* 高质量的随机数生成器 */
static std::mt19937 generator(std::random_device{}());
/* 计算标准差 */
float sigma = std::sqrt(2.0f / (fan_in + fan_out));
/* 创建正态分布对象,均值为0,标准差为sigma */
std::normal_distribution<float> distribution(0.0f, sigma);
/* 遍历权重矩阵,进行赋值 */
uint64_t k=0;
for (uint64_t i=0; i<fan_in; ++i)
for (uint64_t j=0; j<fan_out; ++j)
weights[k++] = distribution(generator);
/* 构建矩阵 */
auto matrix = new Matrx<float>(fan_in, fan_out, weights);
return (void *)matrix;
}
/* 均匀分布Xavier初始化函数 */
#ifdef _WIN64
_declspec(dllexport)
#endif
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
{
/* 构建空的数组 */
auto weights = std::vector<float>(fan_in * fan_out);
/* 使用高质量的随机数生成器 */
static std::mt19937 generator(std::random_device{}());
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
float scale = std::sqrt(2.0f / (fan_in + fan_out));
/* 创建均匀分布对象,范围为[-scale, scale] */
std::uniform_real_distribution<float> distribution(-scale, scale);
/* 遍历权重矩阵,进行赋值 */
uint64_t k=0;
for (uint64_t i=0; i<fan_in; ++i)
for (uint64_t j=0; j<fan_out; ++j)
weights[k++] = distribution(generator);
/* 构建矩阵对象 */
auto matrix = new Matrx<float> (fan_in, fan_out, weights);
return (void *)matrix;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Sigmoid_do(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = 1.0 / (1.0 + std::exp(-x));
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Sigmoid_dot(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& o: v) {
o = o * (1.0 - o);
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* ReLU_do(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = x > 0 ? x : 0;
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* ReLU_dot(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = x > 0 ? 1.0f : 0.0f;
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Leaky_ReLU_do(void *m, float alpha) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = x > 0 ? x : alpha * x;
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Leaky_ReLU_dot(void *m, float alpha) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = x > 0 ? 1.0 : alpha;
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Tanh_do(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
x = std::tanh(x);
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
#ifdef _WIN64
_declspec(dllexport)
#endif
void* Tanh_dot(void *m) {
Matrx<float> *M = (Matrx<float>*) m;
std::vector<float> v = M->get_data();
for (float& x: v) {
if (x > 20.0) x = 0.0; // tanh(x) ≈ 1, 导数≈0
else if (x < -20.0) x = 0.0; // tanh(x) ≈ -1, 导数≈0
else {
float t = tanh(x);
x = 1.0 - t * t;
}
}
auto N = new Matrx<float> (M->get_row(), M->get_col(), v);
return (void *)N;
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
___col___
/ \
+---------+ \
| + + + + | | row - 第一维度
| + + + + | |
+---------+ /
*/
#pragma once
#include <cmath>
#include <vector>
#include <iostream>
#include <stdexcept>
#include <cstdint>
namespace qing {
template <class T>
class Matrx {
public:
Matrx();
Matrx(const uint64_t row, const uint64_t col);
Matrx(const uint64_t row, const uint64_t col, const std::vector<T>& src);
const T& get(uint64_t row, uint64_t col) const;
//-------------旧接口----------------
T& witch(uint64_t row, uint64_t col);
//-------------新接口----------------
void set(uint64_t row, uint64_t col, T v);
//-----------------------------------
const std::vector<T>& get_data() const;
const uint64_t get_col() const;
const uint64_t get_row() const;
bool has_same_shape(const Matrx<T>& other);
bool has_dot_shape(const Matrx<T>& other);
void assert_same_shape(const Matrx<T>& other);
void assert_dot_shape(const Matrx<T>& other);
Matrx<T> operator+(const Matrx<T>& other);
Matrx<T> operator+(T& item);
Matrx<T> operator-(const Matrx<T>& other);
Matrx<T> operator-(T& item);
Matrx<T> operator*(const Matrx<T>& other);
Matrx<T> operator*(T& item);
Matrx<T> operator/(T& item);
Matrx<T> operator/(const Matrx<T>& other);
Matrx<T> dot(const Matrx<T>& other);
void save(std::ostream& out) {
out << row << " " << col;
for (const auto& item : arr) {
out << " ";
out << item;
}
}
private:
uint64_t row, col;
std::vector<T> arr;
};
}
+21
View File
@@ -0,0 +1,21 @@
/* I2C操作库 - PCA9685 - MPU6050 */
#ifndef QING_I2C
#define QING_I2C
/* I2C */
#define BOT4CJ_I2C_OK 0
#define BOT4CJ_I2C_ERR 1
#define BOT4CJ_I2C_OPENED_FAILED -1
#ifdef _WIN64
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endif
DLLEXPORT int16_t I2C_init(const char* filepath);
DLLEXPORT void I2C_free(int16_t fd);
DLLEXPORT int I2C_setAddr(int16_t fd, uint8_t addr);
DLLEXPORT int I2C_writeReg(int16_t fd, uint8_t reg, uint8_t val);
DLLEXPORT uint8_t I2C_readReg(int16_t fd, uint8_t reg);
DLLEXPORT int8_t I2C_readRegFlag(int16_t fd, uint8_t reg);
#endif
+80
View File
@@ -0,0 +1,80 @@
#define _USE_MATH_DEFINES
#include <cmath>
#include <vector>
#include "fft.h"
#include "Matrix.hh"
using namespace qing;
void functf1d(float A[DN])
{
unsigned long int i;
float dt, tt;
float p = 0.1;
float freq = 10.0; // 10Hz
float fs = DN; // 采样率65536Hz
dt = 1.0 / fs;
for(i = 0; i < DN; i++)
{
tt = (float)i * dt;
A[i] = sin(2.0 * M_PI * freq * tt) * p; // 正弦波
//A[1][i] = 0.0; // 虚部为0
}
}
// 初始化FFT数组
static void fft_init(float A[2][DN], Matrx<float>& M)
{
for (int i = 0; i < DN; i++) {
float a = M.witch(i, 0);
A[0][i] = a;
A[1][i] = 0;
}
}
// 计算FFT结果
static void fft_result(std::vector<float>& R, float A[2][DN])
{
for (int i = 0; i < DN/2; i++) {
float a = pow(A[0][i], 2) + pow(A[1][i], 2);
R[i] = sqrt(a) / (DN/2);
//std::cout << i << ":\t" << R[i] << std::endl;
}
}
extern "C" {
// 生成正弦波
#ifdef _WIN64
__declspec(dllexport) void* gen_wave()
#else
void* gen_wave()
#endif
{
std::vector<float> A(DN);
functf1d(A.data());
return (void *)new Matrx<float>(DN, 1, A);
}
// 开始FFT
#ifdef _WIN64
__declspec(dllexport) void* start_fft(void* M)
#else
void* start_fft(void* M)
#endif
{
float A[2][DN];
std::vector<float> R(N);
fft_init(A, *(Matrx<float> *)M);
//fft1(A, 1); // 复序列基2 FFT
fft1r1(A, 1); // 单实序列
fft_result(R, A);
return (void*) new Matrx<float>(N/2, 1, R);
}
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef FFT_H
#define FFT_H
#ifndef N
#define N 32768
#endif
#ifndef DN
#define DN (N*2)
#endif
#define eps 1.0e-9
#ifdef __cplusplus
extern "C" {
#endif
void initial(void);
void fft1(float A[2][N], int ifft);
unsigned long int inverseq(unsigned long int i1,
unsigned long int imax);
void fft1r2(float A[2][N], float B[2][N], int ifft);
void fft1r1(float A[2][DN], int ifft);
#ifdef __cplusplus
}
#endif
#endif
+231
View File
@@ -0,0 +1,231 @@
/* fft1.c: fast Fourier transform base on 2 for one dimensional
complex array, direct and inverse,
This program is contributed by Jiang Yong, 2003. 1. */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "fft.h"
static int ifirst=0, sign=-1;
static unsigned M, jup[64], kup[64];
static float pi, wr[N], wi[N];
/********* initial **************/
void initial(void)
{
unsigned long int i, j, ni;
float unit, tt;
ni = N;
M = 0;
while (ni > 1) { ni=ni/2; M=M+1; }
ni = 1;
for(j=0; j<M; j++) ni = ni * 2;
if ((ni-N) != 0)
{
printf("N isn't compatible! (N => %i, ni -> %lu)\n", N, ni);
exit(1);
}
kup[0] = N/2; /* kup[l] = 2 ^ (M - l - 1) */
jup[0] =1;
for (j=1;j<M;j++)
{
kup[j]=kup[j-1]/2;
jup[j]=jup[j-1]*2;
}
pi=4.0*atan((float)1.0);
unit=2.0*pi/((float)N);
tt=0.0;
for(i=0;i<N;i++)
{
wr[i] = cos(tt);
wi[i] = sign * sin(tt);
tt = tt + unit;
}
}
/** ifft=1<-->FFT ** ifft=-1<-->IFFT **/
void fft1(float A[2][N], int ifft)
{
unsigned long int l, k, i, j, jl0, jl0k, jl1k, reseq, ij;
float wrc, wis, ar, ai, x[2][N];
if (ifirst == 0) { initial(), ifirst=1; }
if (ifft == -1)
for(i=0;i<N;i++) A[1][i] = -A[1][i];
for (l=0; l<M; l++)
{
for (j=0; j<jup[l]; j++)
{
reseq=inverseq(j, l);
ij=kup[l]*reseq;
jl0=2*kup[l]*j;
wrc = wr[ij];
wis = wi[ij];
for (k=0; k<kup[l]; k++)
{
jl0k = jl0 + k;
jl1k = jl0 + kup[l] + k;
ar = A[0][jl1k] * wrc - A[1][jl1k] * wis;
ai = A[0][jl1k] * wis + A[1][jl1k] * wrc;
A[0][jl1k] = A[0][jl0k] - ar;
A[1][jl1k] = A[1][jl0k] - ai;
A[0][jl0k] = A[0][jl0k] + ar;
A[1][jl0k] = A[1][jl0k] + ai;
}
}
}
for (i=0; i<N; i++)
{
j=inverseq(i, M);
x[0][i] = A[0][j];
x[1][i] = A[1][j];
}
for (i=0; i<N; i++)
{
A[0][i] = x[0][i];
A[1][i] = x[1][i];
}
if (ifft == -1)
for (i=0; i<N; i++)
{
A[0][i] = A[0][i]/((float)N);
A[1][i] = -A[1][i]/((float)N);
}
}
/*********************************/
unsigned long int inverseq(unsigned long int i1,
unsigned long int imax)
{
unsigned long int j, ii, i0, inv;
i0 = i1;
inv = 0;
for (j=0; j < imax; j++)
{
ii=i0%2;
inv=2*inv+ii;
i0=(i0-ii)/2;
}
return (inv);
}
/* ifft=1 <--> FFT ** ifft=-1 <--> IFFT */
void fft1r2(float A[2][N], float B[2][N], int ifft)
{
unsigned long int k, j;
float norm, c[2][N];
if (ifft != -1)
{
for (k=0; k<N; k++)
{
c[0][k]=A[0][k];
c[1][k]=B[0][k];
}
fft1(c, 1);
A[0][0] = c[0][0];
A[1][0] = 0.0;
B[0][0] = c[1][0];
B[1][0] = 0.0;
for (j=1; j<N; j++)
{
A[0][j] = 0.5 * (c[0][j] + c[0][N-j]);
A[1][j] = 0.5 * (c[1][j] - c[1][N-j]);
B[0][j] = 0.5 * (c[1][j] + c[1][N-j]);
B[1][j] = -0.5 * (c[0][j] - c[0][N-j]);
}
} /* end if*/
else
{
for (j=0; j<N/2; j++)
/* check {xj}, {yj} is to real {Ak}, {Bk} */
{
if (j == 0) norm = 2.0 * fabs(A[1][0])
+ 2.0 * fabs(B[1][0]);
else norm = fabs(A[1][j]+A[1][N-j])
+ fabs(A[0][j]-A[0][N-j])
+ fabs(B[1][j]+B[1][N-j])
+ fabs(B[0][j]-B[0][N-j]);
if (norm > eps)
{
printf("The IDFT of the series isn't real!\n");
exit(0);
}
}
for (j=0; j<N; j++)
{
c[0][j]=A[0][j] -B[1][j];
c[1][j]=A[1][j] +B[0][j];
}
fft1(c, -1);
for (k=0; k<N; k++)
{
A[0][k] = c[0][k];
B[0][k] = c[1][k];
A[1][k] = 0.0;
B[1][k] = 0.0;
}
} /* end else */
}
/* ifft = 1 <--> FFT ** ifft = -1 <--> IFFT */
void fft1r1(float A[2][DN], int ifft)
{
unsigned long int j, k, dk;
float norm, c[2][N], d[2][N], tt, hunit, cosj, sinj;
hunit = 4.0 * atan(1.0) / (float)N;
if ( ifft != -1)
{
for (k=0; k<N; k++)
{
dk = k + k;
c[0][k] = A[0][dk];
d[0][k] = A[0][dk+1];
c[1][k] = 0.0;
d[1][k] = 0.0;
}
fft1r2(c, d, 1);
for (j = 0; j < N; j++)
{
tt = (float)j * hunit;
cosj = cos(tt);
sinj = sign * sin(tt);
A[0][j] = c[0][j] + cosj * d[0][j] - sinj * d[1][j];
A[1][j] = c[1][j] + cosj * d[1][j] + sinj * d[0][j];
}
A[0][N] = c[0][0] - d[0][0];
A[1][N] = c[1][0] - d[1][0];
for(j=1; j<N; j++)
{
A[0][DN-j] = A[0][j];
A[1][DN-j] = -A[1][j];
}
} // end if
else
{
for (j = 0; j <N; j++) // check {xj} is to real {Ak}
{
if (j ==0) norm = 2.0 * fabs(A[1][0]);
else norm = fabs(A[1][j]+A[1][DN-j])+
fabs(A[0][j]-A[0][DN-j]);
if (norm > eps)
{
printf("The IDFT of the series {xj} isn't real!\n");
exit(0);
}
}
for (j = 0; j <N; j++)
{
c[0][j]= 0.5 * (A[0][j]+A[0][N+j]);
c[1][j]= 0.5 * (A[1][j]+A[1][N+j]);
tt = (float) j * hunit;
cosj = cos(tt);
sinj = -sign*sin(tt);
d[0][j]= 0.5 * (cosj * (A[0][j]-A[0][N+j])
-sinj*(A[1][j] - A[1][N+j]));
}
fft1r2(c, d, -1);
for (k=0; k<N; k++)
{
dk = k+k;
A[0][dk]=c[0][k];
A[0][dk+1]=d[0][k];
A[1][dk]=0.0;
A[1][dk+1]=0.0;
}
} // end else
}
+63
View File
@@ -0,0 +1,63 @@
/**
* 一维卡尔曼滤波器
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
* 使用示例
* extKalman_t p; //定义一个卡尔曼滤波器结构体
* float SersorData; //需要进行滤波的数据
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
* while(1)
* {
* SersorData = sersor(); //获取数据
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
* }
*/
#include "kalman.h"
/**
* @name kalmanCreate
* @brief 创建一个卡尔曼滤波器
* @param p: 滤波器
* T_Q:系统噪声协方差
* T_R:测量噪声协方差
*
* @retval none
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
*/
void KalmanCreate(extKalman_t* p, float T_Q, float T_R)
{
p->X_last = (float)0;
p->P_last = 0;
p->Q = T_Q;
p->R = T_R;
p->A = 1;
p->B = 0;
p->H = 1;
p->X_mid = p->X_last;
}
/**
* @name KalmanFilter
* @brief 卡尔曼滤波器
* @param p: 滤波器
* dat:待滤波数据
* @retval 滤波后的数据
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
* 以下是卡尔曼的5个核心公式
* 一阶H'即为它本身,否则为转置矩阵
*/
float KalmanFilter(extKalman_t* p, float dat)
{
p->X_mid = p->A * p->X_last; //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
p->P_mid = p->A * p->P_last + p->Q; //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
p->kg = p->P_mid / (p->P_mid + p->R); //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
p->X_now = p->X_mid + p->kg * (dat - p->X_mid); //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
p->P_now = (1 - p->kg) * p->P_mid; //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
p->P_last = p->P_now; //状态更新
p->X_last = p->X_now;
return p->X_now; //输出预测结果x(k|k)
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef _KALMAN_H
#define _KALMAN_H
typedef struct
{
float X_last; //上一时刻的最优结果 X(k-|k-1)
float X_mid; //当前时刻的预测结果 X(k|k-1)
float X_now; //当前时刻的最优结果 X(k|k)
float P_mid; //当前时刻预测结果的协方差 P(k|k-1)
float P_now; //当前时刻最优结果的协方差 P(k|k)
float P_last; //上一时刻最优结果的协方差 P(k-1|k-1)
float kg; //kalman增益
float A; //系统参数
float B;
float Q;
float R;
float H;
} extKalman_t;
void KalmanCreate(extKalman_t* p, float T_Q, float T_R);
float KalmanFilter(extKalman_t* p, float dat);
#endif
+354
View File
@@ -0,0 +1,354 @@
#ifdef _WIN32
#include <windows.h>
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "user32.lib")
#define _USE_MATH_DEFINES
#include <cmath>
#else
#include <alsa/asoundlib.h>
#include <cmath>
#endif
#include <iostream>
#include <cstdint>
#include <vector>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
#ifndef M_LN2
#define M_LN2 0.693147180559945309417
#endif
#ifdef _WIN32
// Windows 回调结构
struct PlaybackContext {
volatile bool done;
WAVEHDR* header;
};
// Windows 播放完成回调函数
void CALLBACK waveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (uMsg == WOM_DONE) {
PlaybackContext* ctx = reinterpret_cast<PlaybackContext*>(dwInstance);
ctx->done = true;
}
}
#endif
extern "C" {
#ifdef _WIN32
#define SND_EXPORT __declspec(dllexport)
#else
#define SND_EXPORT
#endif
SND_EXPORT bool play_frequency(float frequency, int32_t duration_ms,
double volume, int32_t channels,
int32_t sample_rate) {
// 参数验证
if (frequency <= 0.0f || duration_ms <= 0 ||
volume <= 0.0 || volume > 1.0 ||
channels <= 0 || channels > 8 ||
sample_rate <= 0) {
std::cerr << "Invalid parameters" << std::endl;
return false;
}
// 精确计算帧数:使用 round 而非截断
// duration_ms / 1000.0 得到秒数,乘以采样率得到帧数
int64_t num_frames = static_cast<int64_t>(
std::round(static_cast<double>(sample_rate) * duration_ms / 1000.0)
);
if (num_frames <= 0) {
std::cerr << "Duration too short to generate any frames" << std::endl;
return false;
}
// 计算实际播放时长(用于调试)
double actual_duration_ms = static_cast<double>(num_frames) / sample_rate * 1000.0;
std::cout << "Requested duration: " << duration_ms << " ms, "
<< "Actual duration: " << actual_duration_ms << " ms, "
<< "Frames: " << num_frames << std::endl;
// 计算总采样点数
int32_t total_samples = static_cast<int32_t>(num_frames * channels);
std::vector<int16_t> buffer(total_samples);
// 计算振幅(应用对数音量缩放以获得更自然的音量控制)
int32_t amplitude = static_cast<int32_t>(32767.0 * volume);
// 生成正弦波数据
for (int64_t i = 0; i < num_frames; i++) {
// 使用 double 计算以提高精度
double t = static_cast<double>(i) / sample_rate;
double value = sin(2.0 * M_PI * frequency * t);
// 应用淡入淡出以避免咔嗒声
int64_t fade_samples = static_cast<int64_t>(sample_rate * 0.005); // 5ms 淡入淡出
double fade_factor = 1.0;
if (i < fade_samples) {
// 淡入:从 0 到 1
fade_factor = static_cast<double>(i) / fade_samples;
} else if (i >= num_frames - fade_samples) {
// 淡出:从 1 到 0
fade_factor = static_cast<double>(num_frames - 1 - i) / fade_samples;
}
int16_t sample = static_cast<int16_t>(
amplitude * value * fade_factor
);
// 填充所有声道
for (int32_t ch = 0; ch < channels; ch++) {
buffer[i * channels + ch] = sample;
}
}
#ifdef _WIN32
// ===== Windows 实现(带回调,精确计时) =====
WAVEFORMATEX wfx;
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = channels;
wfx.nSamplesPerSec = sample_rate;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = channels * 2; // 16-bit = 2 bytes per sample
wfx.nAvgBytesPerSec = sample_rate * wfx.nBlockAlign;
wfx.cbSize = 0;
PlaybackContext context;
context.done = false;
context.header = new WAVEHDR();
// 打开波形设备,使用回调函数
HWAVEOUT hWaveOut;
MMRESULT result = waveOutOpen(
&hWaveOut,
WAVE_MAPPER,
&wfx,
reinterpret_cast<DWORD_PTR>(waveOutProc), // 回调函数
reinterpret_cast<DWORD_PTR>(&context), // 传递给回调的数据
CALLBACK_FUNCTION
);
if (result != MMSYSERR_NOERROR) {
std::cerr << "Cannot open audio device. Error code: " << result << std::endl;
delete context.header;
return false;
}
// 准备 WAVEHDR
WAVEHDR& header = *context.header;
ZeroMemory(&header, sizeof(WAVEHDR));
header.lpData = reinterpret_cast<char*>(buffer.data());
header.dwBufferLength = static_cast<DWORD>(total_samples * sizeof(int16_t));
header.dwFlags = 0;
header.dwLoops = 0;
result = waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cerr << "Failed to prepare audio header. Error code: " << result << std::endl;
waveOutClose(hWaveOut);
delete context.header;
return false;
}
// 写入音频数据
result = waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
if (result != MMSYSERR_NOERROR) {
std::cerr << "Failed to write audio data. Error code: " << result << std::endl;
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutClose(hWaveOut);
delete context.header;
return false;
}
// 等待播放完成(使用事件等待而非轮询)
auto start_time = timeGetTime();
while (!context.done) {
// 使用 MsgWaitForMultipleObjects 等待消息或超时
// 这样可以在等待的同时处理 Windows 消息
DWORD wait_result = MsgWaitForMultipleObjects(0, NULL, FALSE, 100, QS_ALLINPUT);
if (wait_result == WAIT_OBJECT_0) {
// 处理 Windows 消息
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// 超时检查(防止无限等待)
DWORD elapsed = timeGetTime() - start_time;
if (elapsed > static_cast<DWORD>(duration_ms + 1000)) {
std::cerr << "Playback timeout" << std::endl;
waveOutReset(hWaveOut);
break;
}
}
auto end_time = timeGetTime();
DWORD actual_play_time = end_time - start_time;
std::cout << "Actual playback time: " << actual_play_time << " ms" << std::endl;
// 清理
waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
waveOutClose(hWaveOut);
delete context.header;
#else
// ===== Linux ALSA 实现 =====
snd_pcm_t *pcm_handle = nullptr;
const char* device = "default";
int rc = snd_pcm_open(&pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0);
if (rc < 0) {
std::cerr << "Cannot open device '" << device << "': "
<< snd_strerror(rc) << std::endl;
return false;
}
// 设置硬件参数
snd_pcm_hw_params_t *hw_params;
snd_pcm_hw_params_alloca(&hw_params);
rc = snd_pcm_hw_params_any(pcm_handle, hw_params);
if (rc < 0) {
std::cerr << "Cannot get hardware parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_access(pcm_handle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (rc < 0) {
std::cerr << "Cannot set access type: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_format(pcm_handle, hw_params,
SND_PCM_FORMAT_S16_LE);
if (rc < 0) {
std::cerr << "Cannot set sample format: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_hw_params_set_channels(pcm_handle, hw_params, channels);
if (rc < 0) {
std::cerr << "Cannot set channel count: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
unsigned int hw_sample_rate = static_cast<unsigned int>(sample_rate);
rc = snd_pcm_hw_params_set_rate_near(pcm_handle, hw_params,
&hw_sample_rate, 0);
if (rc < 0) {
std::cerr << "Cannot set sample rate: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
if (hw_sample_rate != static_cast<unsigned int>(sample_rate)) {
std::cout << "Requested sample rate " << sample_rate
<< " Hz not supported, using " << hw_sample_rate << " Hz" << std::endl;
}
// 设置缓冲区大小和周期
snd_pcm_uframes_t buffer_size;
rc = snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
if (buffer_size > static_cast<snd_pcm_uframes_t>(num_frames)) {
snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hw_params,
&reinterpret_cast<snd_pcm_uframes_t&>(num_frames));
}
rc = snd_pcm_hw_params(pcm_handle, hw_params);
if (rc < 0) {
std::cerr << "Cannot set hardware parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
// 设置软件参数
snd_pcm_sw_params_t *sw_params;
snd_pcm_sw_params_alloca(&sw_params);
rc = snd_pcm_sw_params_current(pcm_handle, sw_params);
if (rc < 0) {
std::cerr << "Cannot get software parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_sw_params_set_start_threshold(pcm_handle, sw_params,
static_cast<snd_pcm_uframes_t>(num_frames));
if (rc < 0) {
std::cerr << "Cannot set start threshold: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
rc = snd_pcm_sw_params(pcm_handle, sw_params);
if (rc < 0) {
std::cerr << "Cannot set software parameters: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
// 播放音频(写入数据)
snd_pcm_sframes_t frames_written = 0;
snd_pcm_uframes_t frames_to_write = static_cast<snd_pcm_uframes_t>(num_frames);
// 循环写入直到所有数据都播放完毕
while (frames_written < static_cast<snd_pcm_sframes_t>(num_frames)) {
snd_pcm_uframes_t remaining = static_cast<snd_pcm_uframes_t>(num_frames - frames_written);
rc = snd_pcm_writei(pcm_handle,
buffer.data() + (frames_written * channels),
remaining);
if (rc == -EPIPE) {
// 缓冲区下溢,需要恢复
std::cerr << "Buffer underrun occurred" << std::endl;
snd_pcm_prepare(pcm_handle);
rc = snd_pcm_writei(pcm_handle,
buffer.data() + (frames_written * channels),
remaining);
}
if (rc < 0) {
std::cerr << "Error writing to PCM device: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
if (rc > 0) {
frames_written += rc;
}
}
// 等待所有数据播放完成
rc = snd_pcm_drain(pcm_handle);
if (rc < 0) {
std::cerr << "Error draining PCM device: " << snd_strerror(rc) << std::endl;
snd_pcm_close(pcm_handle);
return false;
}
snd_pcm_close(pcm_handle);
#endif
return true;
}
} // extern "C"
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <cuda_runtime.h>
#include <stdio.h>
#define CHECK(call) do {\
cudaError_t err = call;\
if (err != cudaSuccess) {\
fprintf(stderr, "CUDA error at %s:%d\n", __FILE__, __LINE__);\
fprintf(stderr, "%s\n", cudaGetErrorString(err));\
exit(EXIT_FAILURE);\
}\
} while (0)
+872
View File
@@ -0,0 +1,872 @@
#include <cstdint>
#include <stdint.h>
#include <time.h>
#include <random>
#include "error.cuh"
extern "C" {
/* 创建一个设备数组 */
static void* _crtDevArr(const size_t m)
{
void *d;
CHECK(cudaMalloc(&d, m));
return d;
}
/* 释放一个设备数组 */
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Free(void* d)
#else
void Cu4cj_Free(void* d)
#endif
{
CHECK(cudaFree(d));
}
/* 创建一个单精度浮点数组 输入数组的长度(不初始化) */
#ifdef _WIN64
__declspec(dllexport) void* Cu4cj_Orig(uint64_t n)
#else
void* Cu4cj_Orig(int64_t n)
#endif
{
return _crtDevArr(n * sizeof(float));
}
/*
* 从主机内存拷贝数据到设备内存
* FIXME: 是否需要做成输入目的地址
*/
#ifdef _WIN64
__declspec(dllexport) float* Cu4cj_ToDev(float* h, uint64_t n)
#else
float* Cu4cj_ToDev(float* h, int64_t n)
#endif
{
float* d = (float*)Cu4cj_Orig(n);
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
return d;
}
/* 从设备内存拷贝数据到主机内存 */
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_ToHost(float* d, float* h, const uint64_t n)
#else
void Cu4cj_ToHost(float* d, float* h, int64_t n)
#endif
{
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
}
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Copy(float* d, float* h, const uint64_t n)
#else
void Cu4cj_Copy(float* d, float* h, int64_t n)
#endif
{
CHECK(cudaMemcpy(h, d, n * sizeof(float), cudaMemcpyDeviceToHost));
}
/* 创建一个单精度浮点数组并将其初始化为零
* FIXME: 当n == 10000 时进程被杀死 */
#ifdef _WIN64
__declspec(dllexport) float* Cu4cj_Zero(const uint64_t n)
#else
float* Cu4cj_Zero(int64_t n)
#endif
{
float* d = (float*)Cu4cj_Orig(n);
CHECK(cudaMemset(d, 0, n*sizeof(float)));
return d;
}
/* 正态分布初始化(适用于神经网络权重) */
#ifdef _WIN64
__declspec(dllexport) float* Cu4cj_Normal(uint64_t n, uint64_t fan_in, uint64_t fan_out)
#else
float* Cu4cj_Normal(int64_t n, int64_t fan_in, int64_t fan_out)
#endif
{
// 计算 Xavier/Glorot 初始化的标准差
float std = sqrt(2.0f / (fan_in + fan_out));
// 先在主机上分配内存并生成正态分布随机数
float* h = (float*)malloc(n * sizeof(float));
/* 初始化随机数种子 */
srand(time(NULL));
for (size_t i = 0; i < n; i++) {
// 使用 Box-Muller 变换生成正态分布随机数
float u1 = (float)rand() / RAND_MAX;
float u2 = (float)rand() / RAND_MAX;
float z = sqrt(-2.0f * log(u1)) * cos(2.0f * 3.1415926535f * u2);
h[i] = z * std;
}
// 在设备上分配内存
float* d = (float*)Cu4cj_Orig(n);
// 将主机内存中的数据拷贝到设备内存
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
// 释放主机内存
free(h);
return d;
}
// 向量加法
static void __global__ _devAdd(float* d1, float* d2, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] + d2[i];
}
}
// 向量加法
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Add(float* d1, float* d2, float* d_r, uint64_t n)
#else
void Cu4cj_Add(float* d1, float* d2, float* d_r, int64_t n)
#endif
{
_devAdd<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 向量减法
static void __global__ _devSub(float* d1, float* d2, float* r, int64_t n)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] - d2[i];
}
}
// 向量减法
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Sub(float* d1, float* d2, float* d_r, uint64_t n)
#else
void Cu4cj_Sub(float* d1, float* d2, float* d_r, int64_t n)
#endif
{
_devSub<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 向量依次相乘
static void __global__ _devMul(float* d1, float* d2, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] * d2[i];
}
}
// 向量依次相乘
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Mul(float* d1, float* d2, float* d_r, uint64_t n)
#else
void Cu4cj_Mul(float* d1, float* d2, float* d_r, int64_t n)
#endif
{
_devMul<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 向量依次相除
static void __global__ _devDiv(float* d1, float* d2, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] / d2[i];
}
}
// 向量依次相除
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_Div(float* d1, float* d2, float* d_r, uint64_t n)
#else
void Cu4cj_Div(float* d1, float* d2, float* d_r, int64_t n)
#endif
{
_devDiv<<<(n + 255) / 256, 256>>>(d1, d2, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 向量加法广播
static void __global__ _devAddBroadcast(float* d1, float a, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] + a;
}
}
// 加法广播
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, uint64_t n)
#else
void Cu4cj_AddBroadcast(float* d1, float a, float* d_r, int64_t n)
#endif
{
_devAddBroadcast<<<(n + 255) / 256, 255>>>(d1, a, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 减法广播
static void __global__ _devSubBroadcast(float* d1, float a, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] - a;
}
}
// 减法广播
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, uint64_t n)
#else
void Cu4cj_SubBroadcast(float* d1, float a, float* d_r, int64_t n)
#endif
{
_devSubBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 乘法广播
static void __global__ _devMulBroadcast(float* d1, float a, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] * a;
}
}
// 乘法广播
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, uint64_t n)
#else
void Cu4cj_MulBroadcast(float* d1, float a, float* d_r, int64_t n)
#endif
{
_devMulBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 除法广播
static void __global__ _devDivBroadcast(float* d1, float a, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] / a;
}
}
// 除法广播
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, uint64_t n)
#else
void Cu4cj_DivBroadcast(float* d1, float a, float* d_r, int64_t n)
#endif
{
_devDivBroadcast<<<(n + 255) / 256, 256>>>(d1, a, d_r, n);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 矩阵乘法
static void __global__ _devMatDot(float* d1, float* d2, float* r, int64_t m, int64_t n, int64_t k) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
if (i < m && j < k) {
float sum = 0.0f;
for (int p = 0; p < n; p++) {
sum += d1[i * n + p] * d2[p * k + j];
}
r[i * k + j] = sum;
}
}
// 矩阵乘法
// m、n、k 分别为矩阵 A、B、C 的行数、列数
// 其中 n 为矩阵 A 的列数,也为矩阵 B 的行数
#ifdef _WIN64
__declspec(dllexport) void Cu4cj_MatDot(float* d1, float* d2, float* d_r, uint64_t m, uint64_t n, uint64_t k)
#else
void Cu4cj_MatDot(float* d1, float* d2, float* d_r, int64_t m, int64_t n, int64_t k)
#endif
{
dim3 blockDim(16, 16);
dim3 gridDim((m + blockDim.x - 1) / blockDim.x, (k + blockDim.y - 1) / blockDim.y);
_devMatDot<<<gridDim, blockDim>>>(d1, d2, d_r, m, n, k);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
}
// 矩阵转置
static void __global__ _devTransport(float* d1, float* d2, int64_t m, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
if (i < m && j < n) {
d2[j * m + i] = d1[i * n + j];
}
}
#ifdef _WIN64
__declspec(dllexport) float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
#else
float* Cu4cj_Transport(float *m, uint64_t r, uint64_t c)
#endif
{
float *d_r = (float*)Cu4cj_Zero(c * r * sizeof(float));
_devTransport<<<1, c * r>>>(m, d_r, c, r);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return d_r;
}
// Sigmoid 激活函数,输入为矩阵,输出为矩阵
static void __global__ _devSigmoid(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = 1.0f / (1.0f + exp(-d1[i]));
}
}
// Sigmoid 导数
static void __global__ _devSigmoidDerivative(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] * (1.0f - d1[i]);
}
}
// ReLU
static void __global__ _devReLU(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] > 0.0f ? d1[i] : 0.0f;
}
}
// ReLU 导数
static void __global__ _devReLUDerivative(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] > 0.0f ? 1.0f : 0.0f;
}
}
// Leaky_ReLU
static void __global__ _devLeakyReLU(float* d1, float* r, int64_t n, float a) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] > 0.0f ? d1[i] : a * d1[i];
}
}
// Leaky_ReLU 导数
static void __global__ _devLeakyReLU_Derivative(float* d1, float* r, int64_t n, float a) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = d1[i] > 0.0f ? 1.0f : a;
}
}
// Tanh 激活函数,输入为矩阵,输出为矩阵
static void __global__ _devTanh(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = (exp(d1[i]) - exp(-d1[i])) / (exp(d1[i]) + exp(-d1[i]));
}
}
// Tanh 导数
static void __global__ _devTanhDerivative(float* d1, float* r, int64_t n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
r[i] = 1.0f - d1[i] * d1[i];
}
}
}
namespace qing {
class Matrix {
public:
Matrix(uint64_t r, uint64_t c) {
this->r = r;
this->c = c;
this->d = (float*)Cu4cj_Zero(r * c * sizeof(float));
}
Matrix(uint64_t r, uint64_t c, float* d) {
this->r = r;
this->c = c;
this->d = d;
}
Matrix(const Matrix& m) {
this->r = m.getR();
this->c = m.getC();
this->d = (float*)Cu4cj_Zero(m.getR() * m.getC() * sizeof(float));
Cu4cj_Copy(m.getD(), d, m.getR() * m.getC() * sizeof(float));
}
~Matrix() {
Cu4cj_Free(d);
}
uint64_t getR() const {
return r;
}
uint64_t getC() const {
return c;
}
float* getD() const {
return d;
}
float get(uint64_t r, uint64_t c) const {
float h;
CHECK(cudaMemcpy(&h, &d[c * this->r + r], sizeof(float), cudaMemcpyDeviceToHost));
return h;
}
void set(uint64_t r, uint64_t c, float v) {
CHECK(cudaMemcpy(&d[c * this->r + r], &v, sizeof(float), cudaMemcpyHostToDevice));
}
bool hasSameShape(const Matrix& m) {
return r == m.getR() && c == m.getC();
}
bool hasDotShape(const Matrix& m) {
return c == m.getR();
}
Matrix operator+(const Matrix& m) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_Add(d, m.getD(), d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator+(float a) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_AddBroadcast(d, a, d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator-(const Matrix& m) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_Sub(d, m.getD(), d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator-(float a) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_SubBroadcast(d, a, d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator*(const Matrix& m) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_Mul(d, m.getD(), d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator*(float a) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_MulBroadcast(d, a, d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator/(const Matrix& m) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_Div(d, m.getD(), d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix operator/(float a) {
float *d_r = (float*)Cu4cj_Zero(r * c * sizeof(float));
Cu4cj_DivBroadcast(d, a, d_r, r * c);
return Matrix(r, c, d_r);
}
Matrix dot(const Matrix& m) {
//if (!hasDotShape(m)) {
// throw std::invalid_argument("Dot product: matrices must have compatible shapes");
//}
float *d_r = (float*)Cu4cj_Zero(r * m.getC() * sizeof(float));
Cu4cj_MatDot(d, m.getD(), d_r, r, c, m.getC());
return Matrix(r, m.getC(), d_r);
}
Matrix T() {
float *d_r = (float*)Cu4cj_Transport(d, r, c);
return Matrix(c, r, d_r);
}
private:
uint64_t r, c;
float* d;
};
}
using namespace qing;
extern "C" {
#ifdef _WIN64
__declspec(dllexport) void* createMatrix(const uint64_t r, const uint64_t c)
#else
void* createMatrix(int64_t r, int64_t c)
#endif
{
return(void*) new Matrix(r, c);
}
#ifdef _WIN64
__declspec(dllexport) void freeMatrix(void* m)
#else
void freeMatrix(void* m)
#endif
{
delete (Matrix*)m;
}
#ifdef _WIN64
__declspec(dllexport) void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
#else
void* xavierNormalInit(uint64_t fan_in, uint64_t fan_out)
#endif
{
float* d = Cu4cj_Normal(fan_in * fan_out, fan_in, fan_out);
return (void *)new Matrix(fan_in, fan_out, d);
}
#ifdef _WIN64
__declspec(dllexport) void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
#else
void* xavierUniformInit(uint64_t fan_in, uint64_t fan_out)
#endif
{
uint64_t n = fan_in * fan_out;
// 先在主机上分配内存并生成正态分布随机数
float* h = (float*)malloc(n * sizeof(float));
/* 使用高质量的随机数生成器 */
static std::mt19937 generator(std::random_device{}());
/* 计算区间范围:sqrt(2 / (fan_in + fan_out)) */
float scale = std::sqrt(2.0f / (fan_in + fan_out));
/* 创建均匀分布对象,范围为[-scale, scale] */
std::uniform_real_distribution<float> distribution(-scale, scale);
/* 遍历权重矩阵,进行赋值 */
uint64_t k=0;
for (uint64_t i=0; i<fan_in; ++i)
for (uint64_t j=0; j<fan_out; ++j)
h[k++] = distribution(generator);
// 在设备上分配内存
float* d = (float*)Cu4cj_Orig(n*sizeof(float));
// 将主机内存中的数据拷贝到设备内存
CHECK(cudaMemcpy(d, h, n * sizeof(float), cudaMemcpyHostToDevice));
// 释放主机内存
free(h);
return (void *)new Matrix(fan_in, fan_out, d);
}
#ifdef _WIN64
__declspec(dllexport) uint64_t getrowMatrix(void *m)
#else
uint64_t getrowMatrix(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
return m1->getR();
}
#ifdef _WIN64
__declspec(dllexport) uint64_t getcolMatrix(void *m)
#else
uint64_t getcolMatrix(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
return m1->getC();
}
#ifdef _WIN64
__declspec(dllexport) float getMatrix(void *m, uint64_t r, uint64_t c)
#else
float getMatrix(void *m, uint64_t r, uint64_t c)
#endif
{
Matrix* m1 = (Matrix*)m;
return m1->get(r, c);
}
#ifdef _WIN64
__declspec(dllexport) void setMatrix(void *m, uint64_t r, uint64_t c, float v)
#else
void setMatrix(void *m, uint64_t r, uint64_t c, float v)
#endif
{
Matrix* m1 = (Matrix*)m;
m1->set(r, c, v);
}
#ifdef _WIN64
__declspec(dllexport) bool sameShapeMatrix(void *m, void *s)
#else
bool sameShapeMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
return (*m1).hasSameShape(*s1);
}
#ifdef _WIN64
__declspec(dllexport) bool dotShapeMatrix(void *m, void *s)
#else
bool dotShapeMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
return (*m1).hasDotShape(*s1);
}
#ifdef _WIN64
__declspec(dllexport) void* plusMatrix(void *m, void *s)
#endif
#ifdef __linux__
void* plusMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
Matrix r = *m1 + *s1;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* plusBroadcastMatrix(void *m, float a)
#else
void* plusBroadcastMatrix(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix r = *m1 + a;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* subMatrix(void *m, void *s)
#else
void* subMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
Matrix r = *m1 - *s1;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* subBroadcastMatrix(void *m, float a)
#else
void* subBroadcastMatrix(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix r = *m1 - a;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* multiMatrix(void *m, void *s)
#else
void* multiMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
Matrix r = (*m1) * (*s1);
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* multiBroadcastMatrix(void *m, float a)
#else
void* multiBroadcastMatrix(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix r = (*m1) * a;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* divMatrix(void *m, void *s)
#else
void* divMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
Matrix r = (*m1) / (*s1);
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* divBroadcastMatrix(void *m, float a)
#else
void* divBroadcastMatrix(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix r = (*m1) / a;
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* dotMatrix(void *m, void *s)
#else
void* dotMatrix(void *m, void *s)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix* s1 = (Matrix*)s;
Matrix r = (*m1).dot(*s1);
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* transposeMatrix(void *m)
#endif
#ifdef __linux__
void* transposeMatrix(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix r = m1->T();
return (void *)new Matrix(r);
}
#ifdef _WIN64
__declspec(dllexport) void* Sigmoid_do(void *m)
#else
void* Sigmoid_do(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devSigmoid<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* Sigmoid_dot(void *m)
#else
void* Sigmoid_dot(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devSigmoidDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* ReLU_do(void *m)
#else
void* ReLU_do(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* ReLU_dot(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devReLUDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* Leaky_ReLU_do(void *m, float a)
#else
void* LeakyReLU_do(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devLeakyReLU<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* Leaky_ReLU_dot(void *m, float a)
#else
void* LeakyReLU_dot(void *m, float a)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devLeakyReLU_Derivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC(), a);
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
// Tanh
#ifdef _WIN64
__declspec(dllexport) void* Tanh_do(void *m)
#else
void* Tanh_do(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devTanh<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
#ifdef _WIN64
__declspec(dllexport) void* Tanh_dot(void *m)
#else
void* Tanh_dot(void *m)
#endif
{
Matrix* m1 = (Matrix*)m;
Matrix s = Matrix(*m1);
_devTanhDerivative<<<1, s.getR()* s.getC()>>>(s.getD(), s.getD(), s.getR()* s.getC());
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
return (void *)new Matrix(s);
}
}
+339
View File
@@ -0,0 +1,339 @@
/* TODO(20260518-qing): 稍后我们专门做一个i2c代码仓库,这样就能够区分不同架构上面的int的大小
* TODO(20260615-qing): 有没有可能只做一个简单的封装不引入异常机制 */
package bot4cj.hd
import pthread4cj.*
import ctype4cj.*
/* -------- 我们将直接调用glibc中的一些函数 -------- */
foreign func ioctl(fd: C_Int, addr: C_ULong, ...): C_Int /* -------- 成功返回非负整数,失败返回-1 -------- */
const I2C_FAILED = -1i32 /* -------- I2C关闭文件失败 -------- */
const I2C_SUCCESS = 0i32 /* -------- I2C关闭文件成功 -------- */
const I2C_SLAVE = 0x0703u64 /* -------- at least on arm64 -------- */
/* -------- OPEN I2C DEVICE 打开I2C设备 -------- */
@C
public func openI2cDev(path: CString): Int32 {
unsafe {
let fd = libc_open(path, O_RDWR)
if (fd > 0) {
I2cLock_create(fd) /* -------- 为I2C设备创建锁 -------- */
return fd
}
else {
throw I2cOpenedFailed("?")
}
}
}
/* ---- 简化版,判断在外部实现 ---- */
@C
public func openI2cDev1(path: CString): C_Int {
unsafe {
let fd = libc_open(path, O_RDWR)
I2cLock_create(fd)
return fd
}
}
/* -------- CLOSE I2C DEVICE 关闭I2C设备 -------- */
@C
public func closeI2cDev(fd: Int32): Unit {
unsafe {
I2cLock_destroy(fd) /* -------- 销毁I2C设备的锁 -------- */
let ret = libc_close(fd)
if (ret == I2C_FAILED) {
throw I2cClosedFailed("?") }
else if (ret == I2C_SUCCESS) { ; }
else {
throw I2cUnknowExp(
"?", "$function returned: {ret.toString()}")
}
}
}
@C
public func closeI2cDev1(fd: C_Int): Unit {
unsafe {
I2cLock_destroy(fd)
libc_close(fd)
}
}
/* -------- SET I2C ADDR 设置I2C地址 -------- */
@C
public func setI2cAddr(fd: Int32, addr: UInt8): Unit {
unsafe {
let ret = ioctl(fd, I2C_SLAVE, addr)
if (ret < 0) {
throw I2cSetAddressFailed("?") }
}
}
@C
public func setI2cAddr1(fd: C_Int, addr: UInt8): C_Int {
unsafe {
return ioctl(fd, I2C_SLAVE, addr)
}
}
/* -------- WRITE I2C REG 写I2C寄存器 -------- */
@C
public func writeI2cReg(fd: Int32, reg: UInt8, val:UInt8): Unit {
var a: VArray<UInt8, $2> = [reg, val]
var p: CPointer<Unit> = unsafe { CPointer<Unit>(inout a) }
if (2 != unsafe { libc_write(fd, p, 2) }) {
throw I2cWriteRegFailed("?")
}
}
/* -------- READ I2C REG 读I2C寄存器 --------
* INFO(20260522-qing): 读取寄存器之前要先写寄存器地址
* INFO(20260616-qing): 这个函数只读取一个字节的数据
* -------- */
@C
public func readI2cReg(fd: Int32, reg: UInt8): Int8 {
var ptr = reg
if (1 != unsafe { libc_write(fd, CPointer<Unit>(inout ptr), 1) }) {
throw I2cReadRegFailed("?")
}
var value: Int8 = 0
var p = unsafe { CPointer<Unit>(inout value) }
let ret = unsafe { libc_read(fd, p, 1) }
if (ret != 1) { throw I2cReadRegFailed("?") }
return value
}
/* -------- I2C锁具 --------
* INFO(20260521-qing): 因为每个I2C接口在通信时具有原子化的需求
* - 需要一个互斥锁集合,在打开第一个I2C设备时被初始化,
* - 释放最后一个I2C设备时,将锁具释放
* - **每次进行I2C通信时,进行上锁操作** */
@C
struct I2C_Lock {
var i2c_fd: Int32 = 0 /* -------- 这个锁绑定的I2C设备文件句柄 -------- */
var lock = CPointer<UInt8>() /* -------- phread_mutex_t 锁对象 -------- */
var is_active = false /* -------- 该锁具是否被激活 --------*/
}
var MAIN_LOCK = CPointer<UInt8>() /* -------- 主锁,操作锁具时使用 -------- */
var LOCKS = CPointer<I2C_Lock>() /* -------- 指向锁具内存的指针 -------- */
const LOCKS_SIZE: Int64 = 20 /* -------- 锁具的容量 --------*/
/* -------- 为一个I2C设备注册锁具 -------- */
@C
func I2cLock_create(fd: Int32): Unit {
unsafe {
/* -------- 初始化主要的锁 -------- */
if (MAIN_LOCK.isNull()) {
MAIN_LOCK = _mutex_create()
LOCKS = LibC.malloc<I2C_Lock>(count: LOCKS_SIZE)
for (i in 0..20) {
let tmp = I2C_Lock()
(LOCKS+i).write(tmp)
}
}
/* -------- 获取主锁 --------*/
_mutex_lock(MAIN_LOCK)
var flag = false
for (idx in 0..LOCKS_SIZE) {
var lock = (LOCKS+idx).read()
if (lock.is_active == false) {
lock.is_active = true
lock.i2c_fd = fd
lock.lock = _mutex_create()
_mutex_init(lock.lock)
(LOCKS+idx).write(lock)
flag = true
break
}
}
_mutex_unlock(MAIN_LOCK)
if (flag ) { return } /* -------- 出口 -------- */
throw OverflowException("LOCKS is over.")
}
}
/* -------- 为一个I2C设备注销锁具 -------- */
@C
func I2cLock_destroy(fd: Int32): Unit {
unsafe {
/* -------- 检查锁具有没有被初始化 --------*/
if (MAIN_LOCK.isNull()) {
throw NoneValueException("no i2c lock")
}
_mutex_lock(MAIN_LOCK)
var flag_any = false
var flag_target = false
for (idx in 0..LOCKS_SIZE) {
var lock = (LOCKS+idx).read()
if (lock.is_active == true && lock.i2c_fd == fd) {
lock.is_active = false
_mutex_destroy(lock.lock)
_mutex_free(lock.lock)
(LOCKS+idx).write(lock)
flag_target = true
/* -------- NOTE: 扫描整个锁具不终止 -------- */
}
else if (lock.is_active == true && lock.i2c_fd != fd) {
flag_any = true
}
}
_mutex_unlock(MAIN_LOCK)
/* -------- 如果锁具里已经没有I2C锁, 就释放整个锁具 -------- */
if (!flag_any) {
LibC.free(LOCKS)
_mutex_free(MAIN_LOCK)
MAIN_LOCK = CPointer<UInt8>()
}
/* -------- 出口 -------- */
if (flag_target) {
return }
throw NoneValueException("i2c lock nor found")
}
}
/* -------- 为一个I2C锁具加锁 -------- */
@C
func I2cLock_lock(fd: Int32): Unit {
unsafe {
if (MAIN_LOCK.isNull()) {
throw NoneValueException("no i2c lock")
}
_mutex_lock(MAIN_LOCK)
var flag = false
for (idx in 0..LOCKS_SIZE) {
var lock = (LOCKS+idx).read()
if (lock.is_active == true && lock.i2c_fd == fd) {
_mutex_lock(lock.lock)
(LOCKS+idx).write(lock)
flag = true
break
}
}
_mutex_unlock(MAIN_LOCK)
if (flag) { return }
throw NoneValueException("i2c lock nor found")
}
}
/* -------- 为一个I2C锁具解锁 -------- */
@C
func I2cLock_unlock(fd: Int32): Unit {
unsafe {
if (MAIN_LOCK.isNull()) {
throw NoneValueException("no i2c lock")
}
_mutex_lock(MAIN_LOCK)
var flag = false
for (idx in 0..LOCKS_SIZE) {
var lock = (LOCKS+idx).read()
if (lock.is_active == true && lock.i2c_fd == fd) {
_mutex_unlock(lock.lock)
(LOCKS+idx).write(lock)
flag = true
break
}
}
_mutex_unlock(MAIN_LOCK)
if (flag) {return}
throw NoneValueException("i2c lock nor found")
}
}
/* -------- 原生I2C通信类--------
* INFO(20260521-qing): 直接调用glibc的接口 */
public class I2C_Orig {
private var fd: Int32 = -1
private var path: String
public init (path: String) {
this.path = path
let s1 = unsafe {
LibC.mallocCString(path) }
fd = unsafe { openI2cDev(s1) }
}
public func _close(): Unit {
unsafe { closeI2cDev(fd) }
}
public func setAddr(addr: UInt8) {
unsafe { setI2cAddr(fd, addr) }
}
public func writeReg(reg: UInt8, val:UInt8): Unit {
unsafe { writeI2cReg(fd, reg, val) }
}
public func readReg(reg: UInt8): Int8 {
var value = unsafe { readI2cReg(fd, reg) }
return value
}
/*--------获取文件句柄--------*/
public func getFd(): Int32 {
return fd
}
}
/* -------- I2C设备异常 -------- */
open private class I2cExcept <: Exception {
public I2cExcept(dev: String, msg: String) {
super('${dev}: ${msg}') }
}
/* -------- 打开I2C设备失败 -------- */
private class I2cOpenedFailed <: I2cExcept {
public I2cOpenedFailed(dev: String) {
super(dev, "failed to open i2c device") }
}
/* -------- 关闭I2C设备失败 -------- */
private class I2cClosedFailed <: I2cExcept {
public I2cClosedFailed(dev: String) {
super(dev, "failed to close i2c device") }
}
/* -------- I2C设置地址失败 -------- */
private class I2cSetAddressFailed <: I2cExcept {
public I2cSetAddressFailed(dev: String) {
super(dev, "failed to open i2c device") }
}
/* -------- I2C写从机I2C失败 -------- */
private class I2cWriteRegFailed <: I2cExcept {
public I2cWriteRegFailed(dev: String) {
super(dev, "failed to write i2c reg") }
}
/* -------- I2C读从机I2C失败 -------- */
private class I2cReadRegFailed <: I2cExcept {
public I2cReadRegFailed(dev: String) {
super(dev, "failed to read i2c reg") }
}
/* -------- 未知I2C异常 -------- */
private class I2cUnknowExp <: I2cExcept {
public I2cUnknowExp(dev: String, msg: String) {
super(dev, msg) }
}
+243
View File
@@ -0,0 +1,243 @@
/* ----------------
* MPU6050陀螺仪与加速度计模块
* -
* TODO(20260516-qing): 陀螺仪需要卡尔曼滤波、通过四元数等方式获取姿态角
* - 所以应该使用一个C/C++线程来持续地采样,并且留一个接口进行获取
*/
package bot4cj.hd
import std.time.*
import std.math.*
import pthread4cj.*
import bot4cj.math.*
const ADDR_0: UInt8 = 0x68 /* 默认地址 */
const ADDR_1: UInt8 = 0x68 /* 地址2 */
const PWR_MGMT_1: UInt8 = 0x6B /* 电源管理寄存器1 */
const PWR_NOSLEEP: UInt8 = 0x00 /* 电源管理 - 非睡眠模式 */
const GYRO_CONF_REG: UInt8 = 0x1B /* 陀螺仪配置寄存器 */
const GYRO_FS_500DPS: UInt8 = 0x08 /* 陀螺仪量程 - 正负500度/秒 */
const GYRO_REG_X_HIGH: UInt8 = 0x43 /* x - 陀螺仪横轴 高字节 */
const GYRO_REG_X_LOW: UInt8 = 0x44
const GYRO_REG_Y_HIGH: UInt8 = 0x45 /* y */
const GYRO_REG_Y_LOW: UInt8 = 0x46
const GYRO_REG_Z_HIGH: UInt8 = 0x47 /* z */
const GYRO_REG_Z_LOW: UInt8 = 0x48
const ACCEL_CONF_REG: UInt8 = 0x1C /* 加速度计配置寄存器 */
const ACCEL_FS_8g: UInt8 = 0x10 /* 加速度计量程 正负8g */
const ACCEL_REG_X_HIGH: UInt8 = 0x3B
const ACCEL_REG_X_LOW: UInt8 = 0x3C
const ACCEL_REG_Y_HIGH: UInt8 = 0x3D
const ACCEL_REG_Y_LOW: UInt8 = 0x3E
const ACCEL_REG_Z_HIGH: UInt8 = 0x3F
const ACCEL_REG_Z_LOW: UInt8 = 0x40
/* -------- MPU6050传感器数据 -------- */
@C
public struct MPU6050_sensorData {
public var accel_x: Int16 = 0 /* 加速度计 */
public var accel_y: Int16 = 0
public var accel_z: Int16 = 0
public var gyro_x: Int16 = 0 /* 陀螺仪 */
public var gyro_y: Int16 = 0
public var gyro_z: Int16 = 0
public func print() { /* 格式化打印 */
println("[accel x, y, z] ${accel_x}, ${accel_y}, ${accel_z}")
println("[gyro x, y, z] ${gyro_x}, ${gyro_y}, ${gyro_z}")
println("--------------------")
}
}
/* -------- 设置MPU6050 -------- */
@C
func mpu6050_setup(i2c_fd: Int32, addr: UInt8) {
unsafe {
I2cLock_lock(i2c_fd)
setI2cAddr(i2c_fd, addr);
sleep(Duration.millisecond * 1)
writeI2cReg(i2c_fd, PWR_MGMT_1, PWR_NOSLEEP); /* 退出睡眠 */
writeI2cReg(i2c_fd, GYRO_CONF_REG, GYRO_FS_500DPS); /* ±500°/s */
writeI2cReg(i2c_fd, ACCEL_CONF_REG, ACCEL_FS_8g); /* ±8g */
I2cLock_unlock(i2c_fd)
sleep(Duration.millisecond * 100); /* 等待稳定 */
}
}
@C
/* -------- 读取 16 位传感器数据(带符号)--------
* FIXME: 是否能够正确处理符号位 */
func read16BitReg(i2c_fd: Int32, high: UInt8, low: UInt8): Int16 {
unsafe {
let hh: Int16 = Int16(readI2cReg(i2c_fd, high))
let ll: Int16 = Int16(readI2cReg(i2c_fd, low))
return (hh << 8) | ll
}
}
/* -------- 读取传感器数据 -------- */
@C
func mpu6050_readSensor(i2c_fd: Int32, addr: UInt8) {
var data = MPU6050_sensorData()
unsafe {
I2cLock_lock(i2c_fd)
setI2cAddr(i2c_fd, addr)
data.accel_x = read16BitReg(i2c_fd, ACCEL_REG_X_HIGH, ACCEL_REG_X_LOW);
data.accel_y = read16BitReg(i2c_fd, ACCEL_REG_Y_HIGH, ACCEL_REG_Y_LOW);
data.accel_z = read16BitReg(i2c_fd, ACCEL_REG_Z_HIGH, ACCEL_REG_Z_LOW);
data.gyro_x = read16BitReg(i2c_fd, GYRO_REG_X_HIGH, GYRO_REG_X_LOW);
data.gyro_y = read16BitReg(i2c_fd, GYRO_REG_Y_HIGH, GYRO_REG_Y_LOW);
data.gyro_z = read16BitReg(i2c_fd, GYRO_REG_Z_HIGH, GYRO_REG_Z_LOW);
I2cLock_unlock(i2c_fd)
}
return data
}
/* -------- 计算PITCH角 -------- */
//@C
//func mpu6050_calcPitch()
/* -------- mpu6050主线程 --------
* TODO(20260525-qing): 使用互补滤波就够用了 */
const RAD_TO_DEG = 57.2956f32 /* ---- 弧度转换为角度的系数(180/π ≈ 57.2956) ---- */
//foreign func pthread_testcancel(): Unit
@C
func mpu6050_main(args: CPointer<Unit>): CPointer<Unit> {
/* -------- 获取传入线程的参数 -------- */
let param = unsafe{ CPointer<mpu6050_th_param>(args).read() }
/* -------- 为加速度计准备滤波器组 -------- */
var kalman_accel_x = extKalman(20.0f32, 200.0f32)
var kalman_accel_y = extKalman(20.0f32, 200.0f32)
var kalman_accel_z = extKalman(20.0f32, 200.0f32)
/* -------- 为陀螺仪准备滤波器组 -------- */
var kalman_gyro_x = extKalman(20.0f32, 200.0f32)
var kalman_gyro_y = extKalman(20.0f32, 200.0f32)
var kalman_gyro_z = extKalman(20.0f32, 200.0f32)
while(true) {
println("I am running. i2c_fd: ${param.i2c_fd}, addr: ${param.addr}")
//unsafe { _mutex_lock(mutex) }
/* -------- 获取传感器数据 -------- */
var data = unsafe { mpu6050_readSensor(param.i2c_fd, param.addr) }
/* -------- 为加速度计执行滤波操作 -------- */
let filtered_accel_x = kalman_accel_x.filter(Float32(data.accel_x))
let filtered_accel_y = kalman_accel_y.filter(Float32(data.accel_y))
let filtered_accel_z = kalman_accel_z.filter(Float32(data.accel_z))
/* -------- 为陀螺仪执行滤波操作 -------- */
let filtered_gyro_x = kalman_gyro_x.filter(Float32(data.gyro_x))
let filtered_gyro_y = kalman_gyro_y.filter(Float32(data.gyro_y))
let filtered_gyro_z = kalman_gyro_z.filter(Float32(data.gyro_z))
/* -------- 计算Pitch(俯仰角)和Roll(翻滚角) -------- */
let roll = atan2(filtered_accel_y, filtered_accel_z) * RAD_TO_DEG
let pitch = atan(-filtered_accel_x / sqrt(filtered_accel_y * filtered_accel_y + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
let roll1 = atan(filtered_accel_y / sqrt(filtered_accel_x * filtered_accel_x + filtered_accel_z * filtered_accel_z)) * RAD_TO_DEG
let pitch1 = atan2(-filtered_accel_x, filtered_accel_z) * RAD_TO_DEG
print("roll: ${roll}\t")
println("pitch: ${pitch}")
print("roll1: ${roll1}\t")
println("pitch1: ${pitch1}")
//unsafe { _mutex_unlock(mutex) }
sleep(Duration.millisecond * 10)
}
/* -------- 返回NULL指针以占位 -------- */
return CPointer<Unit>()
}
/* -------- mpu6050线程参数 --------
* INFO(20260522-qing): mutex字段是一个锁的指针,需要在MPU6050的构造函数中获取资源
* - 用于辅助别的线程获取值。 */
@C
struct mpu6050_th_param {
public var i2c_fd: Int32 = 0 /* i2c文件句柄 */
public var addr: UInt8 = 0 /* i2c设备地址 */
public var mutex = CPointer<UInt8>() /* 用于辅助别的线程获取值的锁 */
}
/* -------- MPU6050陀螺仪、加速度计 --------
* - 输入并持有一个I2C文件句柄 */
public class MPU6050 {
/* -------- 主初始化器 --------
* FIXME(20260517-qing): 由于gc的原因,我们可能会手动构造RAII,
* - 将一部分初始化移动到别的函数 */
public init(i2c_fd: Int32, addr: UInt8) {
this.i2c_fd = i2c_fd
this.addr = addr
unsafe {
param = LibC.malloc<mpu6050_th_param>()
var tmp = mpu6050_th_param()
tmp.i2c_fd = i2c_fd
tmp.addr = addr
tmp.mutex = LibC.malloc<UInt8>()
param.write(tmp)
}
}
/* -------- 终结器 -------- */
~init() {
/* -------- 清除构造时申请的线程参数和锁 -------- */
unsafe {
var tmp = param.read()
LibC.free(tmp.mutex)
LibC.free(param)
}
}
/* -------- 初始化该模块 -------- */
public func setup() {
unsafe { mpu6050_setup(i2c_fd, addr) }
}
/*----------------*/
private var i2c_fd: Int32 /* -------- I2C设备文件句柄 -------- */
private var addr: UInt8 /* -------- I2C设备地址 -------- */
/* -------- 获取原始MPU6050传感器数据 -------- */
public func readSensor(): MPU6050_sensorData {
return unsafe { mpu6050_readSensor(i2c_fd, addr) }
}
/* -------- mpu6050启动守护 --------
* TODO(20260520-qing): 放进类中
* --------------------------------- */
var param: CPointer<mpu6050_th_param> /* -------- 线程参数必须能够持久保存 -------- */
var pth: Option<Pth> = None /* -------- 线程对象 -------- */
public func startDaemon() { /* -------- 启动线程 -------- */
pth = Pth(mpu6050_main, CPointer<Unit>(param))
}
/* -------- 停止线程 -------- */
public func stopDaemon() {
if (let Some(P) <- pth) {
P.close() }
}
/* -------- 等待线程终止 -------- */
public func waitDaemon() {
if (let Some(P) <- pth) {
var ptr: CPointer<Unit> = CPointer<Unit>()
P.join(CPointer<CPointer<Unit>>(CPointer<CPointer<Unit>>(ptr))) }
}
}
+69
View File
@@ -0,0 +1,69 @@
package bot4cj.hd
import pthread4cj.*
/* pca9685舵机控制版 - bot4cj
* TODO(20260515-qing): 由于对统一接口的需求,设计基于回调的虚拟化硬件
* TODO(20260516-qing): 获取pca9685的中文或者英文参考资料
* NOTE(20260618-qing): 该代码已经被Lua脚本所取代,留着备用说不定以后会删除。 */
public class PCA9685 {
private static const ADDR_0: UInt8 = 0x40 /* PCA9685的i2c地址 */
private let i2c_fd: Int32
private let addr: UInt8
public init( i2c_fd: Int32,
addr!: UInt8 = ADDR_0) { /* PCA9685硬件地址 */
this.i2c_fd = i2c_fd
this.addr = addr
/* -------- 初始化PCA9685 --------
* FIXME(20260517-qing): 在旧版机器人代码移除以后,移除这一段 */
}
/* -------- 设置PWM频率为50Hz(舵机标准周期20ms0) --------
* TODO(20260516-qing): 不将数值写死 */
public func setup() {
unsafe {
I2cLock_lock(i2c_fd)
setI2cAddr(i2c_fd, addr);
writeI2cReg(i2c_fd, 0x00, 0x20); /* 开启AI(地址自增)和关闭SLEEP */
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
/* FIXME: 这一条是不是重复了 */
var freq: Float32 = 50.0;
var prescale_val: Float32 = 25000000.0 / (4096.0 * freq) - 1.0; /* 25MHz主频 */
var prescale: UInt8 = UInt8(prescale_val + 0.5);
writeI2cReg(i2c_fd, 0x00, 0x10); /* 进入SLEEP模式(允许改频率) */
writeI2cReg(i2c_fd, 0xFE, prescale); /* 写入预分频值 */
writeI2cReg(i2c_fd, 0x00, 0x20); /* 退出SLEEP */
I2cLock_unlock(i2c_fd)
}
sleep(5000 * Duration.nanosecond); /* 等待振荡器启动 */
}
/* -------- 设置指定通道的PWM信号(0-4095)-------- */
public func setPWM(channel: UInt8, on: UInt16, off: UInt16) {
var reg: UInt8 = 0x06 + 4 * channel; /* LED0 寄存器起始地址 */
unsafe {
writeI2cReg(i2c_fd, reg, UInt8(on & 0xFF));
writeI2cReg(i2c_fd, reg + 1, UInt8(on >> 8));
writeI2cReg(i2c_fd, reg + 2, UInt8(off & 0xFF));
writeI2cReg(i2c_fd, reg + 3, UInt8(off >> 8));
}
return
}
/* -------- 角度转PWM值(0°~180° → 0.5ms~2.5ms脉宽) -------- */
public func setAngle(channel: UInt8, angle: Float32) {
const min: Float32 = 0.5; /* 0°对应0.5ms */
const max: Float32 = 2.5; /* 180°对应2.5ms */
let width: Float32 = min + (max - min) * (angle / 180.0);
let off_val: UInt16 = UInt16(width * 4096.0 / 20.0); /* 20ms 周期 */
unsafe {
I2cLock_lock(i2c_fd)
setI2cAddr(i2c_fd, this.addr);
this.setPWM(channel, 0, off_val); /* ON=0, OFF=计算值 */
I2cLock_unlock(i2c_fd)
}
}
}
+30
View File
@@ -0,0 +1,30 @@
package bot4cj.hd
import std.math.*
/* -------- 舵机类 --------
* 含最大值、最小值、漂移值三个调整项
* 以及一个设置角度回调函数
*
* FIXME(20260618-qing): 在构造函数中设置角度最大值 */
public class Servo {
private var max: Float32
private var min: Float32
private var offset: Float32
private var callback_setAngle: (Float32) -> Unit
private static let MAX_ANGLE: Float32 = 180.0f32
public init(max!: Float32, min!: Float32, offset!: Float32,
callback_setAngle!: (Float32) -> Unit) {
this.max =max
this.min = min
this.offset = offset
this.callback_setAngle = callback_setAngle
}
public func setAngle(angle: Float32) {
var a = angle
a += offset /* ---- 加上漂移值 ---- */
if (a > abs(max)) { a = abs(max)}
if (a < abs(min)) { a = abs(min)}
if (max < 0.0f32) { a = MAX_ANGLE - a }
callback_setAngle(a)
}
}
+16
View File
@@ -0,0 +1,16 @@
macro package bot4cj.hd.extern
import std.fs.*
import std.io.*
import std.ast.*
public macro ExternHardware(attrTokens: Tokens, inputTokens: Tokens): Tokens {
let expr = LitConstExpr(attrTokens)
let file = File(expr.literal.value, OpenMode.Read)
let bytes = readToEnd(file)
file.close()
let sb = StringBuilder()
sb.appendFromUtf8(bytes)
let code = sb.toString()
return cangjieLex(code)
}
+138
View File
@@ -0,0 +1,138 @@
package bot4cj.hd
import ctype4cj.*
const FBIOGET_VSCREENINFO = 0x4602u64
const FBIOGET_FSCREENINFO = 0x4600u64
const PROT_WRITE: C_Int = 0x2
const MAP_SHARED: C_Int = 0x01
/* --- 定义在I2C.cj里了 ---*/
//foreign func ioctl(fd: C_Int, request: C_ULong, ...): C_Int
/* ---- 内存映射 ---- */
public type off_t = C_LongLong
foreign func mmap(addr: CPointer<Unit>, length: UIntNative, prot: C_Int, flags: C_Int, fd: C_Int, offset: off_t): CPointer<Unit>
/* -------- 打开framebuffer帧缓冲设备 -------- */
@C
public func fb_open(fb_dev: CString): C_Int {
return unsafe { libc_open(fb_dev, O_RDWR) }
}
/* -------- 帧缓冲区固定参数 -------- */
@C
public struct fb_fix_screeninfo {
var id: VArray<C_Char, $16> /* 设备驱动名称字符串,比如"TT Builtin" */
var smem_start: C_ULong = 0 /* 帧缓冲内存(显存)的物理起始地址 */
/* (physical address) */
var smem_len: UInt32 = 0 /* 帧缓冲内存的总长度,单位字节 */
var type_: UInt32 = 0 /* 显存类型(fb存储布局)see FB_TYPE_* */
var type_aux: UInt32 = 0 /* 显存附加类型 */
var visual: UInt32 = 0 /* 色彩显示模式 see FB_VISUAL_* */
var xpanstep: UInt16 = 0 /* zero if no hardware panning */
var ypanstep: UInt16 = 0 /* zero if no hardware panning */
var ywrapstep: UInt16 = 0 /* zero if no hardware ywrap */
var line_length: UInt32 = 0 /* 显示屏上一行像素数据所占用的字节数 */
var mmio_start: C_ULong = 0 /* 内存映射I/O的物理起始地址 */
/* (physical address) */
var mmio_len: UInt32 = 0 /* 内存映射的区域长度,单位字节 */
var accel: UInt32 = 0 /* 硬件加速类型 */
/* specific chip/card we have */
var capabilities: UInt16 = 0 /* see FB_CAP_* */
var reserved: VArray<UInt16, $2> /* 保留字段,用于未来扩展,确保兼容 */
public init() {
id = VArray<C_Char, $16> (repeat: 0)
reserved = VArray<UInt16, $2> (repeat: 0)
}
}
/* 颜色字段偏移量的解释:所有偏移量均从右侧开始计算,
* 位于一个“像素”值内部,该像素值的宽度正好等于 'bits_per_pixel'
* (也就是说:你可以将偏移量作为 << 的右操作数使用)。
* 之后的像素就是一个比特流,会原封不动地写入显存。
*
* 对于伪彩色:所有颜色分量的偏移量和长度应该相同。
* 偏移量指定了调色板索引的最低有效位在像素值中的位置。
* 长度表示可用的调色板条目数量(即条目数 = 1 << 长度)。
*/
@C
public struct fb_bitfield {
var offset: UInt32 = 0 /* 位域的起始位置(从右侧算起)*/
var length: UInt32 = 0 /* 位域的长度(比特数) */
var msb_right: UInt32 = 0 /* != 0 : 最高有效位在右侧 */
/* (即低位在右) */
}
/* -------- 帧缓冲区可变参数 -------- */
@C
public struct fb_var_screeninfo {
var xres: UInt32 = 0 /* visible resolution */
var yres: UInt32 = 0
var xres_virtual: UInt32 = 0 /* virtual resolution */
var yres_virtual: UInt32 = 0
var xoffset: UInt32 = 0 /* offset from virtual to visible */
var yoffset: UInt32 = 0 /* resolution */
var bits_per_pixel: UInt32 = 0 /* guess what */
var grayscale: UInt32 = 0 /* 0 = color, 1 = grayscale, */
/* >1 = FOURCC */
var red: fb_bitfield /* bitfield in fb mem if true color, */
var green: fb_bitfield /* else only length is significant */
var blue: fb_bitfield
var transp: fb_bitfield /* transparency */
var nonstd: UInt32 = 0 /* != 0 Non standard pixel format */
var activate: UInt32 = 0 /* see FB_ACTIVATE_* */
var height: UInt32 = 0 /* height of picture in mm */
var width: UInt32 = 0 /* width of picture in mm */
var accel_flags: UInt32 = 0 /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
var pixclock: UInt32 = 0 /* pixel clock in ps (pico seconds) */
var left_margin: UInt32 = 0 /* time from sync to picture */
var right_margin: UInt32 = 0 /* time from picture to sync */
var upper_margin: UInt32 = 0 /* time from sync to picture */
var lower_margin: UInt32 = 0
var hsync_len: UInt32 = 0 /* length of horizontal sync */
var vsync_len: UInt32 = 0 /* length of vertical sync */
var sync: UInt32 =0 /* see FB_SYNC_* */
var vmode: UInt32 = 0 /* see FB_VMODE_* */
var rotate: UInt32 = 0 /* angle we rotate counter clockwise */
var colorspace: UInt32 = 0 /* colorspace for FOURCC-based modes */
var reserved: VArray<UInt32, $4>/* Reserved for future compatibility */
public init() {
red = fb_bitfield()
green = fb_bitfield()
blue = fb_bitfield()
transp = fb_bitfield()
reserved = VArray<UInt32, $4> (repeat: 0)
}
}
/* ---- 获取帧缓冲区可变参数 ---- */
@C
public func fb_get_var_info(fd: C_Int) {
var info = fb_var_screeninfo()
unsafe { ioctl(fd, FBIOGET_VSCREENINFO, inout info) }
return info
}
/* ---- 获取帧缓冲区固定参数 ---- */
@C
public func fb_get_fix_info(fd: C_Int) {
var info = fb_fix_screeninfo()
unsafe { ioctl(fd, FBIOGET_FSCREENINFO, inout info) }
return info
}
/* ---- 内存映射 ---- */
@C
public func fb_mmap(addr: CPointer<Unit>, length: UIntNative, fd: C_Int, offset: off_t): CPointer<Unit>
{
let buf = unsafe { mmap(addr, length, PROT_WRITE, MAP_SHARED, fd, offset) }
return buf
}
+22
View File
@@ -0,0 +1,22 @@
package bot4cj.hd
import std.time.*
//foreign func play_frequency(frequency: Int32, duration_ms: Int32, sample_rate: Int32): Bool
//foreign func play_frequency(frequency: Float32, duration_ms: Int32, volume: Float64, channels: Int32, sample_rate: Int32): Bool
/* -------- 扬声器类 --------
* TODO: 一次打开设备,一段时间后再关闭 */
public class Spk {
private var channels: Int32
private var sample_rate: Int32
public init(channels!: Int32, sample_rate!: Int32) {
this.channels = channels
this.sample_rate = sample_rate
}
/* -------- 控制扬声器发声 -------- */
public func play(frequency: Float32, volume: Float64, durations_ms: Int32): Bool {
//unsafe { play_frequency(frequency, durations_ms, volume, channels, sample_rate) }
sleep(Duration.millisecond * Int64(durations_ms))
return true
}
}
+177
View File
@@ -0,0 +1,177 @@
package bot4cj
import std.io.*
import std.fs.*
import std.env.*
import std.time.*
import std.argopt.*
import std.convert.*
import std.collection.*
import stdx.encoding.json.*
import bot4cj.hd.*
import bot4cj.math.*
import ctype4cj.*
/* ---- 神经网络输出路径 ---- */
var NNS_OUT: Option<String> = None
main(args: Array<String>): Int64 {
/* ---- 解析命令行参数 ---- */
let specs = [
ArgumentSpec.Long("help", ArgumentMode.NoValue), /* ---- 拉起帮助菜单 ---- */
ArgumentSpec.Short(r'i', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输入 ---- */
ArgumentSpec.Short(r'o', ArgumentMode.RequiredValue), /* ---- 神经网络模型 - 输出 ---- */
ArgumentSpec.Long("test", ArgumentMode.RequiredValue) /* ---- 运行一个测试分支 - 需要输入测试类型 ---- */
]
/* ---- 执行解析 ---- */
let parsed = parseArguments(args, specs)
let options = parsed.options
//---------------------------
/* 读取设置项 */
/*---------------------------*/
/*---------------------------*/
/* 初始化神经网络 (init nn)
* TODO: 可以去掉内部初始化参数,使用矩阵式模板进行初始化 */
//var nns: ArrayList<Nn> =
//if (options.keys().contains("i")) {
// let filename = options['i']
// load_nns(filename) }
//else {
// let nn_in = ArrayList<Nn>()
// nn_in.add(Nn(41, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
// nn_in.add(Nn(80, 80, ActivationFuncs.Leaky_ReLU, 0.2f32))
// nn_in.add(Nn(80, 40, ActivationFuncs.Leaky_ReLU, 0.2f32))
// nn_in.add(Nn(40, 17, ActivationFuncs.Sigmoid, 0.2f32)) // 16个舵机加1个时延
// nn_in }
/* ---- 使用-o选项进行输出路径的选择 ---- */
if (options.contains("o")) {
NNS_OUT = Option<String>.Some(options['o'])
}
/*---------------------------*/
/*---------------------------*/
/* 分支选择 */
/* -------- 拉起帮助手册
* -------- */
if (options.contains("help")) {
print_help()
return 0
}
/* -------- 启动一个测试分支
* -------- */
else if (options.contains("test")) {
run_test(options["test"])
return 0
}
/* -------- 缺省行为:从标准输入获取动作脚本
* --------
* -------- TODO(20260519-qing): 进行脚本引入(相对路径也可,从**环境变量**的路径中搜寻也可) */
else {
let consoleReader = getStdIn()
let robot = RobotStruct(consoleReader)
try {
robot.active()
robot.run()
}
finally {
robot.deactive()
}
/* ---- NOTE(20260523-qing): 储存微调网络模型
* ---- */
// let consoleReader = getStdIn()
// run_action_drl(consoleReader, nns)
// if (options.contains("o")) {
// save_nns(nns, options['o']) }
}
return 0
}
/* -------- 打印帮助手册
* -------- */
func print_help()
{
println("这是一个机器人动作脚本解析器。")
println("使用管道输入动作脚本,示例:")
println("cat std.mt | ./bot")
println()
println("--i2c=/path/to/device\t选择i2c设备")
println()
println("-i 选择输入的模型的路径")
println("-o 设置输出的模型的路径")
}
/* -------- 集成多种常规测试、临时测试
* -------- */
func run_test(key: String) {
/* ----------- 神经网络与门测试
* -----------
* ----------- INFO: 我们使用随机生成的动态数据集,这样会导致一些后果 */
if (key == "nn") {
let nn_t = ArrayList<Nn1>()
nn_t.add(Nn1(2, 4, 0.05f32, Af.Leaky_ReLU(FP32(0.01))))
nn_t.add(Nn1(4, 1, 0.05f32, Af.Sigmoid))
let batch = 1
let num =2000
let prt_num = 100
for (i in 0..(num*batch)) {
let m = Matrix1(1, 2)
let t = Matrix1(1, 1)
prepare_andDoor(m, t)
let l = train1(nn_t, m, t, 1.0f32) // 这个1.0是学习率的折扣,而非学习率本身
if (i % prt_num == prt_num-1) {
print("${i}\t:")
print("${l}")
println()
}
}
}
else if (key == "cnn") {
testCNN()
}
else if (key == "cnn1") {
testGeometryCNN()
}
/* -------- 测试解析矩阵,并且输出
* -------- */
else if (key == "matrix_o") {
let file = File("ss/test.mt", OpenMode.Read)
let robot = RobotStruct(file)
file.close()
let outf = File("ss/output.mt", OpenMode.Write)
robot.save(outf)
outf.close()
}
/* -------- 解析测试输出的矩阵
* -------- */
else if (key == "matrix_2") {
let file = File("ss/output.mt", OpenMode.Read)
let robot = RobotStruct(file)
file.close()
robot.active()
robot.run()
robot.deactive()
let outf = File("ss/output_1.mt", OpenMode.Write)
robot.save(outf)
outf.close()
}
}
+204
View File
@@ -0,0 +1,204 @@
// Adam.cj - Adam 优化器实现(添加全连接层支持)
package bot4cj.math
import std.math.*
import std.collection.*
public class Adam {
let learningRate: FP32
let beta1: FP32
let beta2: FP32
let epsilon: FP32
var m: HashMap<String, Matrix1>
var v: HashMap<String, Matrix1>
var t: Int64
// 完整参数构造函数
public init(
lr: FP32,
beta1: FP32,
beta2: FP32,
epsilon: FP32
) {
learningRate = lr
this.beta1 = beta1
this.beta2 = beta2
this.epsilon = epsilon
m = HashMap<String, Matrix1>()
v = HashMap<String, Matrix1>()
t = 0
}
// 默认参数构造函数
public init() {
learningRate = FP32(0.001)
beta1 = FP32(0.9)
beta2 = FP32(0.999)
epsilon = FP32(0.00000001)
m = HashMap<String, Matrix1>()
v = HashMap<String, Matrix1>()
t = 0
}
// 仅自定义学习率的构造函数
public init(lr: FP32) {
learningRate = lr
beta1 = FP32(0.9)
beta2 = FP32(0.999)
epsilon = FP32(0.00000001)
m = HashMap<String, Matrix1>()
v = HashMap<String, Matrix1>()
t = 0
}
/**
* 计算幂运算的辅助函数
*/
private func power(base: FP32, exp: Int64): FP32 {
var result = FP32(1.0)
for (i in 0..exp) {
result = result * base
}
return result
}
/**
* 计算平方根的辅助函数 - 使用牛顿迭代法
*/
private func sqrt(val: FP32): FP32 {
let x = val.getValue()
if (x <= 0.0) {
return FP32(0.0)
}
var guess = x
for (i in 0..10) {
guess = (guess + x / guess) / 2.0
}
return FP32(guess)
}
/**
* 更新矩阵参数(通用方法)
* @param param 要更新的参数矩阵
* @param grad 梯度矩阵
* @param key 参数在HashMap中的键名
*/
private func updateMatrix(
param: Matrix1,
grad: Matrix1,
key: String
): Unit {
// 初始化动量
let hasState = m.contains(key)
if (!hasState) {
let rows = param.get_row()
let cols = param.get_col()
m.add(key, Matrix1(rows, cols))
v.add(key, Matrix1(rows, cols))
}
let mMat = m.get(key).getOrThrow()
let vMat = v.get(key).getOrThrow()
let rows = param.get_row()
let cols = param.get_col()
for (r in 0..rows) {
for (c in 0..cols) {
let g = grad.get(r, c).getOrThrow()
// 更新一阶矩
let oldM = mMat.get(r, c).getOrThrow()
let newM = beta1 * oldM + (FP32(1.0) - beta1) * g
mMat.set(r, c, newM)
// 更新二阶矩
let oldV = vMat.get(r, c).getOrThrow()
let newV = beta2 * oldV + (FP32(1.0) - beta2) * g * g
vMat.set(r, c, newV)
// 偏差修正
let one = FP32(1.0)
let beta1Pow = power(beta1, t)
let beta2Pow = power(beta2, t)
let mHat = newM / (one - beta1Pow)
let vHat = newV / (one - beta2Pow)
// 更新参数
let oldParam = param.get(r, c).getOrThrow()
let update = learningRate * mHat / (sqrt(vHat) + epsilon)
param.set(r, c, oldParam - update)
}
}
}
/**
* 更新卷积层参数
*/
public func updateConvLayer(layer: ConvLayer): Unit {
t += 1
// 更新卷积核
var kGrad = match (layer.kernelsGrad) {
case Some(val) => val
case _ => throw Exception("No kernel gradients")
}
for (oc in 0..layer.nOutputChannels) {
let key = "conv_kernel_" + oc.toString()
updateMatrix(layer.kernels[oc], kGrad[oc], key)
}
// 更新偏置
var bGrad = match (layer.biasGrad) {
case Some(val) => val
case _ => throw Exception("No bias gradient")
}
let biasKey = "conv_bias"
updateMatrix(layer.biases, bGrad, biasKey)
}
/**
* 更新全连接层参数(Nn1)
*/
public func updateFCLayer(layer: Nn1): Unit {
t += 1
// 更新权重
var wGrad = match (layer.weights_grad) {
case Some(val) => val
case _ => throw Exception("No weights gradient")
}
let weightKey = "fc_weights"
updateMatrix(layer.weights, wGrad, weightKey)
// 更新偏置
var bGrad = match (layer.bias_grad) {
case Some(val) => val
case _ => throw Exception("No bias gradient")
}
let biasKey = "fc_bias"
updateMatrix(layer.bias, bGrad, biasKey)
}
/**
* 重置优化器状态
*/
public func reset(): Unit {
m.clear()
v.clear()
t = 0
}
/**
* 获取当前迭代次数
*/
public func getStep(): Int64 {
t
}
}
+273
View File
@@ -0,0 +1,273 @@
package bot4cj.math
import std.math.*
/* -------- Activation Functions 激活函数族 --------
* TODO(20260516-qing): 增加序列化、反序列化工厂函数 */
public enum Af {
None | Sigmoid | ReLU | Leaky_ReLU(FP32) | Tanh | Softmax
/* -------- 执行激活函数 -------- */
public func forward(x: Matrix1) {
match(this) {
case None => none(x)
case Sigmoid => sigmoid(x)
case ReLU => relu(x)
case Leaky_ReLU(alpha) => leaky_relu(x, alpha)
case Tanh => tanh(x)
case Softmax => softmax(x)
}
}
/* -------- 激活函数的导数 -------- */
public func backward(y: Matrix1) {
match(this) {
case None => none_dot(y)
case Sigmoid => sigmoid_dot(y)
case ReLU => relu_dot(y)
case Leaky_ReLU(alpha) => leaky_relu_dot(y, alpha)
case Tanh => tanh_dot(y)
case Softmax => softmax_jacobian(y)
}
}
/* -------- 序列化与反序列化 -------- */
public func get_type(): String {
return match(this) {
case None => "None"
case Sigmoid => "Sigmoid"
case ReLU => "ReLU"
case Leaky_ReLU(alpha) => "Leaky_ReLU"
case Tanh => "Tanh"
case Softmax => "Softmax"
}
}
/* */
public func get_value(): FP32 {
return match(this) {
case None => FP32(0.0f32)
case Sigmoid => FP32(0.0f32)
case ReLU => FP32(0.0f32)
case Leaky_ReLU(alpha) => alpha
case Tanh => FP32(0.0f32)
case Softmax => FP32(0.0f32)
}
}
/* -------- 空的激活函数 -------- */
private static func none(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
N.set(r, c, FP32(x1)) }
}
return N
}
/* -------- 空激活函数的导数 -------- */
private static func none_dot(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
N.set(r, c, FP32(1.0f32)) }
}
return N
}
/* -------- S型生长曲线 -------- */
private static func sigmoid(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
N.set(r, c, FP32(x2)) }
}
return N
}
/* -------- S型生长曲线的导数 -------- */
private static func sigmoid_dot(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 = 1.0f32 / (1.0f32 + exp(-x1))
let x3 = x2 * (1.0f32 - x2)
N.set(r, c, FP32(x3)) }
}
return N
}
/* -------- 线性整流单元 -------- */
private static func relu(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 =if (x1 > 0.0f32) {x1} else{0.0f32}
N.set(r, c, FP32(x2))
}
}
return N
}
/* -------- 线性整流单元的导数 -------- */
private static func relu_dot(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 =if (x1 > 0.0f32) {1.0f32} else{0.0f32}
N.set(r, c, FP32(x2))
}
}
return N
}
/* -------- 带泄露的线性整流单元 -------- */
private static func leaky_relu(M: Matrix1, alpha: FP32) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 =if (x1 > 0.0f32) {x1} else{x1 * alpha.getValue()}
N.set(r, c, FP32(x2))
}
}
return N
}
/* -------- 带泄露的整流单元导数 -------- */
private static func leaky_relu_dot(M: Matrix1, alpha: FP32) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 =if (x1 > 0.0f32) { 1.0f32 } else{alpha.getValue()}
N.set(r, c, FP32(x2)) }
}
return N
}
/* -------- 双曲正切函数 --------*/
private static func tanh(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 = tanh(x1)
N.set(r, c, FP32(x2)) }
}
return N
}
/* -------- 双曲正切函数的导数 -------- */
private static func tanh_dot(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
for (r in 0..row) {
for (c in 0..col) {
let y = M.get(r, c).getOrThrow()
let y_val= y.getValue() /*---- NOTE(20260525-qing): 修复命名(x => y) ----*/
let rea = 1.0f32 - y_val * y_val
N.set(r, c, FP32(rea)) }
}
return N
}
/* -------- 归一化指数函数 -------- */
private static func softmax(M: Matrix1) {
let row = M.get_row()
let col = M.get_col()
let N = Matrix1(row, col)
var max_val = 0.0f32
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
if (x1 > max_val) {
max_val = x1 }
}
}
var sum = 0.0f32
for (r in 0..row) {
for (c in 0..col) {
let x = M.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 = exp(x1 - max_val)
N.set(r, c, FP32(x2))
sum += x2
}
}
for (r in 0..row) {
for (c in 0..col) {
let x = N.get(r, c).getOrThrow()
let x1= x.getValue()
let x2 = x1 / sum
N.set(r, c, FP32(x2))
}
}
return N
}
/* -------- 计算Softmax的雅可比矩阵 --------
* 输入:已计算好的softmax输出(概率分布)
* 输出:雅可比矩阵 J[i][j] = ∂y_i/∂x_j */
private func softmax_jacobian(softmax_output: Matrix1) {
let row = softmax_output.get_row()
let col = softmax_output.get_col()
let n = row * col
var jacobian = Matrix1(n, n)
for (i in 0..n) {
for (j in 0..n) {
let row_i = i / col /* 需要将一维索引转换为二维索引来访问Matrix1 */
let col_i = i % col
let row_j = j / col
let col_j = j % col
let val_i = softmax_output.get(row_i, col_i).getOrThrow().getValue()
let val_j = softmax_output.get(row_j, col_j).getOrThrow().getValue()
if (i == j) { /* 对角线元素: y_i * (1 - y_i) */
jacobian.set(i, j, FP32(val_i * (1.0f32 - val_i))) }
else { /* 非对角线元素: -y_i * y_j */
jacobian.set(i, j, FP32(-val_i * val_j)) }
}
}
return jacobian
}
}
+37
View File
@@ -0,0 +1,37 @@
package bot4cj.math
import std.random.*
import std.time.*
import std.math.*
/* -------- 神经网络使用的权重矩阵初始化类 --------
* TODO(20260517-qing): 将该函数改为枚举类,统一一个接口,根据模式匹配执行初始化
* - 这样在矩阵式中可以定义初始化方式 */
public class Ini {
/* -------- xavier正态初始化 -------- */
public static func xavierNormalInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
let seed = DateTime.now().nanosecond
let random = Random(UInt64(seed))
let sigma = sqrt(2.0f32 / Float32(fan_in + fan_out)) /* 计算标准差 */
for (r in 0..fan_in) {
for (c in 0..fan_out) {
let v = random.nextGaussianFloat32(mean: 0.0f32, sigma:sigma)
target.set(r, c, FP32(v))
}
}
}
/* -------- Glorot均匀初始化 -------- */
public static func xavierUniformInit(target: Matrix1, fan_in: Int64, fan_out: Int64) {
let limit = sqrt(6.0f32 / Float32(fan_in + fan_out))
let seed = DateTime.now().nanosecond
let random = Random(UInt64(seed))
for (r in 0..fan_in) {
for (c in 0..fan_out) {
let v = (random.nextFloat32() * 2.0f32 - 1.0f32) * limit /* 均匀分布 [-limit, limit] */
target.set(r, c, FP32(v)) }
}
}
}
+535
View File
@@ -0,0 +1,535 @@
package bot4cj.math
import std.fs.*
import std.io.*
import std.convert.*
//foreign func createMatrix(row: UInt64, col: UInt64): CPointer<Unit>
//foreign func freeMatrix(M: CPointer<Unit>): Unit
//foreign func witchMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): CPointer<Float32>
//foreign func getMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64): Float32
//foreign func setMatrix(M: CPointer<Unit>, row: UInt64, col: UInt64, v: Float32): Unit
//foreign func getrowMatrix(M: CPointer<Unit>): UInt64
//foreign func getcolMatrix(M: CPointer<Unit>): UInt64
//foreign func sameShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
//foreign func dotShapeMatrix(M: CPointer<Unit>, S: CPointer<Unit>): Bool
//foreign func plusMatrix(M: CPointer<Unit>, S:CPointer<Unit>): CPointer<Unit>
//foreign func plusBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
//foreign func subMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
//foreign func subBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
//foreign func multiMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
//foreign func multiBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
//foreign func divMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
//foreign func divBroadcastMatrix(M: CPointer<Unit>, T: Float32): CPointer<Unit>
//foreign func dotMatrix(M: CPointer<Unit>, S: CPointer<Unit>): CPointer<Unit>
//foreign func transposeMatrix(M: CPointer<Unit>): CPointer<Unit>
//foreign func xavierNormalInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
//foreign func xavierUniformInit(fan_in: UInt64, fan_out: UInt64): CPointer<Unit>
/* -------- 32位浮点数 -------- */
public struct FP32 {
private var val: Float32
public init(t: Float32) { /* 以Float32初始化对象 */
val = t }
public func getValue() { /* 获取值 */
return val }
/* -------- 运算符重载 -------- */
//public operator func =(t: Float32) { /*-------- this can not --------*/
// val = t }
public operator func +(t: Float32) {
return FP32(val + t)}
public operator func +(S: FP32) {
return this + S.getValue() }
public operator func -(t: Float32) {
return FP32(val - t) }
public operator func -(S: FP32) {
return this - S.getValue() }
public operator func *(t: Float32) {
return FP32(val * t) }
public operator func *(S: FP32) {
return this * S.getValue() }
public operator func /(t: Float32) {
return FP32(val / t) }
public operator func /(S: FP32) {
return this / S.getValue() }
}
/*
* 新版的矩阵对象,仓颉原生实现
* TODO(20260517-qing): 设计原生的多维数组类
*
___col___
/ \
+---------+ \
| + + + + | | row - 第一维度
| + + + + | |
+---------+ /
* TODO(20260523-qing): 以某种方式统一矩阵式的字符串矩阵和这里的数值矩阵
*/
public class Matrix1 {
private var row: Int64 /* ---- 行数 ---- */
private var col: Int64 /* ---- 列数 ---- */
private var arr: Array<FP32> /* ---- 数据 ---- */
/* -------- 构造器,默认值为0 -------- */
public Matrix1(row: Int64, col: Int64) {
this.row = row
this.col = col
this.arr = Array<FP32>(row * col, {_ => FP32(0.0f32)})
}
/* --------- 这几个方法是访问器 -------- */
/* ------------------------------------- */
/* -------- TODO(20260524-qing): 使用prop成员 -------- */
public func get(r: Int64, c: Int64) {
return arr.get(r * this.col + c)
}
public func set(r: Int64, c: Int64, v: FP32) {
arr[r * this.col + c] = v
}
public func get_row() { return row }
public func get_col() { return col }
/* -------- 这两个函数是比较器 -------- */
/* ------------------------------------ */
public func has_same_shape(other: Matrix1) {
let a = this.get_row() == other.get_row()
let b = this.get_col() == other.get_col()
return a && b
}
public func has_dot_shape(other: Matrix1) {
return this.get_col() == other.get_row()
}
/* -------- 这些是运算符重载 -------- */
/* ---------------------------------- */
public operator func +(a: FP32) { /* ---- 矩阵广播加法 ---- */
let r =get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = this.get(ir, ic).getOrThrow()
M.set(ir, ic, v + a)
}
}
return M
}
public operator func +(S: Matrix1) { /* ---- 矩阵加法 ---- */
if (!has_same_shape(S)) {
throw IllegalArgumentException("Matrixes doesn't match")
}
let r =get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = this.get(ir, ic).getOrThrow()
let vs = S.get(ir, ic).getOrThrow()
M.set(ir, ic, v + vs)
}
}
return M
}
public operator func -(a: FP32) { /* ---- 矩阵广播减法 ----*/
let r = get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c){
let v = this.get(ir, ic).getOrThrow()
M.set(ir, ic, v - a)
}
}
return M
}
public operator func -(S: Matrix1) { /* ---- 矩阵减法 ---- */
if (!has_same_shape(S)) {
throw IllegalArgumentException("Matrixes doesn't match")
}
let r =get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = this.get(ir, ic).getOrThrow()
let vs = S.get(ir, ic).getOrThrow()
M.set(ir, ic, v - vs)
}
}
return M
}
public operator func *(a: FP32) { /* ---- 矩阵广播乘法 ---- */
let r = get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c){
let v = this.get(ir, ic).getOrThrow()
M.set(ir, ic, v * a)
}
}
return M
}
public operator func *(S: Matrix1) { /* ---- 逐元素相乘 ---- */
if (!has_same_shape(S)) {
throw IllegalArgumentException("Matrixes doesn't match")
}
let r =get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = this.get(ir, ic).getOrThrow()
let vs = S.get(ir, ic).getOrThrow()
M.set(ir, ic, v * vs)
}
}
return M
}
public operator func /(a: FP32) { /* ---- 矩阵广播除法 ---- */
let r = get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c){
let v = this.get(ir, ic).getOrThrow()
M.set(ir, ic, v / a)
}
}
return M
}
public operator func /(S: Matrix1) { /* ---- 逐元素除法 ---- */
if (!has_same_shape(S)) {
throw IllegalArgumentException("Matrixes doesn't match")
}
let r =get_row()
let c = get_col()
let M = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = this.get(ir, ic).getOrThrow()
let vs = S.get(ir, ic).getOrThrow()
M.set(ir, ic, v / vs)
}
}
return M
}
/* ------------------------------ */
/* -------- 矩阵点积乘法 -------- */
public func dot(S: Matrix1) {
if (!has_dot_shape(S)) {
throw IllegalArgumentException("Matrixes doesn't match")
}
let r =get_row()
let c = S.get_col()
let M = Matrix1(r, c)
for (ir in 0..get_row()) {
for (ic in 0..S.get_col()) {
var sum = FP32(0.0f32)
for (ik in 0..get_col()) {
let a = get(ir, ik).getOrThrow()
let b = S.get(ik, ic).getOrThrow()
sum += a * b
}
M.set(ir, ic, sum)
}
}
return M
}
/* -------- 克隆一个一模一样的矩阵 -------- */
public func clone() {
let r = get_row()
let c = get_col()
let N = Matrix1(r, c)
for (ir in 0..r) {
for (ic in 0..c) {
let v = get(ir, ic).getOrThrow()
N.set(ir, ic, v)
}
}
return N
}
/* -------- 矩阵转置,返回转置后的矩阵 -------- */
public func T() {
let r = get_row()
let c = get_col()
let N = Matrix1(c, r)
for (ir in 0..r) {
for (ic in 0..c) {
let v = get(ir, ic).getOrThrow()
N.set(ic, ir, v)
}
}
return N
}
}
/* -------- 旧版的矩阵类,依赖c++编译的共享动态库 --------
* TODO: 将矩阵元数据存储在仓颉部分(done at 202605xx ) */
//public class Matrix {
//
// /* -------- 主初始化器,初始化并获取资源 -------- */
// public init(row: UInt64, col: UInt64) {
// unsafe { M = createMatrix(row, col) }
// }
//
// /* -------- 初始化器,利用已经存在的元数据进行构建 -------- */
// public Matrix(m: CPointer<Unit>) { M = m }
//
// /* -------- Xavier正态分布初始化 -------- */
// public static func initWithXavierNormal(fan_in: UInt64, fan_out: UInt64) {
// unsafe {
// let m = xavierNormalInit(fan_in, fan_out);
// return Matrix(m)
// }
// }
//
// /* -------- Glorot均匀初始化 -------- */
// public static func initWithXavierUniform(fan_in: UInt64, fan_out: UInt64) {
// unsafe {
// let m = xavierUniformInit(fan_in, fan_out);
// return Matrix(m)
// }
// }
//
// /* ======== 在析构函数中释放资源 ======== */
// ~init() {
// unsafe { freeMatrix(M) }
// }
//
// /* -------- 取下标,由于不能返回值类型的引用被弃用 -------- */
// //public operator func [](r: UInt64, c: UInt64): CPointer<Float32> {
// // unsafe { return witchMatrix(M, r, c) }
// //}
//
// /* -------- 获得某个矩阵单元的值 -------- */
// public func get(r: UInt64, c: UInt64): Float32 {
// unsafe { return getMatrix(M, r, c) }
// }
//
// /* -------- 设置某个矩阵单元的值 --------*/
// public func set(r: UInt64, c: UInt64, v: Float32) {
// unsafe { setMatrix(M, r, c, v) }
// }
//
// /* ------- 两个矩阵形状是否相同 --------
// * FIXME: 这个函数所重载的运算符不贴切 */
// public operator func ==(s: Matrix): Bool {
// unsafe { return sameShapeMatrix(M, s.getData()) }
// }
//
// /* -------- 获得行数 -------- */
// public func getRow(): UInt64 {
// unsafe { return getrowMatrix(M) }
// }
//
// /* -------- 获取列数 --------*/
// public func getCol(): UInt64 {
// unsafe { return getcolMatrix(M) }
// }
//
// /* -------- 获取指向元数据的C风格指针 -------- */
// public func getData(): CPointer<Unit> { return M; }
//
// /* -------- 是否两个矩阵具有点积和的形状 -------- */
// public func hasDotShapeWith(s: Matrix): Bool {
// unsafe { return dotShapeMatrix(M, s.getData()) }
// }
//
// /* -------- 逐元素算数运算 -------- */
// /* -------------------------------- */
// public operator func +(s: Matrix): Matrix {
// unsafe {
// if (!sameShapeMatrix(M, s.getData())) {
// println('------------')
// print_shape()
// s.print_shape()
// println('------------')
// throw IllegalArgumentException("not same shape")
// }
// let r = plusMatrix(M, s.getData())
// return Matrix(r)
// }
// }
//
// public operator func +(t: Float32): Matrix {
// unsafe {
// let r = plusBroadcastMatrix(M, t)
// return Matrix(r);
// }
// }
//
// public operator func -(s: Matrix): Matrix {
// unsafe {
// if (!(this == s)) {
// println('------------')
// print_shape()
// s.print_shape()
// println('------------')
// throw IllegalArgumentException("not same shape")
// }
// let r = subMatrix(M, s.getData())
// return Matrix(r)
// }
// }
//
// public operator func -(t: Float32): Matrix {
// unsafe {
// let r = subBroadcastMatrix(M, t)
// return Matrix(r);
// }
// }
//
// public operator func *(s: Matrix): Matrix {
// unsafe {
// if (!(this == s)) {
// println('------------')
// print_shape()
// s.print_shape()
// println('------------')
// throw IllegalArgumentException("not same shape")
// }
// let r = multiMatrix(M, s.getData())
// return Matrix(r)
// }
// }
//
// public operator func *(t: Float32): Matrix {
// unsafe {
// let r = multiBroadcastMatrix(M, t)
// return Matrix(r)
// }
// }
//
// public operator func /(s: Matrix): Matrix {
// unsafe {
// if (!(this == s)) {
// println('------------')
// print_shape()
// s.print_shape()
// println('------------')
// throw IllegalArgumentException("not same shape")
// }
// let r = divMatrix(M, s.getData())
// return Matrix(r)
// }
// }
//
// public operator func /(t: Float32): Matrix {
// unsafe {
// let r = divBroadcastMatrix(M, t)
// return Matrix(r)
// }
// }
//
// /* -------------------------------- */
// /* -------------------------------- */
//
//
// /* -------- 矩阵点积和算法 -------- */
// public func dot (s: Matrix): Matrix {
// unsafe {
// if (!hasDotShapeWith(s)) {
// println('------------')
// print_shape()
// s.print_shape()
// println('------------')
// throw IllegalArgumentException("not dot shape")
// }
// let r = dotMatrix(M, s.getData())
// return Matrix(r)
// }
// }
//
// /* -------- 矩阵转置 -------- */
// public func T(): Matrix {
// unsafe {
// let r = transposeMatrix(M)
// return Matrix(r)
// }
// }
//
// /* -------- 打印矩阵 -------- */
// public func print(): Unit {
// unsafe {
// for (i in 0..getRow()) {
// for (j in 0..getCol()) {
// let a = this.get(i, j)
// print(a)
// print(" ")
// }
// }
// }
// println()
// }
//
// /* -------- 打印形状 -------- */
// public func print_shape() {
// println("${getRow()}, ${getCol()}")
// }
//
// /* -------- 保存矩阵 -------- */
// public func save(file: File) {
// unsafe {
// for (i in 0..getRow()) {
// for (j in 0..getCol()) {
// file.write(this.get(i, j).toString().toArray())
// if (j < getCol()-1) {
// file.write(" ".toArray()) }
// else { file.write("\n".toArray()) }
// }
// }
// }
// }
//
// /* -------- 静态方法,加载单独项 -------- */
// private static func loadItem(sr: StringReader<InputStream>) {
// let s = sr.readUntil({r => r==r' ' || r ==r'\n'})
// return Float32.parse(s.getOrThrow())
// }
//
// /* -------- 静态方法,加载矩阵 -------- */
// public static func load(sr: StringReader<InputStream>, row: UInt64, col: UInt64): Matrix {
// let m = Matrix(row, col)
// unsafe {
// for (i in 0..m.getRow()) {
// for (j in 0..m.getCol()) {
// let f = loadItem(sr)
// m.set(i, j, f)
// }
// }
// m
// }
// }
//
// /* -------- 矩阵的C风格指针 -------- */
// private var M: CPointer<Unit>
//}
+56
View File
@@ -0,0 +1,56 @@
package bot4cj.math
import std.collection.*
/* -------- 张量 --------
* INFO(20260524-qing): 输入一个维度列表 */
class Tensor<T> {
private let data: Array<T>
private let dims: Array<Int64>
private let size: Int64
public init(dims: Array<Int64>, val: T) {
var size = 1
for (item in dims) { /* ---- 计算总量 ---- */
size *= item
}
this.data = Array<T>(size, {_ => val})
this.dims = dims
this.size = size
}
/* -------- 私有构造函数,用于内部创建张量 -------- */
private init(dims: Array<Int64>, data: Array<T>) {
this.dims = dims
this.data = data
var size = 1
for (item in dims) {
size *= item
}
this.size = size
}
/* -------- 获取维度列表(形状) -------- */
public func shape(): Array<Int64> {
return dims
}
/* -------- 逐元素进行某种运算 -------- */
public func oneByOne(S: Tensor<T>, f: (T, T) -> T): Tensor<T> {
let s1 = S.shape()
/* ---- 比较维度数量 ---- */
if (s1 != dims) {
throw IllegalArgumentException("Tensors' shape are not same")
}
/* ---- 比较维度形状 ----*/
for (i in 0..dims.size) {
if (s1[i] != dims[i]) {
throw IllegalArgumentException("Tensors' shape are not same")
}
}
/* ---- 逐个进行计算 ---- */
let res_data = Array<T>(size, {i => f(data[i], S.data[i])})
return Tensor<T>(dims, res_data)
}
}
+379
View File
@@ -0,0 +1,379 @@
// cnn.cj - 修正 forward 中的偏置加法
package bot4cj.math
import std.math.*
import std.random.*
import std.collection.*
public class ConvLayer {
let nInputChannels: Int64
let nOutputChannels: Int64
let kernelHeight: Int64
let kernelWidth: Int64
let stride: Int64
let padding: Int64
let learningRate: FP32
let f: Af
var connectingMatrix: Matrix1
var kernels: Array<Matrix1>
var biases: Matrix1
var prevInput: Option<Matrix1> = Option.None
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
var prevCombination: Option<Matrix1> = Option.None
var prevOutput: Option<Matrix1> = Option.None
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
var kernelsGrad: Option<Array<Matrix1>> = Option.None
var biasGrad: Option<Matrix1> = Option.None
public init(
inCh: Int64,
outCh: Int64,
kernelSize: (Int64, Int64),
stride: Int64,
padding: Int64,
lr: FP32,
activation: Af,
connMatrix: ?Matrix1
) {
nInputChannels = inCh
nOutputChannels = outCh
kernelHeight = kernelSize[0]
kernelWidth = kernelSize[1]
this.stride = stride
this.padding = padding
learningRate = lr
this.f = activation
// 初始化 connectingMatrix
connectingMatrix = match (connMatrix) {
case m: Matrix1 => m
case _ => Matrix1(nInputChannels, nOutputChannels)
}
let isNone = match (connMatrix) {
case _: Matrix1 => false
case _ => true
}
if (isNone) {
for (r in 0..nInputChannels) {
for (c in 0..nOutputChannels) {
connectingMatrix.set(r, c, FP32(1.0))
}
}
}
// 初始化 kernels
kernels = Array(nOutputChannels, { _ => Matrix1(1, 1) })
for (oc in 0..nOutputChannels) {
var nConnections = 0
for (ic in 0..nInputChannels) {
let val = connectingMatrix.get(ic, oc).getOrThrow()
if (val.getValue() > 0.0) {
nConnections += 1
}
}
let kernelLen = nConnections * kernelHeight * kernelWidth
let kernel = Matrix1(1, kernelLen)
Ini.xavierUniformInit(kernel, 1, kernelLen)
kernels[oc] = kernel
}
// 初始化 biases
biases = Matrix1(1, nOutputChannels)
Ini.xavierUniformInit(biases, 1, nOutputChannels)
}
// ==================== 前向传播 ====================
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
prevInput = Option.Some(x)
prevInputShape = Option.Some(inputShape)
let batch = inputShape[0]
let inH = inputShape[1]
let inW = inputShape[2]
let outH = (inH + 2 * padding - kernelHeight) / stride + 1
let outW = (inW + 2 * padding - kernelWidth) / stride + 1
let outSize = batch * outH * outW
var combination = Matrix1(outSize, nOutputChannels)
var output = Matrix1(outSize, nOutputChannels)
for (b in 0..batch) {
for (oh in 0..outH) {
for (ow in 0..outW) {
let outRow = b * outH * outW + oh * outW + ow
for (oc in 0..nOutputChannels) {
var sum = FP32(0.0)
var kernelIdx = 0
for (ic in 0..nInputChannels) {
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
if (connVal.getValue() > 0.0) {
for (kh in 0..kernelHeight) {
for (kw in 0..kernelWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
let xVal = x.get(inRow, ic).getOrThrow()
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
sum = sum + xVal * kVal
}
kernelIdx += 1
}
}
}
}
combination.set(outRow, oc, sum)
}
}
}
}
// 手动广播偏置加法(因为 Matrix1 的 + 要求形状相同)
for (r in 0..outSize) {
for (c in 0..nOutputChannels) {
let val = combination.get(r, c).getOrThrow()
let biasVal = biases.get(0, c).getOrThrow()
combination.set(r, c, val + biasVal)
}
}
prevCombination = Option.Some(combination)
output = f.forward(combination)
prevOutput = Option.Some(output)
prevOutputShape = Option.Some((batch, outH, outW, nOutputChannels))
return output
}
// ==================== 反向传播 ====================
public func backward(errs: Matrix1): Matrix1 {
var x = Matrix1(1, 1)
match (prevInput) {
case Some(T) => x = T
case _ => throw Exception("No input data")
}
var shape = (0, 0, 0, 0)
match (prevInputShape) {
case Some(T) => shape = T
case _ => throw Exception("No input shape")
}
var combination = Matrix1(1, 1)
match (prevCombination) {
case Some(val) => combination = val
case _ => throw Exception("No combination")
}
var outShape = (0, 0, 0, 0)
match (prevOutputShape) {
case Some(val) => outShape = val
case _ => throw Exception("No output shape")
}
let batch = shape[0]
let inH = shape[1]
let inW = shape[2]
let outH = outShape[1]
let outW = outShape[2]
let delta = errs * f.backward(combination)
calcKernelGradient(x, delta, batch, inH, inW, outH, outW)
calcBiasGradient(delta)
let errsUp = calcUpsampledError(delta, batch, inH, inW, outH, outW)
return errsUp
}
/**
* 计算卷积核梯度
*/
private func calcKernelGradient(
x: Matrix1,
delta: Matrix1,
batch: Int64,
inH: Int64,
inW: Int64,
outH: Int64,
outW: Int64
): Unit {
var grads = Array(nOutputChannels, { _ => Matrix1(1, 1) })
for (oc in 0..nOutputChannels) {
let kernelCols = kernels[oc].get_col()
var grad = Matrix1(1, kernelCols)
for (b in 0..batch) {
for (oh in 0..outH) {
for (ow in 0..outW) {
let deltaRow = b * outH * outW + oh * outW + ow
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
var kernelIdx = 0
for (ic in 0..nInputChannels) {
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
if (connVal.getValue() > 0.0) {
for (kh in 0..kernelHeight) {
for (kw in 0..kernelWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
let xVal = x.get(inRow, ic).getOrThrow()
let oldGrad = grad.get(0, kernelIdx).getOrThrow()
grad.set(0, kernelIdx, oldGrad + xVal * deltaVal)
}
kernelIdx += 1
}
}
}
}
}
}
}
grads[oc] = grad
}
kernelsGrad = Option.Some(grads)
}
/**
* 计算偏置梯度
*/
private func calcBiasGradient(delta: Matrix1): Unit {
let rows = delta.get_row()
var grad = Matrix1(1, nOutputChannels)
for (oc in 0..nOutputChannels) {
var sum = FP32(0.0)
for (r in 0..rows) {
sum = sum + delta.get(r, oc).getOrThrow()
}
grad.set(0, oc, sum)
}
biasGrad = Option.Some(grad)
}
/**
* 计算传递到上一层的误差
*/
private func calcUpsampledError(
delta: Matrix1,
batch: Int64,
inH: Int64,
inW: Int64,
outH: Int64,
outW: Int64
): Matrix1 {
let inSize = batch * inH * inW
var errsUp = Matrix1(inSize, nInputChannels)
for (b in 0..batch) {
for (oh in 0..outH) {
for (ow in 0..outW) {
let deltaRow = b * outH * outW + oh * outW + ow
for (oc in 0..nOutputChannels) {
let deltaVal = delta.get(deltaRow, oc).getOrThrow()
var kernelIdx = 0
for (ic in 0..nInputChannels) {
let connVal = connectingMatrix.get(ic, oc).getOrThrow()
if (connVal.getValue() > 0.0) {
for (kh in 0..kernelHeight) {
for (kw in 0..kernelWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
let kVal = kernels[oc].get(0, kernelIdx).getOrThrow()
let oldErr = errsUp.get(inRow, ic).getOrThrow()
errsUp.set(inRow, ic, oldErr + deltaVal * kVal)
}
kernelIdx += 1
}
}
}
}
}
}
}
}
return errsUp
}
// ==================== 参数更新 ====================
public func update(discount: Float32): Unit {
var kGrad = Array(0, { _ => Matrix1(1, 1) })
match (kernelsGrad) {
case Some(val) => kGrad = val
case _ => throw Exception("No kernel gradients")
}
var bGrad = Matrix1(1, 1)
match (biasGrad) {
case Some(val) => bGrad = val
case _ => throw Exception("No bias gradient")
}
for (oc in 0..nOutputChannels) {
let rows = kernels[oc].get_row()
let cols = kernels[oc].get_col()
for (r in 0..rows) {
for (c in 0..cols) {
let oldVal = kernels[oc].get(r, c).getOrThrow()
let gradVal = kGrad[oc].get(r, c).getOrThrow()
kernels[oc].set(r, c, oldVal - gradVal * learningRate * FP32(discount))
}
}
}
for (oc in 0..nOutputChannels) {
let oldBias = biases.get(0, oc).getOrThrow()
let gradBias = bGrad.get(0, oc).getOrThrow()
biases.set(0, oc, oldBias - gradBias * learningRate * FP32(discount))
}
}
// ==================== 形状访问器 ====================
public func getInShape(): (Int64, Int64) {
(nInputChannels, kernelHeight * kernelWidth)
}
public func getOutShape(): Int64 {
nOutputChannels
}
public func getLR(): FP32 {
learningRate
}
public func getFType(): String {
f.get_type()
}
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
let outH = (inputHeight + 2 * padding - kernelHeight) / stride + 1
let outW = (inputWidth + 2 * padding - kernelWidth) / stride + 1
(outH, outW)
}
}
+11
View File
@@ -0,0 +1,11 @@
package bot4cj.math
//foreign func start_fft(M: CPointer<Unit>): CPointer<Unit>
//foreign func gen_wave(): CPointer<Unit>
/*-------- 生成测试用的正弦波 --------*/
//public func genWave(): Matrix {
// unsafe { return Matrix(gen_wave()) }
//}
/*-------- 执行fft算法 --------*/
//public func start_fft(M: Matrix): Matrix {
// unsafe { return Matrix(start_fft(M.getData())) }
//}
+136
View File
@@ -0,0 +1,136 @@
// flatten.cj - 移除调试代码
package bot4cj.math
import std.math.*
import std.collection.*
public class FlattenLayer {
let nInputChannels: Int64
let inputHeight: Int64
let inputWidth: Int64
let outputSize: Int64
var prevInput: Option<Matrix1>
var prevInputShape: Option<(Int64, Int64, Int64, Int64)>
var prevOutput: Option<Matrix1>
var prevOutputShape: Option<(Int64, Int64)>
public init(inputChannels: Int64, inputHeight: Int64, inputWidth: Int64) {
nInputChannels = inputChannels
this.inputHeight = inputHeight
this.inputWidth = inputWidth
outputSize = inputChannels * inputHeight * inputWidth
prevInput = None
prevInputShape = None
prevOutput = None
prevOutputShape = None
}
public init(inputShape: (Int64, Int64, Int64)) {
nInputChannels = inputShape[0]
inputHeight = inputShape[1]
inputWidth = inputShape[2]
outputSize = nInputChannels * inputHeight * inputWidth
prevInput = None
prevInputShape = None
prevOutput = None
prevOutputShape = None
}
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
prevInput = x
prevInputShape = inputShape
let batch = inputShape[0]
let inH = inputShape[1]
let inW = inputShape[2]
let inCh = inputShape[3]
let totalRows = batch * outputSize
var output = Matrix1(totalRows, 1)
for (b in 0..batch) {
for (h in 0..inH) {
for (w in 0..inW) {
for (ch in 0..inCh) {
let inRow = b * inH * inW + h * inW + w
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
if (outRow < totalRows) {
let val = x.get(inRow, ch).getOrThrow()
output.set(outRow, 0, val)
}
}
}
}
}
prevOutput = output
prevOutputShape = (totalRows, 1)
return output
}
public func backward(errs: Matrix1): Matrix1 {
var shape = (0, 0, 0, 0)
match (prevInputShape) {
case Some(T) => shape = T
case _ => throw Exception("FlattenLayer.backward: No input shape. Call forward() first.")
}
let batch = shape[0]
let inH = shape[1]
let inW = shape[2]
let inCh = shape[3]
let inSize = batch * inH * inW
var errsUp = Matrix1(inSize, nInputChannels)
let errsCols = errs.get_col()
for (b in 0..batch) {
for (h in 0..inH) {
for (w in 0..inW) {
let inRow = b * inH * inW + h * inW + w
for (ch in 0..inCh) {
let outRow = b * outputSize + (ch * inH * inW + h * inW + w)
if (outRow < errs.get_row()) {
if (errsCols == 1) {
let val = errs.get(outRow, 0).getOrThrow()
errsUp.set(inRow, ch, val)
} else {
var sum = FP32(0.0)
for (col in 0..errsCols) {
sum = sum + errs.get(outRow, col).getOrThrow()
}
errsUp.set(inRow, ch, sum)
}
}
}
}
}
}
return errsUp
}
public func update(discount: Float32): Unit {
// 无参数
}
public func getInShape(): (Int64, Int64, Int64) {
(nInputChannels, inputHeight, inputWidth)
}
public func getOutShape(): Int64 {
outputSize
}
public func getOutputSize(): Int64 {
outputSize
}
}
+69
View File
@@ -0,0 +1,69 @@
package bot4cj.math
/**
* 一维卡尔曼滤波器
* 使用时先定义一个kalman指针,然后调用kalmanCreate()创建一个滤波器
* 每次读取到传感器数据后即可调用KalmanFilter()来对数据进行滤波
* 使用示例
* extKalman_t p; //定义一个卡尔曼滤波器结构体
* float SersorData; //需要进行滤波的数据
* KalmanCreate(&p,20,200); //初始化该滤波器的Q=20 R=200参数
* while(1)
* {
* SersorData = sersor(); //获取数据
* SersorData = KalmanFilter(&p,SersorData); //对数据进行滤波
* }
*/
@C
public struct extKalman {
var X_last: Float32 // 上一时刻的最优结果 X(k-|k-1)
var P_last: Float32 // 上一时刻最优结果的协方差 P(k-1|k-1)
var A: Float32 // 系统参数
var B: Float32
var Q: Float32
var R: Float32
var H: Float32
/**
* @name kalmanCreate
* @brief 创建一个卡尔曼滤波器
* @param p: 滤波器
* T_Q:系统噪声协方差
* T_R:测量噪声协方差
*
* @retval none
* @attention R固定,Q越大,代表越信任侧量值,Q无穷代表只用测量值
* 反之,Q越小代表越信任模型预测值,Q为零则是只用模型预测
*/
public init(T_Q: Float32, T_R: Float32) {
X_last = 0.0f32
P_last = 0.0f32
Q = T_Q
R = T_R
A = 1.0f32
B = 0.0f32
H = 1.0f32
}
/**
* @name KalmanFilter
* @brief 卡尔曼滤波器
* @param p: 滤波器
* dat:待滤波数据
* @retval 滤波后的数据
* @attention Z(k)是系统输入,即测量值 X(k|k)是卡尔曼滤波后的值,即最终输出
* A=1 B=0 H=1 I=1 W(K) V(k)是高斯白噪声,叠加在测量值上了,可以不用管
* 以下是卡尔曼的5个核心公式
* 一阶H'即为它本身,否则为转置矩阵
*/
public mut func filter(dat: Float32): Float32 {
let X_mid = A * X_last //百度对应公式(1) x(k|k-1) = A*X(k-1|k-1)+B*U(k)+W(K)
let P_mid = A * P_last + Q //百度对应公式(2) p(k|k-1) = A*p(k-1|k-1)*A'+Q
let kg = P_mid / (P_mid + R) //百度对应公式(4) kg(k) = p(k|k-1)*H'/(H*p(k|k-1)*H'+R)
let X_now = X_mid + kg * (dat - X_mid) //百度对应公式(3) x(k|k) = X(k|k-1)+kg(k)*(Z(k)-H*X(k|k-1))
let P_now = (1.0f32 - kg) * P_mid //百度对应公式(5) p(k|k) = (I-kg(k)*H)*P(k|k-1)
P_last = P_now
X_last = X_now
return X_now //输出预测结果x(k|k)
}
}
+331
View File
@@ -0,0 +1,331 @@
package bot4cj.math
import std.fs.*
import std.io.*
import std.convert.*
//foreign func Sigmoid_do(m: CPointer<Unit>): CPointer<Unit>
//foreign func Sigmoid_dot(m: CPointer<Unit>): CPointer<Unit>
//foreign func ReLU_do(m: CPointer<Unit>): CPointer<Unit>
//foreign func ReLU_dot(m: CPointer<Unit>): CPointer<Unit>
//foreign func Leaky_ReLU_do(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
//foreign func Leaky_ReLU_dot(m: CPointer<Unit>, alpha: Float32): CPointer<Unit>
//foreign func Tanh_do(m: CPointer<Unit>): CPointer<Unit>
//foreign func Tanh_dot(m: CPointer<Unit>): CPointer<Unit>
/* -------- 新版的神经网络,使用原生矩阵对象实现 --------
* TODO(20260517-qing): 增加batch批次处理,使用ArrayList<Matrix1>
* TODO*(20260519-qing): 将神经网络转换为MatrixObject矩阵式对象
* TODO(20260524-qing): 增加Adam优化器 */
public class Nn1 {
let inputno: Int64 /* -------- 输入 -------- */
let outputno: Int64 /* -------- 输出 -------- */
var weights: Matrix1 /* -- (inputs, outputs) - */
var bias: Matrix1 /* ----- (1, output) ---- */
let learning_rate: FP32 /* ------- 学习率 ------- */
let f: Af /* ------ 激活函数 ------ */
/*------------------*/
var prev_inputs: Option<Matrix1> = None /* ---- 之前的输入 ---- */
var prev_raw_inputs: Option<Matrix1> = None /* ---- 之前的原始输出 ---- */
var prev_outputs: Option<Matrix1> = None /* ---- 之前的输出 ---- */
/*------------------*/
var weights_grad: Option<Matrix1> = None /* ---- 权重梯度 ---- */
var bias_grad: Option<Matrix1> = None /* ---- 偏置梯度 ---- */
/* -------- 初始化器 --------
* TODO(20260516-qing): 不要把初始化方法写死,使用枚举类 */
public init(i: Int64, o: Int64, lr: Float32, f: Af) {
inputno = i
outputno =o
learning_rate=FP32(lr)
this.f = f
weights = Matrix1(inputno, outputno)
bias = Matrix1(1, outputno)
/* -------- 初始化权重 -------- */
Ini.xavierUniformInit(weights, inputno, outputno)
}
/* -------- 神经网络推理 -------- */
public func forward(x: Matrix1) {
prev_inputs = x
var r = x.dot(weights)
r = r + bias
prev_raw_inputs = r
let r1 = f.forward(r)
prev_outputs = r1
return r1
}
/* -------- 反向传播算法 -------- */
public func backward(errs: Matrix1)
{
if (is_softmax(f)) {
return softmax_backward(errs)
}
let fbak = f.backward(prev_raw_inputs.getOrThrow())
let first = errs * fbak /* 逐元素相乘 */
let mx = prev_inputs.getOrThrow().T()
weights_grad = mx.dot(first)
bias_grad = first
let errs_up = first.dot(weights.T())
return errs_up
}
/* -------- 更新权重值、偏爱因子 --------
* NOTE: discount(折扣)是为了方便深度强化学习给出奖励值 */
public func update(discount: Float32) {
weights = weights - weights_grad.getOrThrow() * learning_rate * FP32(discount)
bias = bias - bias_grad.getOrThrow() * learning_rate * FP32(discount)
}
/**/
private static func is_softmax(f: Af) {
return match (f) {
case Af.Softmax => true
case _ => false
}
}
private func softmax_backward(errs: Matrix1) {
/* 使用雅可比矩阵的方式 */
let y = prev_outputs.getOrThrow() /* softmax 输出 */
let dy = errs /* 上游梯度 ∂L/∂y */
let jacobian = f.backward(y) /* n×n 雅可比矩阵 */
/* 将 dy 展平成列向量 n×1 */
let row = dy.get_row()
let col = dy.get_col()
let n = row * col
var dy_vec = Matrix1(n, 1)
var idx = 0
for (r in 0..row) {
for (c in 0..col) {
let val = dy.get(r, c).getOrThrow()
dy_vec.set(idx, 0, val)
idx = idx + 1
}
}
/* dx_vec = J · dy_vec (n×n × n×1 = n×1) */
var dx_vec = Matrix1(n, 1)
for (i in 0..n) {
var sum = 0.0f32
for (j in 0..n) {
let j_val = jacobian.get(i, j).getOrThrow().getValue()
let dy_val = dy_vec.get(j, 0).getOrThrow().getValue()
sum = sum + j_val * dy_val
}
dx_vec.set(i, 0, FP32(sum))
}
/* 恢复为原始形状,作为 first */
var first = Matrix1(row, col)
idx = 0
for (r in 0..row) {
for (c in 0..col) {
let val = dx_vec.get(idx, 0).getOrThrow()
first.set(r, c, val)
idx = idx + 1
}
}
let mx = prev_inputs.getOrThrow().T()
weights_grad = mx.dot(first)
bias_grad = first
let errs_up = first.dot(weights.T())
return errs_up
}
/* -------- 形状访问器 -------- */
public func get_in_shape() {
return inputno }
public func get_out_shape() {
return outputno }
public func get_lr() {
return learning_rate }
public func get_f_type() {
return f.get_type() }
public func get_f_val() {
return f.get_value() }
/* ---------------------------- */
}
///* -------- 旧版激活函数 --------*/
//public enum ActivationFuncs {
// Sigmoid | ReLU | Leaky_ReLU | Tanh
// func fdo(m: Matrix): Matrix { // 暂时只设置ReLU
// unsafe {
// match(this) {
// case Sigmoid => Matrix(Sigmoid_do(m.getData()))
// case ReLU => Matrix(ReLU_do(m.getData()))
// case Leaky_ReLU => Matrix(Leaky_ReLU_do(m.getData(), 0.1f32))
// case Tanh => Matrix(Tanh_do(m.getData()))
// }
// }
// }
// func fdot(m: Matrix): Matrix {
// unsafe {
// match(this) {
// case Sigmoid => Matrix(Sigmoid_dot(m.getData()))
// case ReLU => Matrix(ReLU_dot(m.getData()))
// case Leaky_ReLU => Matrix(Leaky_ReLU_dot(m.getData(), 0.1f32))
// case Tanh => Matrix(Tanh_dot(m.getData()))
// }
// }
// }
// func toString(): String {
// match(this) {
// case Sigmoid => "Sigmoid"
// case ReLU => "ReLU"
// case Leaky_ReLU => "Leaky_ReLU"
// case Tanh => "Tanh"
// }
// }
// static func parse(s: String) {
// match(s) {
// case "Sigmoid" => ActivationFuncs.Sigmoid
// case "ReLU" => ActivationFuncs.ReLU
// case "Leaky_ReLU" => ActivationFuncs.Leaky_ReLU
// case "Tanh" => ActivationFuncs.Tanh
// case _ => throw IllegalArgumentException()
// }
// }
//}
///* -------- 旧版的神经网络类 --------
// * 这个实现依赖C++语言的动态分享库,特点是速度快
// * 这个实现可能是有问题的 */
//public class Nn {
//
// /* -------- 主初始化器 -------- */
// public init(
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32) {
// this.inputno = inputno
// this.outputno = outputno
// weights = Matrix.initWithXavierUniform(inputno, outputno)
// bias = Matrix(1, outputno)
// this.f = f
// this.learning_rate = learning_rate
// }
//
// /* -------- 提供了权重矩阵的初始化器 -------- */
// public init(
// inputno: UInt64, outputno: UInt64, f: ActivationFuncs, learning_rate: Float32, weights: Matrix, bias: Matrix) {
// this.inputno = inputno
// this.outputno = outputno
// this.weights = weights
// this.bias = bias
// this.f = f
// this.learning_rate = learning_rate
// }
//
// /* -------- 神经网络推理 -------- */
// func forward(x: Matrix): Matrix {
// /* x => (1, inputs) */
// let r = x.dot(weights) + bias /* (1, outputno) */
// return r
// }
//
// /* -------- 实施激活函数 -------- */
// func fdo(x: Matrix): Matrix {
// return f.fdo(x) }
//
// /* -------- 激活函数导数 -------- */
// func fdot(x: Matrix): Matrix {
// return f.fdot(x) }
//
// /* This is Wrong */
// //func bak_bsgrad(prev_raw_outputs_dotf: Matrix, errs: Matrix): Matrix {
// /* prev_raw_outputs_dotf => (1, outputno)
// * errs => (1, outputno) */
//
// // return errs * prev_raw_outputs_dotf
// //}
//
// /* -------- 权重梯度的传播 -------- */
// func bak_wsgrad(prev_inputs: Matrix, delta: Matrix) {
// /* prev_inputs => (1, inputs) */
// return prev_inputs.T().dot(delta) /* (inputs, outputs) */
// }
//
// /* -------- 计算上一层的误差 -------- */
// func bak_uperrs(delta: Matrix, prev_dotf: Matrix) {
// let first = weights.dot(delta.T()).T() // (1, inputs)
//
// var uperrs = first * prev_dotf
// uperrs
// }
//
// /* -------- 更新权重、偏爱因子 -------- */
// func update(weights_grad: Matrix, bias_grad: Matrix, discount: Float32) {
// weights -= weights_grad * learning_rate * discount
// bias -= bias_grad * learning_rate * discount
// }
//
// /* -------- 打印权重、偏爱因子 -------- */
// func print() {
// weights.print()
// bias.print()
// }
//
// /* -------- 打印神经网络全连接层的形状 -------- */
// func print_shape() {
// println("${inputno}, ${outputno}") }
//
// /* -------- 储存神经网络 -------- */
// func save(file: File) {
// file.write("FullConnectLayer ".toArray())
// file.write(inputno.toString().toArray())
// file.write(" ".toArray())
// file.write(outputno.toString().toArray())
// file.write(" ".toArray())
// file.write(learning_rate.toString().toArray())
// file.write(" ".toArray())
// file.write(f.toString().toArray())
// file.write("\n".toArray())
// weights.save(file)
// //file.write("\n".toArray())
// bias.save(file)
// //file.write("\n".toArray())
// }
//
// /* -------- 静态方法,加载单个项目 -------- */
// static func loadItem<T>(sr: StringReader<InputStream>) where T <: Parsable<T>
// {
// let s = sr.readUntil({r => r == r" " || r == r"\n"})
// let ss = s.getOrThrow().trimAscii()
// println(ss)
// return T.parse(ss)
// }
//
// /* -------- 静态方法,加载神经网络 -------- */
// static func load(sr: StringReader<InputStream>)
// {
// let inputno = loadItem<UInt64>(sr)
// let outputno = loadItem<UInt64>(sr)
// let lr = loadItem<Float32>(sr)
// let fna = sr.readUntil(r'\n')
// let f = ActivationFuncs.parse(fna.getOrThrow().trimAscii())
// let weights = Matrix.load(sr, inputno, outputno)
// let bias = Matrix.load(sr, 1, outputno)
// Nn(inputno, outputno, f, lr, weights, bias)
// }
//
// let inputno: UInt64
// let outputno: UInt64
// var weights: Matrix /* (inputs, outputs) */
// var bias: Matrix /* (1, outputs) */
// var learning_rate: Float32
// let f: ActivationFuncs
//}
+164
View File
@@ -0,0 +1,164 @@
package bot4cj.math
import std.io.*
import std.fs.*
import std.math.*
import std.time.*
import std.random.*
import std.collection.*
/* -------- 准备与门的测试用例 --------
* INFO(20260524-qing): 输入{2},输出{1} */
public func prepare_andDoor(m: Matrix1, t: Matrix1) {
let random = Random(UInt64(DateTime.now().nanosecond))
let a = random.nextInt8(2)
let b = random.nextInt8(2)
let t0 = if ((a == 0 && b == 0) || (a ==1 && b == 1)) { 1.0f32 } else { 0.0f32 }
//let t1 = if (a != b) { 1.0f32 } else { 0.0f32 }
m.set(0, 0, FP32(Float32(a)))
m.set(0, 1, FP32(Float32(b)))
t.set(0, 0, FP32(t0))
//t.set(0, 1, FP32(t1))
}
/* -------- 新版神经网络推理,使用原生矩阵 -------- */
public func forward1(nns: ArrayList<Nn1>, m: Matrix1) {
var out_dof = Matrix1(1, 1) /* ---- 给一个值占位 ----*/
for (idx in 0..nns.size) {
let x = if (idx == 0) { m } else { out_dof }
out_dof= nns[idx].forward(x)
}
return out_dof
}
/* 【神经网络训练】
* description: 输入一组原始数据和教师数据,进行神经网络训练
* @param nns: 输入神经网络结构
* @param m: 输入一组原始数据
* @param t: 输入一组教师数据
* @param discount: 输入本次训练的折扣系数
*/
public func train1(nns: ArrayList<Nn1>, m: Matrix1, t: Matrix1, discount: Float32) {
/* ======== 这个对象用于暂时占位 ======== */
var res = Matrix1(1, 1)
/* ======== 正向传播 ======== */
for (idx in 0..nns.size) {
let x = if (idx == 0) { m } else { res }
res = nns[idx].forward(x)
}
/* ======== 计算误差 ======== */
var errs = t - res
/* ======== 反向传播 ======== */
for (idx in (nns.size-1)..=0:-1) {
errs = nns[idx].backward(errs)
}
/* ======== 梯度更新 ======== */
for (idx in 0..nns.size) {
nns[idx].update(discount)
}
/* ======== 计算MSE损失 ========
FIXME*(20260517-qing): 对于多个结果计算误差(目前只计算了第一个结果) */
let t0 = t.get(0, 0).getOrThrow().getValue()
//let t1 = t.get(0, 1).getOrThrow().getValue()
let r0 = res.get(0, 0).getOrThrow().getValue()
//let r1 = res.get(0, 1).getOrThrow().getValue()
let loss0 = 0.5 * (t0 - r0) * (t0 - r0)
//let loss1 = 0.5 * (t1 - r1) * (t1 - r1)
//return (loss0 + loss1) / 2.0
return loss0
}
/* -------- (旧版)加载多层神经网络 --------
TODO: 也许用类封装
INFO(20260517-qing): 新版使用仓颉原生矩阵,然后与MatrixObject类互转 */
//public func load_nns(filename: String): ArrayList<Nn> {
// let nns = ArrayList<Nn>()
// let file = File(filename, OpenMode.Read)
// let sr = StringReader<InputStream>(file)
// while (true) {
// if (let Some(S) <- sr.readUntil(r' ')) {
// var S2 = S.trimAscii()
// if (S2 == "FullConnectLayer") {
// nns.add(Nn.load(sr)) }
// else if (S2 == "") { break }
// else { throw IllegalArgumentException() }
// }
// else { break }
// }
// file.close()
// return nns
//}
/* -------- (旧版)储存多层神经网络 -------- */
//public func save_nns(nns: ArrayList<Nn>, filename: String) {
//
// let file: File
// if (exists(filename)) {
// file = File(filename, OpenMode.Write) }
// else { file= File.create(filename) }
// for (nn in nns) { /* 存储全部的层 */
// nn.save(file) }
// file.close()
//}
/* -------- 旧版神经网络推理 -------- */
//public func forward(nns: ArrayList<Nn>, m: Matrix) {
// var out_dof = Matrix(1, 1)
// for (idx in 0..nns.size) {
// let x = if (idx == 0) { m } else { out_dof }
// let res = nns[idx].forward(x)
// out_dof = nns[idx].fdo(res)
// }
// return out_dof
//}
/* -------- 旧版的神经网络训练 -------- */
//public func train(nns: ArrayList<Nn>, m: Matrix, t: Matrix, discount: Float32) {
//
// /* ----------------- */
// let outs_dof = ArrayList<Matrix> ()
// let outs_dotf = ArrayList<Matrix> ()
//
// /* -------- 正向传播 -------- */
// for (idx in 0..nns.size) {
// let x = if (idx == 0) { m } else { outs_dof[idx-1] }
// let res = nns[idx].forward(x)
// let out_dof = nns[idx].fdo(res)
// let out_dotf = nns[idx].fdot(out_dof)
// outs_dof.add(out_dof)
// outs_dotf.add(out_dotf)
// }
//
// /* -------- 计算误差 -------- */
// var errs = outs_dof[nns.size-1] - t
//
// /* -------- 反向传播 -------- */
// for (idx in (nns.size-1)..=0:-1) {
// let up = if (idx == 0) { m} else { outs_dof[idx-1] }
// let bias_grad = errs
// //let delta = nns[idx].bak_bsgrad(errs, outs_dotf[idx])
// let weights_grad = nns[idx].bak_wsgrad(up, bias_grad)
// if (idx > 0) {
// errs = nns[idx].bak_uperrs(bias_grad, outs_dotf[idx-1]) }
// nns[idx].update(weights_grad, bias_grad, discount)
// }
//
// /* -------- 输出损失 -------- */
// unsafe {
// let t0 = t.get(0, 0)
// let t1 = t.get(0, 1)
// let r0 = outs_dof[nns.size-1].get(0, 0)
// let r1 = outs_dof[nns.size-1].get(0, 1)
// let loss0 = -t0 * log(r0) - (1.0f32-t0) * log(1.0f32-r0);
// let loss1 = -t1 * log(r1) - (1.0f32-t1) * log(1.0f32-r1);
// return 0.5 * (loss0 * loss0 + loss1 * loss1)
// }
//
//}
+418
View File
@@ -0,0 +1,418 @@
// pooling_layer.cj
package bot4cj.math
import std.math.*
import std.collection.*
/**
* 池化层类
* 设计风格与 Nn1 和 ConvLayer 保持一致
*/
public class PoolingLayer {
// ==================== 层配置参数 ====================
let nChannels: Int64
let poolHeight: Int64
let poolWidth: Int64
let stride: Int64
let padding: Int64
let poolType: String
let f: Af
let learningRateWeight: FP32
let learningRateBias: FP32
// ==================== 可学习参数 ====================
var weights: Matrix1
var biases: Matrix1
// ==================== 运行时缓存 ====================
var prevInput: Option<Matrix1> = Option.None
var prevInputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
var prevCombination: Option<Matrix1> = Option.None
var prevOutput: Option<Matrix1> = Option.None
var prevOutputShape: Option<(Int64, Int64, Int64, Int64)> = Option.None
var prevMaxIndices: Option<Matrix1> = Option.None
var weightsGrad: Option<Matrix1> = Option.None
var biasGrad: Option<Matrix1> = Option.None
// ==================== 构造函数 ====================
public init(
nChannels: Int64,
poolSize: (Int64, Int64),
stride: Int64,
padding: Int64,
poolType: String,
activation: Af,
lrWeight: FP32,
lrBias: FP32
) {
this.nChannels = nChannels
poolHeight = poolSize[0]
poolWidth = poolSize[1]
this.stride = stride
this.padding = padding
this.poolType = poolType
this.f = activation
learningRateWeight = lrWeight
learningRateBias = lrBias
// 初始化权值
weights = Matrix1(1, nChannels)
if (poolType == "average") {
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
for (c in 0..nChannels) {
weights.set(0, c, avgWeight)
}
} else {
for (c in 0..nChannels) {
weights.set(0, c, FP32(1.0))
}
}
// 初始化偏置为 0
biases = Matrix1(1, nChannels)
for (c in 0..nChannels) {
biases.set(0, c, FP32(0.0))
}
}
public init(
nChannels: Int64,
poolSize: (Int64, Int64),
poolType: String,
activation: Af
) {
this.nChannels = nChannels
poolHeight = poolSize[0]
poolWidth = poolSize[1]
stride = poolHeight
padding = 0
this.poolType = poolType
this.f = activation
learningRateWeight = FP32(0.1)
learningRateBias = FP32(0.1)
weights = Matrix1(1, nChannels)
if (poolType == "average") {
let avgWeight = FP32(1.0) / FP32(Float32(poolHeight * poolWidth))
for (c in 0..nChannels) {
weights.set(0, c, avgWeight)
}
} else {
for (c in 0..nChannels) {
weights.set(0, c, FP32(1.0))
}
}
biases = Matrix1(1, nChannels)
for (c in 0..nChannels) {
biases.set(0, c, FP32(0.0))
}
}
// ==================== 前向传播 ====================
public func forward(x: Matrix1, inputShape: (Int64, Int64, Int64, Int64)): Matrix1 {
prevInput = Option.Some(x)
prevInputShape = Option.Some(inputShape)
let batch = inputShape[0]
let inH = inputShape[1]
let inW = inputShape[2]
let outH = (inH + 2 * padding - poolHeight) / stride + 1
let outW = (inW + 2 * padding - poolWidth) / stride + 1
let outSize = batch * outH * outW
var combination = Matrix1(outSize, nChannels)
var output = Matrix1(outSize, nChannels)
var maxIndices = Matrix1(outSize, nChannels)
for (b in 0..batch) {
for (oh in 0..outH) {
for (ow in 0..outW) {
let outRow = b * outH * outW + oh * outW + ow
for (ch in 0..nChannels) {
var pooledVal = FP32(0.0)
var maxIdx = 0
if (poolType == "max") {
var maxVal = FP32(-1.0e30)
var kernelIdx = 0
for (kh in 0..poolHeight) {
for (kw in 0..poolWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
let val = x.get(inRow, ch).getOrThrow()
if (val.getValue() > maxVal.getValue()) {
maxVal = val
maxIdx = kernelIdx
}
}
kernelIdx += 1
}
}
pooledVal = maxVal
} else {
var sum = FP32(0.0)
var count = 0
for (kh in 0..poolHeight) {
for (kw in 0..poolWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
sum = sum + x.get(inRow, ch).getOrThrow()
count += 1
}
}
}
if (count > 0) {
pooledVal = sum / FP32(Float32(count))
}
}
combination.set(outRow, ch, pooledVal)
maxIndices.set(outRow, ch, FP32(Float32(maxIdx)))
}
}
}
}
// 逐通道应用权值和偏置
for (r in 0..outSize) {
for (ch in 0..nChannels) {
let val = combination.get(r, ch).getOrThrow()
let w = weights.get(0, ch).getOrThrow()
let b = biases.get(0, ch).getOrThrow()
combination.set(r, ch, val * w + b)
}
}
prevCombination = Option.Some(combination)
output = f.forward(combination)
prevOutput = Option.Some(output)
prevOutputShape = Option.Some((batch, outH, outW, nChannels))
prevMaxIndices = Option.Some(maxIndices)
return output
}
// ==================== 反向传播 ====================
public func backward(errs: Matrix1): Matrix1 {
var x = Matrix1(1, 1)
match (prevInput) {
case Some(val) => x = val
case _ => throw Exception("No input data")
}
var shape = (0, 0, 0, 0)
match (prevInputShape) {
case Some(val) => shape = val
case _ => throw Exception("No input shape")
}
var combination = Matrix1(1, 1)
match (prevCombination) {
case Some(val) => combination = val
case _ => throw Exception("No combination")
}
var outShape = (0, 0, 0, 0)
match (prevOutputShape) {
case Some(val) => outShape = val
case _ => throw Exception("No output shape")
}
var maxIndices = Matrix1(1, 1)
let hasMaxIndices = match (prevMaxIndices) {
case Some(_) => true
case _ => false
}
if (hasMaxIndices) {
maxIndices = match (prevMaxIndices) {
case Some(val) => val
case _ => Matrix1(1, 1)
}
}
let batch = shape[0]
let inH = shape[1]
let inW = shape[2]
let outH = outShape[1]
let outW = outShape[2]
let delta = errs * f.backward(combination)
calcWeightBiasGradient(delta, combination)
let errsUp = calcUpsampledError(delta, maxIndices, batch, inH, inW, outH, outW)
return errsUp
}
/**
* 计算权值和偏置梯度
*/
private func calcWeightBiasGradient(delta: Matrix1, combination: Matrix1): Unit {
let outSize = delta.get_row()
var wGrad = Matrix1(1, nChannels)
var bGrad = Matrix1(1, nChannels)
for (ch in 0..nChannels) {
var wSum = FP32(0.0)
var bSum = FP32(0.0)
for (r in 0..outSize) {
let d = delta.get(r, ch).getOrThrow()
bSum = bSum + d
let comb = combination.get(r, ch).getOrThrow()
wSum = wSum + d * comb
}
wGrad.set(0, ch, wSum)
bGrad.set(0, ch, bSum)
}
weightsGrad = Option.Some(wGrad)
biasGrad = Option.Some(bGrad)
}
/**
* 上采样误差(池化层的反向传播核心)
*/
private func calcUpsampledError(
delta: Matrix1,
maxIndices: Matrix1,
batch: Int64,
inH: Int64,
inW: Int64,
outH: Int64,
outW: Int64
): Matrix1 {
let inSize = batch * inH * inW
var errsUp = Matrix1(inSize, nChannels)
for (b in 0..batch) {
for (oh in 0..outH) {
for (ow in 0..outW) {
let deltaRow = b * outH * outW + oh * outW + ow
for (ch in 0..nChannels) {
let deltaVal = delta.get(deltaRow, ch).getOrThrow()
let w = weights.get(0, ch).getOrThrow()
if (poolType == "max") {
let maxIdx = Int64(maxIndices.get(deltaRow, ch).getOrThrow().getValue())
var kernelIdx = 0
var found = false
for (kh in 0..poolHeight) {
for (kw in 0..poolWidth) {
if (kernelIdx == maxIdx && !found) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
errsUp.set(inRow, ch, deltaVal * w)
found = true
}
}
kernelIdx += 1
}
}
} else {
var count = 0
for (kh in 0..poolHeight) {
for (kw in 0..poolWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
count += 1
}
}
}
if (count > 0) {
let avgDelta = deltaVal * w / FP32(Float32(count))
for (kh in 0..poolHeight) {
for (kw in 0..poolWidth) {
let ih = oh * stride + kh - padding
let iw = ow * stride + kw - padding
if (ih >= 0 && ih < inH && iw >= 0 && iw < inW) {
let inRow = b * inH * inW + ih * inW + iw
let oldVal = errsUp.get(inRow, ch).getOrThrow()
errsUp.set(inRow, ch, oldVal + avgDelta)
}
}
}
}
}
}
}
}
}
return errsUp
}
// ==================== 参数更新 ====================
public func update(discount: Float32): Unit {
var wGrad = Matrix1(1, 1)
match (weightsGrad) {
case Some(val) => wGrad = val
case _ => throw Exception("No weight gradients")
}
var bGrad = Matrix1(1, 1)
match (biasGrad) {
case Some(val) => bGrad = val
case _ => throw Exception("No bias gradients")
}
for (ch in 0..nChannels) {
let oldW = weights.get(0, ch).getOrThrow()
let gW = wGrad.get(0, ch).getOrThrow()
weights.set(0, ch, oldW + gW * learningRateWeight * FP32(discount))
}
for (ch in 0..nChannels) {
let oldB = biases.get(0, ch).getOrThrow()
let gB = bGrad.get(0, ch).getOrThrow()
biases.set(0, ch, oldB + gB * learningRateBias * FP32(discount))
}
}
// ==================== 形状访问器 ====================
public func getInShape(): (Int64, Int64) {
(nChannels, poolHeight * poolWidth)
}
public func getOutShape(): Int64 {
nChannels
}
public func getPoolType(): String {
poolType
}
public func getLR(): FP32 {
learningRateWeight
}
public func getFType(): String {
f.get_type()
}
public func calculateOutputSize(inputHeight: Int64, inputWidth: Int64): (Int64, Int64) {
let outH = (inputHeight + 2 * padding - poolHeight) / stride + 1
let outW = (inputWidth + 2 * padding - poolWidth) / stride + 1
(outH, outW)
}
}
+1071
View File
File diff suppressed because it is too large. Load diff
+310
View File
@@ -0,0 +1,310 @@
// test_cnn.cj - 使用 Adam 优化器的版本(所有层)
package bot4cj
import bot4cj.math.*
import std.math.*
import std.random.*
import std.collection.*
func generateSolidColorData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
var labels = Array(numSamples, { _ => 0 })
let rng = Random()
for (i in 0..numSamples) {
let colorClass = Int64(rng.nextFloat64() * 3.0)
labels[i] = colorClass
let h = 8
let w = 8
var matrix = Matrix1(h * w, 1)
let colorValue = match (colorClass) {
case 0 => FP32(1.0)
case 1 => FP32(0.5)
case _ => FP32(0.0)
}
for (r in 0..h * w) {
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.1)
var val = colorValue + noise
if (val.getValue() > 1.0) {
val = FP32(1.0)
} else if (val.getValue() < 0.0) {
val = FP32(0.0)
}
matrix.set(r, 0, val)
}
inputs[i] = matrix
}
(inputs, labels)
}
func printSeparator(): Unit {
println("==================================================")
}
func testCNN(): Unit {
printSeparator()
println("CNN 纯色矩阵识别测试 (所有层使用 Adam 优化器)")
printSeparator()
println()
let nTrainSamples = 200
let nTestSamples = 60
let nEpochs = 50
let batchSize = 10
println("训练样本数: ${nTrainSamples}")
println("测试样本数: ${nTestSamples}")
println("训练轮数: ${nEpochs}")
println("Batch 大小: ${batchSize}")
println()
println("创建网络结构...")
println("Input(8x8x1) -> Conv(3x3,1->4) -> Pool(2x2) -> Conv(3x3,4->8) -> Flatten -> FC(8,3)")
println()
let lr = FP32(0.001)
var conv1 = ConvLayer(1, 4, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
var pool1 = PoolingLayer(4, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
var conv2 = ConvLayer(4, 8, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
var flatten = FlattenLayer(8, 1, 1)
var fc = Nn1(8, 3, Float32(0.4), Af.Softmax)
// 创建 Adam 优化器(为每个可训练层创建独立的优化器)
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
var adamFC = Adam(FP32(0.0005)) // 全连接层(使用更小的学习率)
println("网络创建完成 (Conv1, Conv2, FC 使用 Adam 优化器)")
println()
println("生成数据...")
let (trainInputs, trainLabels) = generateSolidColorData(nTrainSamples)
let (testInputs, testLabels) = generateSolidColorData(nTestSamples)
println("训练集: ${nTrainSamples} 样本")
println("测试集: ${nTestSamples} 样本")
println()
println("开始训练...")
println()
for (epoch in 0..nEpochs) {
var epochLoss = Float32(0.0)
var epochCorrect = 0
for (i in 0..nTrainSamples) {
let x = trainInputs[i]
let label = trainLabels[i]
// ===== 前向传播 =====
let c1Out = conv1.forward(x, (1, 8, 8, 1))
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
// 调试:确认 flatten.forward 执行成功
if (i == 0 && epoch == 0) {
println("DEBUG: flatten.forward done, flatOut rows: ${flatOut.get_row()}, cols: ${flatOut.get_col()}")
}
var fcInput = Matrix1(1, 8)
for (j in 0..8) {
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
}
let fcOut = fc.forward(fcInput)
// Softmax
var softmax = Matrix1(1, 3)
var maxVal = Float32(-1.0e30)
for (c in 0..3) {
let raw = fcOut.get(0, c).getOrThrow().getValue()
if (raw > maxVal) {
maxVal = raw
}
}
var expSum = Float32(0.0)
for (c in 0..3) {
let raw = fcOut.get(0, c).getOrThrow().getValue()
let ev = Float32(exp(raw - maxVal))
softmax.set(0, c, FP32(ev))
expSum = expSum + ev
}
for (c in 0..3) {
let v = softmax.get(0, c).getOrThrow().getValue()
softmax.set(0, c, FP32(v / expSum))
}
let probVal = softmax.get(0, label).getOrThrow().getValue()
let loss = -log(max(probVal, Float32(1e-10)))
epochLoss = epochLoss + loss
var maxProb = Float32(-1.0)
var predClass = 0
for (c in 0..3) {
let p = softmax.get(0, c).getOrThrow().getValue()
if (p > maxProb) {
maxProb = p
predClass = Int64(c)
}
}
if (predClass == label) {
epochCorrect += 1
}
// ===== 反向传播 =====
var delta = Matrix1(1, 3)
for (c in 0..3) {
let prob = softmax.get(0, c).getOrThrow()
if (Int64(c) == label) {
delta.set(0, c, prob - FP32(1.0))
} else {
delta.set(0, c, prob)
}
}
let fcDelta = fc.backward(delta)
// 调试:确认 fcDelta 形状
if (i == 0 && epoch == 0) {
println("DEBUG: fc.backward done, fcDelta rows: ${fcDelta.get_row()}, cols: ${fcDelta.get_col()}")
}
var flatDelta = Matrix1(8, 1)
for (j in 0..8) {
flatDelta.set(j, 0, fcDelta.get(0, j).getOrThrow())
}
// 调试:确认 flatDelta 形状
if (i == 0 && epoch == 0) {
println("DEBUG: flatDelta rows: ${flatDelta.get_row()}, cols: ${flatDelta.get_col()}")
}
let c2Delta = flatten.backward(flatDelta)
let p1Delta = conv2.backward(c2Delta)
let c1Delta = pool1.backward(p1Delta)
let _ = conv1.backward(c1Delta)
// ===== 参数更新 =====
if ((i + 1) % batchSize == 0) {
// 所有可训练层使用 Adam 更新
adamConv1.updateConvLayer(conv1) // 卷积层1 - Adam
adamConv2.updateConvLayer(conv2) // 卷积层2 - Adam
adamFC.updateFCLayer(fc) // 全连接层 - Adam
// 池化层使用 SGD 更新(如果需要的话)
pool1.update(Float32(1.0))
}
}
// Epoch 结束时的更新
adamConv1.updateConvLayer(conv1)
adamConv2.updateConvLayer(conv2)
adamFC.updateFCLayer(fc)
pool1.update(Float32(1.0))
let avgLoss = epochLoss / Float32(nTrainSamples)
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%")
// 打印 Adam 的步数信息
if (epoch == 0) {
println(" Adam Step: ${adamConv1.getStep()}")
}
}
println("")
println("开始测试...")
println("")
var testCorrect = 0
var confusion = Array(3, { _ => Array(3, { _ => 0 }) })
for (i in 0..nTestSamples) {
let x = testInputs[i]
let label = testLabels[i]
let c1Out = conv1.forward(x, (1, 8, 8, 1))
let p1Out = pool1.forward(c1Out, (1, 6, 6, 4))
let c2Out = conv2.forward(p1Out, (1, 3, 3, 4))
let flatOut = flatten.forward(c2Out, (1, 1, 1, 8))
var fcInput = Matrix1(1, 8)
for (j in 0..8) {
fcInput.set(0, j, flatOut.get(j, 0).getOrThrow())
}
let fcOut = fc.forward(fcInput)
var softmax = Matrix1(1, 3)
var maxVal = Float32(-1.0e30)
for (c in 0..3) {
let raw = fcOut.get(0, c).getOrThrow().getValue()
if (raw > maxVal) {
maxVal = raw
}
}
var expSum = Float32(0.0)
for (c in 0..3) {
let raw = fcOut.get(0, c).getOrThrow().getValue()
let ev = Float32(exp(raw - maxVal))
softmax.set(0, c, FP32(ev))
expSum = expSum + ev
}
for (c in 0..3) {
let v = softmax.get(0, c).getOrThrow().getValue()
softmax.set(0, c, FP32(v / expSum))
}
var maxProb = Float32(-1.0)
var predClass = 0
for (c in 0..3) {
let p = softmax.get(0, c).getOrThrow().getValue()
if (p > maxProb) {
maxProb = p
predClass = Int64(c)
}
}
confusion[label][predClass] += 1
if (predClass == label) {
testCorrect += 1
}
}
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
println("测试准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
print("")
let classNames = ["红色(高)", "绿色(中)", "蓝色(低)"]
println("各类别准确率:")
for (c in 0..3) {
var total = 0
for (p in 0..3) {
total += confusion[c][p]
}
if (total > 0) {
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
print(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
}
}
println("")
println("混淆矩阵:")
println(" 预测红 预测绿 预测蓝")
for (t in 0..3) {
var line = "真实${classNames[t]}:"
for (p in 0..3) {
line = line + " ${confusion[t][p]} "
}
print(line)
}
println("")
printSeparator()
println("测试完成!")
printSeparator()
}
+517
View File
@@ -0,0 +1,517 @@
// test_geometry_cnn.cj - 几何图形分类测试(Adam 优化器版本)
package bot4cj
import bot4cj.math.*
import std.math.*
import std.random.*
import std.collection.*
/**
* 计算圆形像素值
*/
func calcCirclePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
let dx = Float64(x) - center + offsetX
let dy = Float64(y) - center + offsetY
let dist = sqrt(dx * dx + dy * dy)
if (dist <= radius) {
let intensity = 1.0 - (dist / radius) * 0.3
FP32(Float32(intensity))
} else if (dist <= radius + 1.5) {
let edgeDist = dist - radius
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.7
FP32(Float32(intensity))
} else {
FP32(0.0)
}
}
/**
* 计算正方形像素值
*/
func calcSquarePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
let halfSize = radius * 0.8
let left = center - halfSize + offsetX
let right = center + halfSize + offsetX
let top = center - halfSize + offsetY
let bottom = center + halfSize + offsetY
let fx = Float64(x)
let fy = Float64(y)
if (fx >= left && fx <= right && fy >= top && fy <= bottom) {
let distToEdge = min(
min(fx - left, right - fx),
min(fy - top, bottom - fy)
)
let intensity = min(1.0, 0.7 + distToEdge / halfSize * 0.3)
FP32(Float32(intensity))
} else {
let distX = max(0.0, max(left - fx, fx - right))
let distY = max(0.0, max(top - fy, fy - bottom))
let edgeDist = sqrt(distX * distX + distY * distY)
if (edgeDist <= 1.5) {
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
FP32(Float32(intensity))
} else {
FP32(0.0)
}
}
}
/**
* 计算三角形像素值
*/
func calcTrianglePixel(x: Int64, y: Int64, center: Float64, radius: Float64, offsetX: Float64, offsetY: Float64): FP32 {
let height = radius * 2.0
let baseWidth = height * 1.2
let topY = center - height * 0.5 + offsetY
let bottomY = center + height * 0.5 + offsetY
let fx = Float64(x)
let fy = Float64(y)
if (fy >= topY && fy <= bottomY) {
let t = (fy - topY) / (bottomY - topY)
let halfWidthAtY = baseWidth * (1.0 - t) * 0.5
let centerX = center + offsetX
if (fx >= centerX - halfWidthAtY && fx <= centerX + halfWidthAtY) {
let distToEdge = min(
fx - (centerX - halfWidthAtY),
(centerX + halfWidthAtY) - fx
)
let intensity = min(1.0, 0.7 + distToEdge / halfWidthAtY * 0.3)
FP32(Float32(intensity))
} else {
let distToLeft = (centerX - halfWidthAtY) - fx
let distToRight = fx - (centerX + halfWidthAtY)
let edgeDist = max(distToLeft, distToRight)
if (edgeDist <= 1.5) {
let intensity = max(0.0, 1.0 - edgeDist / 1.5) * 0.6
FP32(Float32(intensity))
} else {
FP32(0.0)
}
}
} else {
FP32(0.0)
}
}
/**
* 生成几何图形数据
* 图形类型:0=圆形, 1=正方形, 2=三角形
*/
func generateGeometryData(numSamples: Int64): (Array<Matrix1>, Array<Int64>) {
var inputs = Array(numSamples, { _ => Matrix1(1, 1) })
var labels = Array(numSamples, { _ => 0 })
let rng = Random()
let imgSize = 16
for (i in 0..numSamples) {
let shapeType = Int64(rng.nextFloat64() * 3.0)
labels[i] = shapeType
var matrix = Matrix1(imgSize * imgSize, 1)
let center = Float64(imgSize) / 2.0
let radius = Float64(imgSize) / 3.0 + rng.nextFloat64() * 2.0 - 1.0
let offsetX = (rng.nextFloat64() - 0.5) * 2.0
let offsetY = (rng.nextFloat64() - 0.5) * 2.0
for (y in 0..imgSize) {
for (x in 0..imgSize) {
let idx = y * imgSize + x
var pixelValue = match (shapeType) {
case 0 => calcCirclePixel(x, y, center, radius, offsetX, offsetY)
case 1 => calcSquarePixel(x, y, center, radius, offsetX, offsetY)
case _ => calcTrianglePixel(x, y, center, radius, offsetX, offsetY)
}
let noise = FP32(Float32(rng.nextFloat64() - 0.5) * 0.05)
pixelValue = pixelValue + noise
if (pixelValue.getValue() > 1.0) {
pixelValue = FP32(1.0)
} else if (pixelValue.getValue() < 0.0) {
pixelValue = FP32(0.0)
}
matrix.set(idx, 0, pixelValue)
}
}
inputs[i] = matrix
}
(inputs, labels)
}
/**
* 可视化一个样本
*/
func showSample(matrix: Matrix1, imgSize: Int64, label: Int64): Unit {
let shapeNames = ["圆形", "正方形", "三角形"]
println("标签: ${shapeNames[label]}")
for (y in 0..imgSize) {
var line = ""
for (x in 0..imgSize) {
let idx = y * imgSize + x
let val = matrix.get(idx, 0).getOrThrow().getValue()
if (val > 0.8) {
line = line + "██"
} else if (val > 0.6) {
line = line + "▓▓"
} else if (val > 0.4) {
line = line + "▒▒"
} else if (val > 0.2) {
line = line + "░░"
} else {
line = line + " "
}
}
println(line)
}
println("")
}
/**
* Softmax 辅助函数
*/
func computeSoftmax(input: Matrix1): Matrix1 {
let cols = input.get_col()
var output = Matrix1(1, cols)
var maxVal = Float32(-1.0e30)
for (c in 0..cols) {
let raw = input.get(0, c).getOrThrow().getValue()
if (raw > maxVal) {
maxVal = raw
}
}
var expSum = Float32(0.0)
for (c in 0..cols) {
let raw = input.get(0, c).getOrThrow().getValue()
let ev = Float32(exp(raw - maxVal))
output.set(0, c, FP32(ev))
expSum = expSum + ev
}
for (c in 0..cols) {
let v = output.get(0, c).getOrThrow().getValue()
output.set(0, c, FP32(v / expSum))
}
output
}
/**
* 获取预测类别
*/
func predictClass(probs: Matrix1): Int64 {
let cols = probs.get_col()
var maxProb = Float32(-1.0)
var predClass = 0
for (c in 0..cols) {
let p = probs.get(0, c).getOrThrow().getValue()
if (p > maxProb) {
maxProb = p
predClass = Int64(c)
}
}
predClass
}
/**
* 打印分隔线
*/
func printLine(): Unit {
println("============================================================")
}
/**
* 主测试函数
*/
func testGeometryCNN(): Unit {
printLine()
println("CNN 几何图形分类测试 (Adam 优化器版本)")
printLine()
println("")
let imgSize = 16
let nTrainSamples = 200
let nTestSamples = 60
let nEpochs = 50
let batchSize = 10
let nClasses = 3
println("配置信息:")
println(" 图像大小: ${imgSize}x${imgSize}")
println(" 训练样本数: ${nTrainSamples}")
println(" 测试样本数: ${nTestSamples}")
println(" 训练轮数: ${nEpochs}")
println(" Batch 大小: ${batchSize}")
println(" 类别数: ${nClasses} (圆形, 正方形, 三角形)")
println(" 优化器: Adam")
println("")
println("网络结构:")
println(" Input(16x16x1)")
println(" -> Conv1(5x5, 1->8) -> Pool1(2x2)")
println(" -> Conv2(3x3, 8->16) -> Pool2(2x2)")
println(" -> Flatten -> FC(64->32) -> FC(32->3)")
println(" -> Softmax")
println("")
let lr = FP32(0.01)
// 构建网络
var conv1 = ConvLayer(1, 8, (5, 5), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
var pool1 = PoolingLayer(8, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
var conv2 = ConvLayer(8, 16, (3, 3), 1, 0, lr, Af.Leaky_ReLU(FP32(0.01)), Option.None)
var pool2 = PoolingLayer(16, (2, 2), 2, 0, "max", Af.ReLU, lr, lr)
var flatten = FlattenLayer(16, 2, 2)
var fc1 = Nn1(64, 32, Float32(0.3), Af.Leaky_ReLU(FP32(0.01)))
var fc2 = Nn1(32, 3, Float32(0.3), Af.Softmax)
// 创建 Adam 优化器
var adamConv1 = Adam(FP32(0.001)) // 卷积层1
var adamConv2 = Adam(FP32(0.001)) // 卷积层2
var adamFC1 = Adam(FP32(0.0005)) // 全连接层1
var adamFC2 = Adam(FP32(0.0005)) // 全连接层2
println("网络创建完成 (Adam优化器: Conv1, Conv2, FC1, FC2)")
println("")
// 生成数据
println("生成训练数据...")
let (trainInputs, trainLabels) = generateGeometryData(nTrainSamples)
println("生成测试数据...")
let (testInputs, testLabels) = generateGeometryData(nTestSamples)
println("数据生成完成")
println("")
// 显示几个样本
println("样本预览:")
for (i in 0..3) {
if (i < nTrainSamples) {
println("样本 ${i + 1}:")
showSample(trainInputs[i], imgSize, trainLabels[i])
}
}
println("开始训练...")
println("")
var bestAccuracy = Float32(0.0)
// 计算卷积输出尺寸
let conv1OutH = imgSize - 4 // 16 - 5 + 1 = 12
let conv1OutW = imgSize - 4
let pool1OutH = conv1OutH / 2 // 6
let pool1OutW = conv1OutW / 2
let conv2OutH = pool1OutH - 2 // 6 - 3 + 1 = 4
let conv2OutW = pool1OutW - 2
let pool2OutH = conv2OutH / 2 // 2
let pool2OutW = conv2OutW / 2
let flatSize = 16 * pool2OutH * pool2OutW // 64
for (epoch in 0..nEpochs) {
var epochLoss = Float32(0.0)
var epochCorrect = 0
// 训练
for (i in 0..nTrainSamples) {
let x = trainInputs[i]
let label = trainLabels[i]
// 前向传播
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
// 转换为全连接层输入
var fc1Input = Matrix1(1, flatSize)
for (j in 0..flatSize) {
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
}
let fc1Out = fc1.forward(fc1Input)
let fc2Out = fc2.forward(fc1Out)
// Softmax
let probs = computeSoftmax(fc2Out)
// 计算损失和准确率
let probVal = probs.get(0, label).getOrThrow().getValue()
let loss = -log(max(probVal, Float32(1e-10)))
epochLoss = epochLoss + loss
let predClass = predictClass(probs)
if (predClass == label) {
epochCorrect += 1
}
// 反向传播
var delta = Matrix1(1, nClasses)
for (c in 0..nClasses) {
let prob = probs.get(0, c).getOrThrow()
if (Int64(c) == label) {
delta.set(0, c, prob - FP32(1.0))
} else {
delta.set(0, c, prob)
}
}
let fc2Delta = fc2.backward(delta)
let fc1Delta = fc1.backward(fc2Delta)
// 转换回特征图格式
var flatDelta = Matrix1(flatSize, 1)
for (j in 0..flatSize) {
flatDelta.set(j, 0, fc1Delta.get(0, j).getOrThrow())
}
let p2Delta = flatten.backward(flatDelta)
let c2Delta = pool2.backward(p2Delta)
let p1Delta = conv2.backward(c2Delta)
let c1Delta = pool1.backward(p1Delta)
let _ = conv1.backward(c1Delta)
// 批次更新 - 使用 Adam
if ((i + 1) % batchSize == 0) {
adamConv1.updateConvLayer(conv1) // Conv1 - Adam
adamConv2.updateConvLayer(conv2) // Conv2 - Adam
adamFC1.updateFCLayer(fc1) // FC1 - Adam
adamFC2.updateFCLayer(fc2) // FC2 - Adam
// 池化层仍使用 SGD
pool1.update(Float32(1.0))
pool2.update(Float32(1.0))
}
}
// 最终更新 - 使用 Adam
adamConv1.updateConvLayer(conv1)
adamConv2.updateConvLayer(conv2)
adamFC1.updateFCLayer(fc1)
adamFC2.updateFCLayer(fc2)
pool1.update(Float32(1.0))
pool2.update(Float32(1.0))
let avgLoss = epochLoss / Float32(nTrainSamples)
let accuracy = Float32(epochCorrect) / Float32(nTrainSamples) * Float32(100.0)
if (accuracy > bestAccuracy) {
bestAccuracy = accuracy
}
// 输出训练信息
if (epoch % 1 == 0 || epoch == nEpochs - 1) {
println("Epoch ${epoch + 1}/${nEpochs} - Loss: ${avgLoss}, Accuracy: ${accuracy}%, Adam Step: ${adamConv1.getStep()}")
}
}
println("")
println("训练完成! 最佳训练准确率: ${bestAccuracy}%")
println("总更新步数: ${adamConv1.getStep()}")
println("")
// 测试
println("开始测试...")
println("")
var testCorrect = 0
var confusion = Array(nClasses, { _ => Array(nClasses, { _ => 0 }) })
var testLoss = Float32(0.0)
for (i in 0..nTestSamples) {
let x = testInputs[i]
let label = testLabels[i]
// 前向传播
let c1Out = conv1.forward(x, (1, imgSize, imgSize, 1))
let p1Out = pool1.forward(c1Out, (1, conv1OutH, conv1OutW, 8))
let c2Out = conv2.forward(p1Out, (1, pool1OutH, pool1OutW, 8))
let p2Out = pool2.forward(c2Out, (1, conv2OutH, conv2OutW, 16))
let flatOut = flatten.forward(p2Out, (1, pool2OutH, pool2OutW, 16))
var fc1Input = Matrix1(1, flatSize)
for (j in 0..flatSize) {
fc1Input.set(0, j, flatOut.get(j, 0).getOrThrow())
}
let fc1Out = fc1.forward(fc1Input)
let fc2Out = fc2.forward(fc1Out)
let probs = computeSoftmax(fc2Out)
// 计算损失
let probVal = probs.get(0, label).getOrThrow().getValue()
testLoss = testLoss + Float32(-log(max(probVal, Float32(1e-10))))
// 预测
let predClass = predictClass(probs)
confusion[label][predClass] += 1
if (predClass == label) {
testCorrect += 1
}
// 显示前几个错误分类的样本
if (predClass != label && testCorrect < 10) {
println("错误分类样本 ${i + 1}:")
showSample(x, imgSize, label)
let shapeNames = ["圆形", "正方形", "三角形"]
println(" 真实标签: ${shapeNames[label]}, 预测标签: ${shapeNames[predClass]}")
println("")
}
}
let testAcc = Float32(testCorrect) / Float32(nTestSamples) * Float32(100.0)
let avgTestLoss = testLoss / Float32(nTestSamples)
println("测试结果:")
println(" 准确率: ${testAcc}% (${testCorrect}/${nTestSamples})")
println(" 平均损失: ${avgTestLoss}")
println("")
let classNames = ["圆形 ", "正方形", "三角形"]
println("各类别准确率:")
for (c in 0..nClasses) {
var total = 0
for (p in 0..nClasses) {
total += confusion[c][p]
}
if (total > 0) {
let acc = Float32(confusion[c][c]) / Float32(total) * Float32(100.0)
println(" ${classNames[c]}: ${confusion[c][c]}/${total} (${acc}%)")
}
}
println("")
println("混淆矩阵:")
println(" 预测圆形 预测正方形 预测三角形")
for (t in 0..nClasses) {
var line = "真实${classNames[t]}:"
for (p in 0..nClasses) {
line = line + " ${confusion[t][p]} "
}
println(line)
}
println("")
printLine()
println("几何图形分类测试完成!")
printLine()
}
+586
View File
@@ -0,0 +1,586 @@
/**
* ads1115_linux.c
*
* Linux C 通过 I2C 采集 ADS1115 示例
* ADS1115: TI 16位 4通道 ΔΣ ADC, 最高 860SPS, I2C 接口
*
* 编译: gcc -o ads1115 ads1115_linux.c
* 运行: ./ads1115
* ./ads1115 0x48 # 指定 I2C 地址
* ./ads1115 0x48 1 2 # 指定地址、PGA、数据速率
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <time.h>
#include <math.h>
// ============================================================
// I2C 配置
// ============================================================
#define I2C_DEVICE "/dev/i2c-1" // I2C 总线 (树莓派是 i2c-1)
#define ADS1115_ADDR 0x48 // ADDR 接 GND 时默认地址
// ============================================================
// ADS1115 寄存器
// ============================================================
#define ADS1115_REG_CONVERSION 0x00 // 转换结果 (只读)
#define ADS1115_REG_CONFIG 0x01 // 配置寄存器 (读/写)
#define ADS1115_REG_LO_THRESH 0x02 // 低阈值比较
#define ADS1115_REG_HI_THRESH 0x03 // 高阈值比较
// ============================================================
// ADS1115 配置寄存器 bit 定义 (16 位)
// ============================================================
// BIT15: OS[0] — 单次转换启动位 (1=开始)
// BIT14-12: MUX[2:0] — 输入多路选择
// BIT11-9: PGA[2:0] — 可编程增益放大器
// BIT8: MODE — 0=连续转换, 1=单次(省电)
// BIT7-5: DR[2:0] — 数据速率
// BIT4: COMP_MODE — 比较模式 (0=传统)
// BIT3: COMP_POL — 比较极性 (0=低有效)
// BIT2: COMP_LAT — 比较锁存 (0=不锁存)
// BIT1-0: COMP_QUE — 比较器队列 (11=禁用)
// --- MUX 输入通道 ---
#define ADS1115_MUX_AIN0_AIN1 0x0000 // 差分: P=AIN0, N=AIN1 (默认)
#define ADS1115_MUX_AIN0_AIN3 0x1000 // 差分: P=AIN0, N=AIN3
#define ADS1115_MUX_AIN1_AIN3 0x2000 // 差分: P=AIN1, N=AIN3
#define ADS1115_MUX_AIN2_AIN3 0x3000 // 差分: P=AIN2, N=AIN3
#define ADS1115_MUX_AIN0_GND 0x4000 // 单端: AIN0
#define ADS1115_MUX_AIN1_GND 0x5000 // 单端: AIN1
#define ADS1115_MUX_AIN2_GND 0x6000 // 单端: AIN2
#define ADS1115_MUX_AIN3_GND 0x7000 // 单端: AIN3
// --- PGA 增益 (满量程范围) ---
#define ADS1115_PGA_6_144V 0x0000 // ±6.144V (1LSB=0.1875mV)
#define ADS1115_PGA_4_096V 0x0200 // ±4.096V (1LSB=0.125mV)
#define ADS1115_PGA_2_048V 0x0400 // ±2.048V (1LSB=0.0625mV) (默认)
#define ADS1115_PGA_1_024V 0x0600 // ±1.024V (1LSB=0.03125mV)
#define ADS1115_PGA_0_512V 0x0800 // ±0.512V (1LSB=0.015625mV)
#define ADS1115_PGA_0_256V 0x0A00 // ±0.256V (1LSB=0.0078125mV)
// --- 数据速率 ---
#define ADS1115_DR_8SPS 0x0000 // 8 SPS
#define ADS1115_DR_16SPS 0x0020 // 16 SPS
#define ADS1115_DR_32SPS 0x0040 // 32 SPS
#define ADS1115_DR_64SPS 0x0060 // 64 SPS
#define ADS1115_DR_128SPS 0x0080 // 128 SPS (默认)
#define ADS1115_DR_250SPS 0x00A0 // 250 SPS
#define ADS1115_DR_475SPS 0x00C0 // 475 SPS
#define ADS1115_DR_860SPS 0x00E0 // 860 SPS
// --- 比较器队列 (COMP_QUE) ---
#define ADS1115_COMP_QUE_1 0x0000 // 1 次后触发
#define ADS1115_COMP_QUE_2 0x0001 // 2 次后触发
#define ADS1115_COMP_QUE_4 0x0002 // 4 次后触发
#define ADS1115_COMP_QUE_DISABLE 0x0003 // 禁用比较器 (默认)
// --- 操作模式 ---
#define ADS1115_MODE_CONTINUOUS 0x0000 // 连续转换
#define ADS1115_MODE_SINGLE 0x0100 // 单次转换 (默认, 省电)
// --- 启动转换 ---
#define ADS1115_OS_START 0x8000 // 启动单次转换
// ============================================================
// 结构体: ADS1115 配置上下文
// ============================================================
typedef struct {
int fd; // I2C 文件描述符
uint8_t addr; // I2C 设备地址
uint16_t config; // 当前配置值
double fs_voltage; // 满量程电压 (取决于 PGA)
} ads1115_t;
// ============================================================
// PGA 对应的满量程电压 (单位: V)
// ============================================================
static const double pga_fs[] = {
[0] = 6.144, // ADS1115_PGA_6_144V
[1] = 4.096, // ADS1115_PGA_4_096V
[2] = 2.048, // ADS1115_PGA_2_048V
[3] = 1.024, // ADS1115_PGA_1_024V
[4] = 0.512, // ADS1115_PGA_0_512V
[5] = 0.256, // ADS1115_PGA_0_256V
};
// ============================================================
// PGA 增益对应的满量程电压名
// ============================================================
static const char *pga_names[] = {
[0] = "±6.144V",
[1] = "±4.096V",
[2] = "±2.048V",
[3] = "±1.024V",
[4] = "±0.512V",
[5] = "±0.256V",
};
// ============================================================
// 数据速率字符串
// ============================================================
static const char *dr_names[] = {
[0] = "8 SPS",
[1] = "16 SPS",
[2] = "32 SPS",
[3] = "64 SPS",
[4] = "128 SPS",
[5] = "250 SPS",
[6] = "475 SPS",
[7] = "860 SPS",
};
// ============================================================
// 毫秒延时
// ============================================================
static void delay_ms(int ms)
{
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000L;
nanosleep(&ts, NULL);
}
// ============================================================
// 微秒延时
// ============================================================
static void delay_us(int us)
{
struct timespec ts;
ts.tv_sec = us / 1000000;
ts.tv_nsec = (us % 1000000) * 1000L;
nanosleep(&ts, NULL);
}
// ============================================================
// I2C 初始化
// ============================================================
static int i2c_init(ads1115_t *dev, const char *i2c_dev, uint8_t addr)
{
dev->fd = open(i2c_dev, O_RDWR);
if (dev->fd < 0) {
perror("打开 I2C 设备失败");
return -1;
}
if (ioctl(dev->fd, I2C_SLAVE, addr) < 0) {
perror("设置 I2C 从机地址失败");
close(dev->fd);
dev->fd = -1;
return -1;
}
dev->addr = addr;
dev->config = 0;
printf("I2C 初始化完成: 设备=%s, 地址=0x%02X\n", i2c_dev, addr);
return 0;
}
// ============================================================
// I2C 写 2 字节 (大端)
// ============================================================
static int i2c_write_word(ads1115_t *dev, uint8_t reg, uint16_t value)
{
uint8_t buf[3];
buf[0] = reg;
buf[1] = (value >> 8) & 0xFF; // 高字节
buf[2] = value & 0xFF; // 低字节
if (write(dev->fd, buf, 3) != 3) {
perror("I2C 写失败");
return -1;
}
return 0;
}
// ============================================================
// I2C 读 2 字节 (大端)
// ============================================================
static int i2c_read_word(ads1115_t *dev, uint8_t reg, uint16_t *value)
{
// 先写入寄存器地址
if (write(dev->fd, &reg, 1) != 1) {
perror("I2C 设置寄存器地址失败");
return -1;
}
// 读取 2 字节
uint8_t buf[2];
if (read(dev->fd, buf, 2) != 2) {
perror("I2C 读失败");
return -1;
}
*value = ((uint16_t)buf[0] << 8) | buf[1];
return 0;
}
// ============================================================
// 写配置寄存器
// ============================================================
static int ads1115_write_config(ads1115_t *dev, uint16_t config)
{
dev->config = config;
return i2c_write_word(dev, ADS1115_REG_CONFIG, config);
}
// ============================================================
// 读配置寄存器
// ============================================================
static int ads1115_read_config(ads1115_t *dev, uint16_t *config)
{
return i2c_read_word(dev, ADS1115_REG_CONFIG, config);
}
// ============================================================
// 读转换结果寄存器
// ============================================================
static int ads1115_read_conversion(ads1115_t *dev, int16_t *value)
{
uint16_t raw;
int ret = i2c_read_word(dev, ADS1115_REG_CONVERSION, &raw);
if (ret == 0) {
*value = (int16_t)raw;
}
return ret;
}
// ============================================================
// 读阈值寄存器
// ============================================================
static int ads1115_read_thresh(ads1115_t *dev, int16_t *lo, int16_t *hi)
{
uint16_t raw_lo, raw_hi;
if (i2c_read_word(dev, ADS1115_REG_LO_THRESH, &raw_lo) < 0) return -1;
if (i2c_read_word(dev, ADS1115_REG_HI_THRESH, &raw_hi) < 0) return -1;
*lo = (int16_t)raw_lo;
*hi = (int16_t)raw_hi;
return 0;
}
// ============================================================
// 写阈值寄存器
// ============================================================
static int ads1115_write_thresh(ads1115_t *dev, int16_t lo, int16_t hi)
{
if (i2c_write_word(dev, ADS1115_REG_LO_THRESH, (uint16_t)lo) < 0) return -1;
if (i2c_write_word(dev, ADS1115_REG_HI_THRESH, (uint16_t)hi) < 0) return -1;
return 0;
}
// ============================================================
// 初始化 ADS1115
//
// pga_idx: 0~5 对应 ADS1115_PGA_xxx
// dr_idx: 0~7 对应 ADS1115_DR_xxx
// ============================================================
static int ads1115_init(ads1115_t *dev, int pga_idx, int dr_idx)
{
if (pga_idx < 0 || pga_idx > 5) pga_idx = 2; // 默认 ±2.048V
if (dr_idx < 0 || dr_idx > 7) dr_idx = 4; // 默认 128SPS
dev->fs_voltage = pga_fs[pga_idx];
// 构建配置:
// - 单次转换模式
// - 禁用比较器
// - 默认通道 AIN0-GND
uint16_t config = ADS1115_MUX_AIN0_GND // 单端 AIN0
| (pga_idx << 9) // PGA
| ADS1115_MODE_SINGLE // 单次模式
| (dr_idx << 5) // 数据速率
| ADS1115_COMP_QUE_DISABLE; // 禁用比较器
int ret = ads1115_write_config(dev, config);
if (ret < 0) return ret;
printf("ADS1115 初始化完成: ");
printf("PGA=%s, 速率=%s, 满量程=%.3fV\n",
pga_names[pga_idx], dr_names[dr_idx], dev->fs_voltage);
return 0;
}
// ============================================================
// 读取单端通道 (单次转换)
// ch: 0~3 对应 AIN0~AIN3
// ============================================================
static double ads1115_read_single(ads1115_t *dev, int ch)
{
if (ch < 0 || ch > 3) {
fprintf(stderr, "通道号无效: %d (有效: 0~3)\n", ch);
return 0.0;
}
// 1. 重写配置: 设置通道 + 启动转换
uint16_t mux = ADS1115_MUX_AIN0_GND | (ch << 12); // AINch-GND
uint16_t config = (dev->config & ~0x7000) // 清除 MUX 位
| mux
| ADS1115_OS_START; // 启动单次转换
ads1115_write_config(dev, config);
// 2. 等待转换完成 (OS 位被硬件清 0)
// 根据数据速率等待足够时间
uint16_t dr_idx = (config >> 5) & 0x07;
int wait_ms = 1000 / (8 << dr_idx) + 1; // 保守估计
delay_ms(wait_ms + 2);
// 3. 读转换值
int16_t raw = 0;
if (ads1115_read_conversion(dev, &raw) < 0) {
return 0.0;
}
// 4. 转换为电压
// ADS1115 16位有符号, 满量程 = FS_VOLTAGE
// V = raw * FS_VOLTAGE / 32768
double voltage = (double)raw * dev->fs_voltage / 32768.0;
return voltage;
}
// ============================================================
// 读取差分通道 (单次转换)
// pos: 正极通道 (0~3)
// neg: 负极通道 (0~3)
// ============================================================
static double ads1115_read_diff(ads1115_t *dev, int pos, int neg)
{
if (pos < 0 || pos > 3 || neg < 0 || neg > 3) {
fprintf(stderr, "通道号无效\n");
return 0.0;
}
// 构建 MUX 值 (差分)
// AIN0-AIN1: 0, AIN0-AIN3: 1, AIN1-AIN3: 2, AIN2-AIN3: 3
uint16_t mux_val;
if (pos == 0 && neg == 1) mux_val = 0;
else if (pos == 0 && neg == 3) mux_val = 1;
else if (pos == 1 && neg == 3) mux_val = 2;
else if (pos == 2 && neg == 3) mux_val = 3;
else {
fprintf(stderr, "不支持的差分组合: AIN%d - AIN%d\n", pos, neg);
fprintf(stderr, "支持: 0-1, 0-3, 1-3, 2-3\n");
return 0.0;
}
uint16_t mux = mux_val << 12;
uint16_t config = (dev->config & ~0x7000)
| mux
| ADS1115_OS_START;
ads1115_write_config(dev, config);
uint16_t dr_idx = (config >> 5) & 0x07;
int wait_ms = 1000 / (8 << dr_idx) + 1;
delay_ms(wait_ms + 2);
int16_t raw = 0;
if (ads1115_read_conversion(dev, &raw) < 0) {
return 0.0;
}
double voltage = (double)raw * dev->fs_voltage / 32768.0;
return voltage;
}
// ============================================================
// 连续模式读数 (需先配置为连续模式)
// ============================================================
static int ads1115_start_continuous(ads1115_t *dev, int ch)
{
if (ch < 0 || ch > 3) return -1;
uint16_t config = (dev->config & ~0x7100) // 清除 MUX+MODE 位
| (ADS1115_MUX_AIN0_GND | (ch << 12)) // 通道
| ADS1115_MODE_CONTINUOUS // 连续模式
| ADS1115_COMP_QUE_DISABLE;
return ads1115_write_config(dev, config);
}
// ============================================================
// 连续模式读数
// ============================================================
static double ads1115_read_continuous(ads1115_t *dev)
{
int16_t raw = 0;
if (ads1115_read_conversion(dev, &raw) < 0) {
return 0.0;
}
return (double)raw * dev->fs_voltage / 32768.0;
}
// ============================================================
// 扫描所有单端通道
// ============================================================
static void ads1115_scan_all(ads1115_t *dev)
{
printf("\n===== 单端通道扫描 =====\n");
const char *ch_names[] = {"AIN0", "AIN1", "AIN2", "AIN3"};
for (int ch = 0; ch < 4; ch++) {
double v = ads1115_read_single(dev, ch);
printf(" %s: %+.6f V\n", ch_names[ch], v);
}
}
// ============================================================
// 打印当前配置
// ============================================================
static void ads1115_print_config(ads1115_t *dev)
{
uint16_t config;
if (ads1115_read_config(dev, &config) < 0) return;
int mux = (config >> 12) & 0x07;
int pga = (config >> 9) & 0x07;
int mode = (config >> 8) & 0x01;
int dr = (config >> 5) & 0x07;
int comp = config & 0x03;
printf("\n===== ADS1115 寄存器 =====\n");
printf(" 配置寄存器: 0x%04X\n", config);
printf(" MUX: %d (", mux);
switch (mux) {
case 0: printf("AIN0-AIN1"); break;
case 1: printf("AIN0-AIN3"); break;
case 2: printf("AIN1-AIN3"); break;
case 3: printf("AIN2-AIN3"); break;
case 4: printf("AIN0-GND"); break;
case 5: printf("AIN1-GND"); break;
case 6: printf("AIN2-GND"); break;
case 7: printf("AIN3-GND"); break;
}
printf(")\n");
printf(" PGA: %d -> %s (满量程 %.3fV)\n", pga,
(pga <= 5) ? pga_names[pga] : "未知",
(pga <= 5) ? pga_fs[pga] : 0);
printf(" 模式: %s\n", mode ? "单次" : "连续");
printf(" 速率: %d -> %s\n", dr, dr_names[dr & 0x07]);
printf(" 比较器: %s\n", comp == 3 ? "禁用" : "启用");
// 读阈值
int16_t lo, hi;
if (ads1115_read_thresh(dev, &lo, &hi) == 0) {
printf(" 阈值: LO=%d (%.6fV), HI=%d (%.6fV)\n",
lo, (double)lo * dev->fs_voltage / 32768.0,
hi, (double)hi * dev->fs_voltage / 32768.0);
}
}
// ============================================================
// 打印转换结果
// ============================================================
static void print_reading(const char *label, double voltage)
{
printf(" %s: %+.6f V (%.3f mV)\n", label, voltage, voltage * 1000.0);
}
// ============================================================
// 打印帮助
// ============================================================
static void print_help(const char *prog)
{
printf("\n用法: %s [I2C地址] [PGA] [数据速率]\n", prog);
printf("\n参数 (可选):\n");
printf(" I2C地址 0x48~0x4B (默认 0x48)\n");
printf(" PGA 0~5 (默认 2 = ±2.048V)\n");
printf(" 0=±6.144V, 1=±4.096V, 2=±2.048V\n");
printf(" 3=±1.024V, 4=±0.512V, 5=±0.256V\n");
printf(" 数据速率 0~7 (默认 4 = 128SPS)\n");
printf(" 0=8, 1=16, 2=32, 3=64\n");
printf(" 4=128, 5=250, 6=475, 7=860 (SPS)\n");
printf("\n示例:\n");
printf(" %s\n", prog);
printf(" %s 0x49\n", prog);
printf(" %s 0x48 2 6\n", prog);
}
// ============================================================
// 主函数
// ============================================================
int main(int argc, char *argv[])
{
ads1115_t dev;
uint8_t addr = ADS1115_ADDR;
int pga = 2; // ±2.048V
int dr = 4; // 128 SPS
// 解析命令行参数
if (argc >= 2) {
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
print_help(argv[0]);
return 0;
}
addr = (uint8_t)strtol(argv[1], NULL, 0);
}
if (argc >= 3) pga = atoi(argv[2]);
if (argc >= 4) dr = atoi(argv[3]);
printf("ADS1115 Linux I2C 采集示例\n");
printf("============================\n\n");
// 1. 初始化 I2C
if (i2c_init(&dev, I2C_DEVICE, addr) < 0) {
return 1;
}
// 2. 初始化 ADS1115
if (ads1115_init(&dev, pga, dr) < 0) {
close(dev.fd);
return 1;
}
// 3. 单通道测试 (AIN0)
printf("\n===== 单通道测试 (AIN0, 连续读 10 次) =====\n");
for (int i = 0; i < 10; i++) {
double v = ads1115_read_single(&dev, 0);
char label[32];
snprintf(label, sizeof(label), "[%d]", i);
print_reading(label, v);
delay_ms(10);
}
// 4. 扫描所有单端通道
ads1115_scan_all(&dev);
// 5. 差分模式示例
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
for (int i = 0; i < 5; i++) {
double v = ads1115_read_diff(&dev, 0, 1);
char label[32];
snprintf(label, sizeof(label), "[%d]", i);
print_reading(label, v);
delay_ms(10);
}
// 6. 连续模式测试
printf("\n===== 连续模式测试 (AIN0, 5 次) =====\n");
if (ads1115_start_continuous(&dev, 0) == 0) {
delay_ms(50); // 等第一次转换完成
for (int i = 0; i < 5; i++) {
double v = ads1115_read_continuous(&dev);
char label[32];
snprintf(label, sizeof(label), "[%d] 连续", i);
print_reading(label, v);
delay_ms(10);
}
} else {
printf(" 启动连续模式失败\n");
}
// 7. 打印配置信息
ads1115_print_config(&dev);
// 8. 清理
close(dev.fd);
printf("\n程序结束.\n");
return 0;
}
+471
View File
@@ -0,0 +1,471 @@
/**
* ads1256_linux.c
*
* Linux C 通过 SPI 采集 ADS1256 示例
* 适用平台: Raspberry Pi / 任何 Linux + SPI 设备
* 编译: gcc -o ads1256 ads1256_linux.c -lwiringPi (树莓派用 wiringPi)
* gcc -o ads1256 ads1256_linux.c (通用 /dev/spidev)
*
* ADS1256: TI 24位 8通道 ΔΣ ADC, 最高 30kSPS
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <time.h>
// ============================================================
// 配置参数 - 按需修改
// ============================================================
#define SPI_DEVICE "/dev/spidev0.0" // SPI 设备路径
#define SPI_SPEED 1920000 // SPI 时钟 (1.92MHz, ADS1256 最大支持 2MHz)
#define SPI_MODE 1 // SPI 模式: CPOL=0, CPHA=1 (模式1)
#define SPI_BITS 8 // 8 位传输
// ADS1256 片选用 GPIO (如果使用硬件 CS 则不需要)
// 若用 /dev/spidev 的硬件 CS, 这部分不需要手动控制
// #define CS_PIN 8 // 例如 wiringPi 的 GPIO 8
// ============================================================
// ADS1256 寄存器地址
// ============================================================
#define ADS1256_REG_STATUS 0x00
#define ADS1256_REG_MUX 0x01
#define ADS1256_REG_ADCON 0x02
#define ADS1256_REG_DRATE 0x03
#define ADS1256_REG_IO 0x04
#define ADS1256_REG_OFC0 0x05
#define ADS1256_REG_OFC1 0x06
#define ADS1256_REG_OFC2 0x07
#define ADS1256_REG_FSC0 0x08
#define ADS1256_REG_FSC1 0x09
#define ADS1256_REG_FSC2 0x0A
// ============================================================
// ADS1256 命令定义
// ============================================================
#define ADS1256_CMD_WAKEUP 0x00 // 退出待机
#define ADS1256_CMD_RDATA 0x01 // 读数据
#define ADS1256_CMD_RDATAC 0x03 // 连续读数据
#define ADS1256_CMD_SDATAC 0x0F // 停止连续读
#define ADS1256_CMD_RREG 0x10 // 读寄存器 (| 寄存器地址)
#define ADS1256_CMD_WREG 0x50 // 写寄存器 (| 寄存器地址)
#define ADS1256_CMD_SELFCAL 0xF0 // 自校准
#define ADS1256_CMD_SELFOCAL 0xF1 // 自失调校准
#define ADS1256_CMD_SELFGCAL 0xF2 // 自增益校准
#define ADS1256_CMD_SYSOCAL 0xF3 // 系统失调校准
#define ADS1256_CMD_SYSGCAL 0xF4 // 系统增益校准
#define ADS1256_CMD_SYNC 0xFC // 同步
#define ADS1256_CMD_STANDBY 0xFD // 待机
#define ADS1256_CMD_RESET 0xFE // 复位
// ============================================================
// ADS1256 数据速率 (DRATE 寄存器值)
// ============================================================
#define ADS1256_DRATE_30000 0xF0 // 30,000 SPS
#define ADS1256_DRATE_15000 0xE0 // 15,000 SPS
#define ADS1256_DRATE_7500 0xD0 // 7,500 SPS
#define ADS1256_DRATE_3750 0xC0 // 3,750 SPS
#define ADS1256_DRATE_2000 0xB0 // 2,000 SPS
#define ADS1256_DRATE_1000 0xA0 // 1,000 SPS
#define ADS1256_DRATE_500 0x90 // 500 SPS
#define ADS1256_DRATE_100 0x80 // 100 SPS
#define ADS1256_DRATE_60 0x70 // 60 SPS
#define ADS1256_DRATE_50 0x60 // 50 SPS
#define ADS1256_DRATE_30 0x50 // 30 SPS
#define ADS1256_DRATE_25 0x40 // 25 SPS
#define ADS1256_DRATE_15 0x30 // 15 SPS
#define ADS1256_DRATE_10 0x20 // 10 SPS
#define ADS1256_DRATE_5 0x10 // 5 SPS
// ============================================================
// PGA 增益 (ADCON 寄存器的低 3 位)
// ============================================================
#define ADS1256_PGA_1 0x00 // ±5V
#define ADS1256_PGA_2 0x01 // ±2.5V
#define ADS1256_PGA_4 0x02 // ±1.25V
#define ADS1256_PGA_8 0x03 // ±0.625V
#define ADS1256_PGA_16 0x04 // ±0.3125V
#define ADS1256_PGA_32 0x05 // ±0.15625V
#define ADS1256_PGA_64 0x06 // ±0.078125V
// 参考电压 (V)
#define ADS1256_VREF 2.5
// ============================================================
// 全局变量
// ============================================================
static int spi_fd = -1; // SPI 文件描述符
static uint8_t spi_txbuf[32];
static uint8_t spi_rxbuf[32];
// ============================================================
// SPI 初始化
// ============================================================
int spi_init(const char *device, uint32_t speed, uint8_t mode, uint8_t bits)
{
int ret;
spi_fd = open(device, O_RDWR);
if (spi_fd < 0) {
perror("打开 SPI 设备失败");
return -1;
}
// 设置 SPI 模式
ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
if (ret < 0) {
perror("设置 SPI 模式失败");
close(spi_fd);
return -1;
}
// 设置位数
ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
if (ret < 0) {
perror("设置 SPI 位数失败");
close(spi_fd);
return -1;
}
// 设置速度
ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
if (ret < 0) {
perror("设置 SPI 速度失败");
close(spi_fd);
return -1;
}
// 读取并验证设置
uint8_t rb_mode, rb_bits;
uint32_t rb_speed;
ioctl(spi_fd, SPI_IOC_RD_MODE, &rb_mode);
ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &rb_bits);
ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &rb_speed);
printf("SPI 初始化完成: 模式=%d, 位数=%d, 速度=%d Hz\n",
rb_mode, rb_bits, rb_speed);
return 0;
}
// ============================================================
// SPI 收发 (全双工)
// ============================================================
int spi_transfer(uint8_t *tx, uint8_t *rx, int len)
{
struct spi_ioc_transfer tr = {
.tx_buf = (unsigned long)tx,
.rx_buf = (unsigned long)rx,
.len = (uint32_t)len,
.delay_usecs = 0,
.speed_hz = SPI_SPEED,
.bits_per_word = SPI_BITS,
.cs_change = 0,
};
int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr);
if (ret < 0) {
perror("SPI 传输失败");
}
return ret;
}
// ============================================================
// SPI 发送单字节
// ============================================================
static inline void spi_write_byte(uint8_t byte)
{
spi_txbuf[0] = byte;
spi_transfer(spi_txbuf, spi_rxbuf, 1);
}
// ============================================================
// SPI 读单字节
// ============================================================
static inline uint8_t spi_read_byte(void)
{
spi_txbuf[0] = 0xFF; // MOSI 发 0xFF 以产生时钟
spi_transfer(spi_txbuf, spi_rxbuf, 1);
return spi_rxbuf[0];
}
// ============================================================
// 等待 DRDY 引脚变低 (数据就绪)
//
// 注意: 这里用延时模拟等待,实际应接 GPIO 中断或轮询
// 若 DRDY 接了 GPIO (如 wiringPi 的 pin 25),可改为:
// while(digitalRead(DRDY_PIN));
// ============================================================
void wait_drdy(void)
{
// 实际项目中用 GPIO 轮询更准确
// 这里用延时估算
usleep(100); // 100us,根据采样率调整
}
// ============================================================
// ADS1256 复位
// ============================================================
void ads1256_reset(void)
{
spi_write_byte(ADS1256_CMD_RESET);
usleep(1000); // 等待复位完成
spi_write_byte(ADS1256_CMD_WAKEUP); // 退出待机
usleep(1000);
}
// ============================================================
// 写 ADS1256 寄存器
// ============================================================
void ads1256_write_reg(uint8_t reg, uint8_t value)
{
spi_write_byte(ADS1256_CMD_WREG | reg);
spi_write_byte(0x00); // 后续字节数 - 1 (0 = 1 byte)
spi_write_byte(value);
}
// ============================================================
// 读 ADS1256 寄存器
// ============================================================
uint8_t ads1256_read_reg(uint8_t reg)
{
spi_write_byte(ADS1256_CMD_RREG | reg);
spi_write_byte(0x00); // 后续字节数 - 1
return spi_read_byte();
}
// ============================================================
// 设置输入通道 (MUX 寄存器)
//
// mux:
// 单端模式: POS = channel, NEG = 0x08 (AINCOM)
// 差分模式: POS = channel_p, NEG = channel_n
// ============================================================
void ads1256_set_channel(uint8_t pos, uint8_t neg)
{
uint8_t mux = ((pos & 0x07) << 4) | (neg & 0x07);
ads1256_write_reg(ADS1256_REG_MUX, mux);
usleep(1);
}
// ============================================================
// ADS1256 初始化
// ============================================================
int ads1256_init(void)
{
printf("正在初始化 ADS1256...\n");
// 1. 复位 ADS1256
ads1256_reset();
usleep(10000);
// 2. 配置状态寄存器
// BIT7: ID (只读)
// BIT6: ID (只读)
// BIT5: ORDER = 0 (MSB 优先)
// BIT4: ACAL = 1 (自动校准)
// BIT3: BUFEN = 0 (关闭缓冲)
// BIT2: DRB = 0 (DRDY 低有效)
// BIT1-0: 保留
ads1256_write_reg(ADS1256_REG_STATUS, 0x14); // 0b00010100
// 3. 配置 MUX (默认: AIN0 + AINCOM)
ads1256_set_channel(0, 0x08);
// 4. 配置 ADCON (A/D 控制寄存器)
// BIT7-5: CLK1-0 = 00 (时钟输出关), SDCS = 0
// BIT4-3: 保留
// BIT2-0: PGA = 001 (增益 ×2)
ads1256_write_reg(ADS1256_REG_ADCON, ADS1256_PGA_2);
// 5. 设置数据速率 (1000 SPS)
ads1256_write_reg(ADS1256_REG_DRATE, ADS1256_DRATE_1000);
// 6. 自校准
printf("正在进行自校准...\n");
spi_write_byte(ADS1256_CMD_SELFCAL);
// 等待校准完成 (约 400ms @ 1000SPS)
usleep(500000);
printf("ADS1256 初始化完成!\n");
// 读寄存器验证
uint8_t status = ads1256_read_reg(ADS1256_REG_STATUS);
uint8_t mux = ads1256_read_reg(ADS1256_REG_MUX);
uint8_t adcon = ads1256_read_reg(ADS1256_REG_ADCON);
uint8_t drate = ads1256_read_reg(ADS1256_REG_DRATE);
printf("寄存器状态: STATUS=0x%02X, MUX=0x%02X, ADCON=0x%02X, DRATE=0x%02X\n",
status, mux, adcon, drate);
return 0;
}
// ============================================================
// 单次读取 24 位数据
//
// 返回: 24 位有符号整数 (二进制补码)
// ============================================================
int32_t ads1256_read_data(void)
{
uint8_t buf[3];
// 发送 RDATA 命令
wait_drdy();
spi_write_byte(ADS1256_CMD_RDATA);
// 读 3 字节 (24 位)
buf[0] = spi_read_byte();
buf[1] = spi_read_byte();
buf[2] = spi_read_byte();
// 组合成 32 位有符号数 (符号扩展)
int32_t value;
value = ((int32_t)buf[0] << 16) |
((int32_t)buf[1] << 8) |
((int32_t)buf[2]);
// 24 位有符号数符号扩展
if (value & 0x800000) {
value |= 0xFF000000;
}
return value;
}
// ============================================================
// 将原始值转换为电压
// ============================================================
double ads1256_raw_to_voltage(int32_t raw, uint8_t pga)
{
// ADS1256 满量程 = ±VREF / PGA
// 24 位: -2^23 ~ 2^23-1
double voltage = (double)raw * ADS1256_VREF / (double)(1 << 23) / (double)pga;
return voltage;
}
// ============================================================
// 读取指定单端通道
// ============================================================
int32_t ads1256_read_channel(uint8_t ch)
{
ads1256_set_channel(ch, 0x08); // AINch vs AINCOM
usleep(1);
// 发送 SYNC + WAKEUP 以启动转换
spi_write_byte(ADS1256_CMD_SYNC);
usleep(1);
spi_write_byte(ADS1256_CMD_WAKEUP);
// 等待转换完成 (根据采样率调整)
// 1000 SPS -> 约 1ms
usleep(2000);
return ads1256_read_data();
}
// ============================================================
// 读取指定差分通道
// ============================================================
int32_t ads1256_read_diff(uint8_t pos, uint8_t neg)
{
ads1256_set_channel(pos, neg);
usleep(1);
spi_write_byte(ADS1256_CMD_SYNC);
usleep(1);
spi_write_byte(ADS1256_CMD_WAKEUP);
usleep(2000);
return ads1256_read_data();
}
// ============================================================
// 连续扫描所有 8 个单端通道
// ============================================================
void ads1256_scan_all(void)
{
printf("\n===== 多通道扫描 =====\n");
for (int ch = 0; ch < 8; ch++) {
int32_t raw = ads1256_read_channel(ch);
double volts = ads1256_raw_to_voltage(raw, 2); // PGA=2
printf("通道 AIN%d: raw = %7d, 电压 = %+.6f V\n", ch, raw, volts);
}
}
// ============================================================
// SPI 关闭
// ============================================================
void spi_close(void)
{
if (spi_fd >= 0) {
close(spi_fd);
spi_fd = -1;
}
}
// ============================================================
// 毫秒延时
// ============================================================
void delay_ms(int ms)
{
struct timespec ts;
ts.tv_sec = ms / 1000;
ts.tv_nsec = (ms % 1000) * 1000000L;
nanosleep(&ts, NULL);
}
// ============================================================
// 主函数
// ============================================================
int main(int argc, char *argv[])
{
printf("ADS1256 Linux SPI 采集示例\n");
printf("===========================\n\n");
// 1. 初始化 SPI
if (spi_init(SPI_DEVICE, SPI_SPEED, SPI_MODE, SPI_BITS) < 0) {
return 1;
}
// 2. 初始化 ADS1256
if (ads1256_init() < 0) {
spi_close();
return 1;
}
printf("\n===== 单通道测试 (AIN0) =====\n");
for (int i = 0; i < 10; i++) {
int32_t raw = ads1256_read_channel(0);
double volts = ads1256_raw_to_voltage(raw, 2);
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
delay_ms(10);
}
// 3. 扫描所有通道
ads1256_scan_all();
// 4. 差分模式示例 (AIN0 - AIN1)
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
for (int i = 0; i < 5; i++) {
int32_t raw = ads1256_read_diff(0, 1);
double volts = ads1256_raw_to_voltage(raw, 2);
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
delay_ms(10);
}
// 5. 清理
spi_close();
printf("\n程序结束.\n");
return 0;
}
+590
View File
@@ -0,0 +1,590 @@
#!/usr/bin/env python3
"""
图片标注工具
用于手动标注图像中的目标区域并保存位置坐标(百分比格式)到txt文件
支持矩形框标注、预览、编辑和删除功能
"""
import os
import sys
import json
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, simpledialog
from PIL import Image, ImageTk, ImageDraw
import cv2
import numpy as np
from datetime import datetime
from typing import List, Optional, Tuple
import threading
class ImageAnnotationTool:
"""图片标注工具 - 支持手动矩形框标注"""
def __init__(self, root):
self.root = root
self.root.title("图片标注工具")
self.root.geometry("1200x800")
# 数据存储
self.current_image_path = None
self.current_image = None
self.image_pil = None
self.image_tk = None
self.zoom_factor = 1.0
self.image_display = None
self.annotations = [] # 存储标注:[(x1, y1, x2, y2), ...] # 移除了label字段
# 标注状态
self.drawing = False
self.start_x = None
self.start_y = None
self.current_rect = None
self.selected_annotation = -1 # 当前选中的标注索引
self.drag_mode = False # 是否在拖拽模式
self.drag_start = None # 拖拽起始点
# 缩放和平移
self.offset_x = 0
self.offset_y = 0
self.pan_start_x = 0
self.pan_start_y = 0
self.panning = False
# 创建UI
self.setup_ui()
# 绑定事件
self.setup_bindings()
# 更新状态
self.update_status()
def setup_ui(self):
"""设置用户界面"""
# 创建主框架
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 左侧控制面板
control_frame = ttk.Frame(main_frame, width=250)
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 5))
control_frame.pack_propagate(False)
# 文件操作
file_group = ttk.LabelFrame(control_frame, text="文件操作", padding=5)
file_group.pack(fill=tk.X, pady=(0, 10))
ttk.Button(file_group, text="打开图片", command=self.open_image).pack(fill=tk.X, pady=2)
ttk.Button(file_group, text="保存标注", command=self.save_annotations).pack(fill=tk.X, pady=2)
ttk.Button(file_group, text="加载标注", command=self.load_annotations).pack(fill=tk.X, pady=2)
# 标注操作
anno_group = ttk.LabelFrame(control_frame, text="标注操作", padding=5)
anno_group.pack(fill=tk.X, pady=(0, 10))
ttk.Button(anno_group, text="清除当前标注", command=self.clear_current_annotation).pack(fill=tk.X, pady=2)
ttk.Button(anno_group, text="删除所有标注", command=self.clear_all_annotations).pack(fill=tk.X, pady=2)
ttk.Button(anno_group, text="删除选中标注", command=self.delete_selected_annotation).pack(fill=tk.X, pady=2)
# 标注列表
list_group = ttk.LabelFrame(control_frame, text="标注列表", padding=5)
list_group.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
# 创建列表框和滚动条
list_frame = ttk.Frame(list_group)
list_frame.pack(fill=tk.BOTH, expand=True)
self.annotation_listbox = tk.Listbox(list_frame, height=15)
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.annotation_listbox.yview)
self.annotation_listbox.configure(yscrollcommand=scrollbar.set)
self.annotation_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.annotation_listbox.bind('<<ListboxSelect>>', self.on_annotation_select)
# 信息显示
info_group = ttk.LabelFrame(control_frame, text="信息", padding=5)
info_group.pack(fill=tk.X)
self.info_label = ttk.Label(info_group, text="未加载图片", wraplength=220)
self.info_label.pack(fill=tk.X, pady=2)
self.status_label = ttk.Label(info_group, text="状态: 就绪", wraplength=220)
self.status_label.pack(fill=tk.X, pady=2)
# 右侧画布区域
canvas_frame = ttk.Frame(main_frame)
canvas_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# 工具栏
toolbar = ttk.Frame(canvas_frame)
toolbar.pack(fill=tk.X, pady=(0, 5))
ttk.Button(toolbar, text="放大", command=self.zoom_in).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar, text="缩小", command=self.zoom_out).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar, text="适应窗口", command=self.fit_to_window).pack(side=tk.LEFT, padx=2)
ttk.Button(toolbar, text="重置视图", command=self.reset_view).pack(side=tk.LEFT, padx=2)
self.draw_mode_var = tk.BooleanVar(value=True)
ttk.Checkbutton(toolbar, text="绘制模式", variable=self.draw_mode_var).pack(side=tk.LEFT, padx=5)
# 画布
self.canvas = tk.Canvas(canvas_frame, bg='gray', cursor='cross')
self.canvas.pack(fill=tk.BOTH, expand=True)
def setup_bindings(self):
"""设置事件绑定"""
self.canvas.bind('<ButtonPress-1>', self.on_mouse_down)
self.canvas.bind('<B1-Motion>', self.on_mouse_move)
self.canvas.bind('<ButtonRelease-1>', self.on_mouse_up)
self.canvas.bind('<Control-ButtonPress-1>', self.on_pan_start)
self.canvas.bind('<Control-B1-Motion>', self.on_pan_move)
self.canvas.bind('<Control-ButtonRelease-1>', self.on_pan_end)
self.root.bind('<Delete>', lambda e: self.delete_selected_annotation())
self.root.bind('<Control-s>', lambda e: self.save_annotations())
self.root.bind('<Control-o>', lambda e: self.open_image())
def open_image(self):
"""打开图片文件"""
file_path = filedialog.askopenfilename(
title="选择图片文件",
filetypes=[
("图片文件", "*.jpg *.jpeg *.png *.bmp *.tiff"),
("所有文件", "*.*")
]
)
if not file_path:
return
try:
# 加载图片
self.current_image_path = file_path
self.image_pil = Image.open(file_path)
# 重置状态
self.annotations = []
self.selected_annotation = -1
self.zoom_factor = 1.0
self.offset_x = 0
self.offset_y = 0
# 适应窗口
self.fit_to_window()
# 更新标注列表
self.update_annotation_list()
# 更新信息
self.update_info()
self.update_status("图片已加载")
except Exception as e:
messagebox.showerror("错误", f"无法加载图片: {e}")
def fit_to_window(self):
"""调整图片适应窗口"""
if not self.image_pil:
return
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
if canvas_width <= 1 or canvas_height <= 1:
canvas_width = 800
canvas_height = 600
img_width, img_height = self.image_pil.size
# 计算缩放比例
scale_x = canvas_width / img_width
scale_y = canvas_height / img_height
self.zoom_factor = min(scale_x, scale_y)
# 计算偏移量使图片居中
scaled_width = img_width * self.zoom_factor
scaled_height = img_height * self.zoom_factor
self.offset_x = (canvas_width - scaled_width) / 2
self.offset_y = (canvas_height - scaled_height) / 2
self.display_image()
def reset_view(self):
"""重置视图"""
self.zoom_factor = 1.0
self.offset_x = 0
self.offset_y = 0
self.fit_to_window()
def zoom_in(self):
"""放大"""
self.zoom_factor *= 1.2
self.display_image()
def zoom_out(self):
"""缩小"""
self.zoom_factor /= 1.2
self.display_image()
def display_image(self):
"""显示图片"""
if not self.image_pil:
return
# 缩放图片
img_width, img_height = self.image_pil.size
new_width = int(img_width * self.zoom_factor)
new_height = int(img_height * self.zoom_factor)
scaled_image = self.image_pil.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 转换为PhotoImage
self.image_tk = ImageTk.PhotoImage(scaled_image)
# 清除画布
self.canvas.delete("all")
# 显示图片
self.canvas.create_image(self.offset_x, self.offset_y, anchor=tk.NW, image=self.image_tk)
# 绘制标注
self.draw_annotations()
def draw_annotations(self):
"""绘制所有标注"""
if not self.image_pil:
return
img_width, img_height = self.image_pil.size
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
# 转换坐标
canvas_x1 = x1 * self.zoom_factor + self.offset_x
canvas_y1 = y1 * self.zoom_factor + self.offset_y
canvas_x2 = x2 * self.zoom_factor + self.offset_x
canvas_y2 = y2 * self.zoom_factor + self.offset_y
# 确定颜色
if idx == self.selected_annotation:
color = "red"
width = 3
else:
color = "blue"
width = 2
# 绘制矩形
self.canvas.create_rectangle(canvas_x1, canvas_y1, canvas_x2, canvas_y2,
outline=color, width=width, tags=f"rect_{idx}")
# 绘制序号
self.canvas.create_text(canvas_x1 + 5, canvas_y1 + 15,
text=str(idx + 1), anchor=tk.NW,
fill=color, font=("Arial", 10, "bold"),
tags=f"label_{idx}")
def canvas_to_image_coords(self, canvas_x, canvas_y):
"""将画布坐标转换为图片坐标"""
img_x = (canvas_x - self.offset_x) / self.zoom_factor
img_y = (canvas_y - self.offset_y) / self.zoom_factor
return img_x, img_y
def on_mouse_down(self, event):
"""鼠标按下事件"""
if self.draw_mode_var.get():
# 绘制模式
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
if 0 <= img_x <= self.image_pil.size[0] and 0 <= img_y <= self.image_pil.size[1]:
self.drawing = True
self.start_x = img_x
self.start_y = img_y
else:
# 选择模式 - 查找最近的标注
self.find_nearest_annotation(event.x, event.y)
def find_nearest_annotation(self, canvas_x, canvas_y):
"""查找最近的标注"""
if not self.annotations:
return
min_dist = float('inf')
nearest_idx = -1
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
# 转换坐标
cx1 = x1 * self.zoom_factor + self.offset_x
cy1 = y1 * self.zoom_factor + self.offset_y
cx2 = x2 * self.zoom_factor + self.offset_x
cy2 = y2 * self.zoom_factor + self.offset_y
# 计算到矩形边界的距离
if cx1 <= canvas_x <= cx2 and cy1 <= canvas_y <= cy2:
# 点在矩形内
dist = 0
else:
# 计算到最近边的距离
dx = max(cx1 - canvas_x, 0, canvas_x - cx2)
dy = max(cy1 - canvas_y, 0, canvas_y - cy2)
dist = (dx**2 + dy**2)**0.5
if dist < min_dist:
min_dist = dist
nearest_idx = idx
# 如果距离足够近,选中该标注
if min_dist < 20: # 20像素阈值
self.selected_annotation = nearest_idx
self.annotation_listbox.selection_clear(0, tk.END)
self.annotation_listbox.selection_set(nearest_idx)
self.annotation_listbox.see(nearest_idx)
self.display_image()
self.update_status(f"选中标注 #{nearest_idx + 1}")
def on_mouse_move(self, event):
"""鼠标移动事件"""
if self.drawing:
# 绘制临时矩形
if self.current_rect:
self.canvas.delete(self.current_rect)
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
canvas_x1 = self.start_x * self.zoom_factor + self.offset_x
canvas_y1 = self.start_y * self.zoom_factor + self.offset_y
canvas_x2 = img_x * self.zoom_factor + self.offset_x
canvas_y2 = img_y * self.zoom_factor + self.offset_y
self.current_rect = self.canvas.create_rectangle(
canvas_x1, canvas_y1, canvas_x2, canvas_y2,
outline="green", width=2, dash=(5, 5)
)
def on_mouse_up(self, event):
"""鼠标释放事件"""
if self.drawing:
# 完成绘制
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
# 确保坐标正确排序
x1 = min(self.start_x, img_x)
y1 = min(self.start_y, img_y)
x2 = max(self.start_x, img_x)
y2 = max(self.start_y, img_y)
# 检查矩形大小
if x2 - x1 > 5 and y2 - y1 > 5:
# 添加标注(只保存坐标,不保存标签)
self.annotations.append((x1, y1, x2, y2))
self.update_annotation_list()
self.display_image()
self.update_status(f"已添加标注 #{len(self.annotations)}")
else:
self.update_status("标注太小,已忽略")
# 清理绘制状态
self.drawing = False
if self.current_rect:
self.canvas.delete(self.current_rect)
self.current_rect = None
def on_pan_start(self, event):
"""开始平移"""
self.panning = True
self.pan_start_x = event.x
self.pan_start_y = event.y
def on_pan_move(self, event):
"""平移移动"""
if self.panning:
dx = event.x - self.pan_start_x
dy = event.y - self.pan_start_y
self.offset_x += dx
self.offset_y += dy
self.pan_start_x = event.x
self.pan_start_y = event.y
self.display_image()
def on_pan_end(self, event):
"""结束平移"""
self.panning = False
def update_annotation_list(self):
"""更新标注列表"""
self.annotation_listbox.delete(0, tk.END)
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
width = x2 - x1
height = y2 - y1
list_text = f"{idx + 1}. 矩形 ({width:.0f}x{height:.0f})"
self.annotation_listbox.insert(tk.END, list_text)
def on_annotation_select(self, event):
"""标注列表选择事件"""
selection = self.annotation_listbox.curselection()
if selection:
self.selected_annotation = selection[0]
self.display_image()
self.update_status(f"已选中标注 #{self.selected_annotation + 1}")
def delete_selected_annotation(self):
"""删除选中的标注"""
if self.selected_annotation >= 0 and self.selected_annotation < len(self.annotations):
del self.annotations[self.selected_annotation]
self.selected_annotation = -1
self.update_annotation_list()
self.display_image()
self.update_status("已删除选中标注")
def clear_current_annotation(self):
"""清除当前标注"""
self.selected_annotation = -1
self.display_image()
self.update_status("已清除选中状态")
def clear_all_annotations(self):
"""删除所有标注"""
if messagebox.askyesno("确认", "确定要删除所有标注吗?"):
self.annotations = []
self.selected_annotation = -1
self.update_annotation_list()
self.display_image()
self.update_status("已删除所有标注")
def save_annotations(self):
"""保存标注到文件(仅TXT格式,纯坐标,格式:top right bottom left)"""
if not self.current_image_path:
messagebox.showwarning("警告", "请先加载图片")
return
if not self.annotations:
if not messagebox.askyesno("确认", "没有标注要保存,是否继续?"):
return
try:
# 获取图像尺寸
img_width, img_height = self.image_pil.size
# 生成保存路径:图片文件名.txt(同目录下)
image_dir = os.path.dirname(self.current_image_path)
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
file_path = os.path.join(image_dir, f"{image_basename}.txt")
# 保存标注(纯坐标格式,top right bottom left)
with open(file_path, 'w', encoding='utf-8') as f:
for x1, y1, x2, y2 in self.annotations:
# 转换为百分比坐标(top, right, bottom, left)
top_pct = y1 / img_height
right_pct = x2 / img_width
bottom_pct = y2 / img_height
left_pct = x1 / img_width
# 写入坐标数据(保留6位小数,空格分隔)
f.write(f"{top_pct:.6f} {right_pct:.6f} {bottom_pct:.6f} {left_pct:.6f}\n")
self.update_status(f"已保存 {len(self.annotations)} 个标注到 {os.path.basename(file_path)}")
messagebox.showinfo("成功", f"标注已保存到:\n{file_path}\n\n格式: top right bottom left (百分比坐标)")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {e}")
def load_annotations(self):
"""加载标注文件(TXT格式,纯坐标,格式:top right bottom left)"""
if not self.current_image_path:
messagebox.showwarning("警告", "请先加载图片")
return
# 默认加载同目录下的同名txt文件
image_dir = os.path.dirname(self.current_image_path)
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
default_file = os.path.join(image_dir, f"{image_basename}.txt")
if os.path.exists(default_file):
file_path = default_file
else:
file_path = filedialog.askopenfilename(
title="选择标注文件",
initialdir=image_dir,
filetypes=[
("文本文件", "*.txt"),
("所有文件", "*.*")
]
)
if not file_path:
return
try:
self.load_txt_annotations(file_path)
self.update_annotation_list()
self.display_image()
self.update_status(f"已加载 {len(self.annotations)} 个标注")
except Exception as e:
messagebox.showerror("错误", f"加载失败: {e}")
def load_txt_annotations(self, file_path):
"""加载TXT格式的标注文件(纯坐标格式,top right bottom left)"""
self.annotations = []
img_width, img_height = self.image_pil.size
with open(file_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
parts = line.split()
if len(parts) == 4:
try:
top_pct = float(parts[0])
right_pct = float(parts[1])
bottom_pct = float(parts[2])
left_pct = float(parts[3])
# 验证百分比坐标范围
if all(0 <= v <= 1 for v in [top_pct, right_pct, bottom_pct, left_pct]):
# 转换为绝对坐标
x1 = left_pct * img_width
y1 = top_pct * img_height
x2 = right_pct * img_width
y2 = bottom_pct * img_height
self.annotations.append((x1, y1, x2, y2))
else:
print(f"警告: 第{line_num}行坐标超出范围 [0,1]: {line}")
except ValueError as e:
print(f"警告: 第{line_num}行格式错误: {line}, 错误: {e}")
else:
print(f"警告: 第{line_num}行格式错误,需要4个坐标值: {line}")
def update_info(self):
"""更新信息显示"""
if self.current_image_path and self.image_pil:
img_width, img_height = self.image_pil.size
filename = os.path.basename(self.current_image_path)
self.info_label.config(text=f"图片: {filename}\n尺寸: {img_width}x{img_height}\n标注: {len(self.annotations)}个")
else:
self.info_label.config(text="未加载图片")
def update_status(self, message=None):
"""更新状态栏"""
if message:
self.status_label.config(text=f"状态: {message}")
else:
self.status_label.config(text="状态: 就绪")
def update(self):
"""更新界面"""
self.update_info()
self.root.after(1000, self.update)
def main():
"""主函数"""
root = tk.Tk()
app = ImageAnnotationTool(root)
app.update()
root.mainloop()
if __name__ == "__main__":
main()
+82
View File
@@ -0,0 +1,82 @@
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main() {
printf("========== Lua Type Constants ==========\n");
printf("LUA_TNONE = %d\n", LUA_TNONE);
printf("LUA_TNIL = %d\n", LUA_TNIL);
printf("LUA_TBOOLEAN = %d\n", LUA_TBOOLEAN);
printf("LUA_TLIGHTUSERDATA = %d\n", LUA_TLIGHTUSERDATA);
printf("LUA_TNUMBER = %d\n", LUA_TNUMBER);
printf("LUA_TSTRING = %d\n", LUA_TSTRING);
printf("LUA_TTABLE = %d\n", LUA_TTABLE);
printf("LUA_TFUNCTION = %d\n", LUA_TFUNCTION);
printf("LUA_TUSERDATA = %d\n", LUA_TUSERDATA);
printf("LUA_TTHREAD = %d\n", LUA_TTHREAD);
printf("\n========== Testing lua_type() ==========\n");
// 创建 Lua 状态机
lua_State *L = luaL_newstate();
if (L == NULL) {
printf("Failed to create Lua state\n");
return 1;
}
// 测试各种类型压栈后的 lua_type 返回值
printf("\n1. 压入 nil:\n");
lua_pushnil(L);
printf(" lua_type(L, -1) = %d (应为 LUA_TNIL = %d)\n", lua_type(L, -1), LUA_TNIL);
lua_pop(L, 1);
printf("\n2. 压入 boolean (true):\n");
lua_pushboolean(L, 1);
printf(" lua_type(L, -1) = %d (应为 LUA_TBOOLEAN = %d)\n", lua_type(L, -1), LUA_TBOOLEAN);
lua_pop(L, 1);
printf("\n3. 压入 number:\n");
lua_pushnumber(L, 3.14159);
printf(" lua_type(L, -1) = %d (应为 LUA_TNUMBER = %d)\n", lua_type(L, -1), LUA_TNUMBER);
lua_pop(L, 1);
printf("\n4. 压入 string:\n");
lua_pushstring(L, "hello");
printf(" lua_type(L, -1) = %d (应为 LUA_TSTRING = %d)\n", lua_type(L, -1), LUA_TSTRING);
lua_pop(L, 1);
printf("\n5. 创建并压入 table:\n");
lua_newtable(L);
printf(" lua_type(L, -1) = %d (应为 LUA_TTABLE = %d)\n", lua_type(L, -1), LUA_TTABLE);
lua_pop(L, 1);
printf("\n6. 压入函数 (C 函数):\n");
lua_pushcfunction(L, lua_gettop);
printf(" lua_type(L, -1) = %d (应为 LUA_TFUNCTION = %d)\n", lua_type(L, -1), LUA_TFUNCTION);
lua_pop(L, 1);
// 测试无效索引
printf("\n7. 测试无效索引:\n");
printf(" lua_type(L, 100) = %d (应为 LUA_TNONE = %d)\n", lua_type(L, 100), LUA_TNONE);
// 测试 lua_typename 函数
printf("\n========== Testing lua_typename() ==========\n");
printf("LUA_TTABLE 的类型名称: \"%s\"\n", lua_typename(L, LUA_TTABLE));
printf("所有类型常量对应的名称:\n");
printf(" LUA_TNONE -> \"%s\"\n", lua_typename(L, LUA_TNONE));
printf(" LUA_TNIL -> \"%s\"\n", lua_typename(L, LUA_TNIL));
printf(" LUA_TBOOLEAN -> \"%s\"\n", lua_typename(L, LUA_TBOOLEAN));
printf(" LUA_TNUMBER -> \"%s\"\n", lua_typename(L, LUA_TNUMBER));
printf(" LUA_TSTRING -> \"%s\"\n", lua_typename(L, LUA_TSTRING));
printf(" LUA_TTABLE -> \"%s\"\n", lua_typename(L, LUA_TTABLE));
printf(" LUA_TFUNCTION -> \"%s\"\n", lua_typename(L, LUA_TFUNCTION));
printf(" LUA_TUSERDATA -> \"%s\"\n", lua_typename(L, LUA_TUSERDATA));
printf(" LUA_TTHREAD -> \"%s\"\n", lua_typename(L, LUA_TTHREAD));
// 关闭 Lua 状态机
lua_close(L);
return 0;
}
+21
View File
@@ -0,0 +1,21 @@
#include <stdio.h>
#include <fcntl.h>
#include <pthread.h>
#include <linux/spi/spidev.h>
int main () {
printf("int: %lu\n", sizeof(int));
printf("O_RDWR: %i\n", O_RDWR);
printf("pthread_t: %lu\n", sizeof(pthread_t));
printf("pthread_mutex_t: %lu\n", sizeof(pthread_mutex_t));
printf("pthread_mutexattr_t: %lu\n", sizeof(pthread_mutexattr_t));
printf("SPI_IOC_MAGIC: 0x%02X ('%c')\n", SPI_IOC_MAGIC, SPI_IOC_MAGIC);
printf("SPI_IOC_WR_MODE: 0x%08X\n", SPI_IOC_WR_MODE);
printf("SPI_IOC_RD_MODE: 0x%08X\n", SPI_IOC_RD_MODE);
printf("SPI_IOC_WR_BITS_PER_WORD = 0x%08X\n", SPI_IOC_WR_BITS_PER_WORD);
printf("SPI_IOC_RD_BITS_PER_WORD = 0x%08X\n", SPI_IOC_RD_BITS_PER_WORD);
printf("SPI_IOC_WR_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_WR_MAX_SPEED_HZ);
printf("SPI_IOC_RD_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_RD_MAX_SPEED_HZ);
}
+3
View File
@@ -0,0 +1,3 @@
print("Hello from script file!")
name = "Lua"
print("Welcome to " .. name)
+22
View File
@@ -0,0 +1,22 @@
-- 接收一个参数(表),返回另一个表
function process(data)
local name = data.name
local age = data.age
local city = data.city
-- 处理逻辑
-- 修改:将 %d 改为 %s,因为 age 实际传入的是字符串
local result_message = string.format("Hello %s, age %s from %s", name, age, city)
-- 返回字典
return {
result = "success",
message = result_message,
code = 200
}
end
-- 脚本主体:接收传入的参数,调用处理函数,返回结果
-- 注意:传入的参数在 ... 中(可变参数)
local input = ... -- 获取第一个参数(我们的字典)
return process(input)