移植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

+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)