移植bot4cj
This commit is contained in:
1 parent
7803843155
commit
78ad3e6d57
76 files changed
+14468
No files matched your search
@@ -0,0 +1,586 @@
|
||||
/**
|
||||
* ads1115_linux.c
|
||||
*
|
||||
* Linux C 通过 I2C 采集 ADS1115 示例
|
||||
* ADS1115: TI 16位 4通道 ΔΣ ADC, 最高 860SPS, I2C 接口
|
||||
*
|
||||
* 编译: gcc -o ads1115 ads1115_linux.c
|
||||
* 运行: ./ads1115
|
||||
* ./ads1115 0x48 # 指定 I2C 地址
|
||||
* ./ads1115 0x48 1 2 # 指定地址、PGA、数据速率
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/i2c-dev.h>
|
||||
#include <time.h>
|
||||
#include <math.h>
|
||||
|
||||
// ============================================================
|
||||
// I2C 配置
|
||||
// ============================================================
|
||||
#define I2C_DEVICE "/dev/i2c-1" // I2C 总线 (树莓派是 i2c-1)
|
||||
#define ADS1115_ADDR 0x48 // ADDR 接 GND 时默认地址
|
||||
|
||||
// ============================================================
|
||||
// ADS1115 寄存器
|
||||
// ============================================================
|
||||
#define ADS1115_REG_CONVERSION 0x00 // 转换结果 (只读)
|
||||
#define ADS1115_REG_CONFIG 0x01 // 配置寄存器 (读/写)
|
||||
#define ADS1115_REG_LO_THRESH 0x02 // 低阈值比较
|
||||
#define ADS1115_REG_HI_THRESH 0x03 // 高阈值比较
|
||||
|
||||
// ============================================================
|
||||
// ADS1115 配置寄存器 bit 定义 (16 位)
|
||||
// ============================================================
|
||||
// BIT15: OS[0] — 单次转换启动位 (1=开始)
|
||||
// BIT14-12: MUX[2:0] — 输入多路选择
|
||||
// BIT11-9: PGA[2:0] — 可编程增益放大器
|
||||
// BIT8: MODE — 0=连续转换, 1=单次(省电)
|
||||
// BIT7-5: DR[2:0] — 数据速率
|
||||
// BIT4: COMP_MODE — 比较模式 (0=传统)
|
||||
// BIT3: COMP_POL — 比较极性 (0=低有效)
|
||||
// BIT2: COMP_LAT — 比较锁存 (0=不锁存)
|
||||
// BIT1-0: COMP_QUE — 比较器队列 (11=禁用)
|
||||
|
||||
// --- MUX 输入通道 ---
|
||||
#define ADS1115_MUX_AIN0_AIN1 0x0000 // 差分: P=AIN0, N=AIN1 (默认)
|
||||
#define ADS1115_MUX_AIN0_AIN3 0x1000 // 差分: P=AIN0, N=AIN3
|
||||
#define ADS1115_MUX_AIN1_AIN3 0x2000 // 差分: P=AIN1, N=AIN3
|
||||
#define ADS1115_MUX_AIN2_AIN3 0x3000 // 差分: P=AIN2, N=AIN3
|
||||
#define ADS1115_MUX_AIN0_GND 0x4000 // 单端: AIN0
|
||||
#define ADS1115_MUX_AIN1_GND 0x5000 // 单端: AIN1
|
||||
#define ADS1115_MUX_AIN2_GND 0x6000 // 单端: AIN2
|
||||
#define ADS1115_MUX_AIN3_GND 0x7000 // 单端: AIN3
|
||||
|
||||
// --- PGA 增益 (满量程范围) ---
|
||||
#define ADS1115_PGA_6_144V 0x0000 // ±6.144V (1LSB=0.1875mV)
|
||||
#define ADS1115_PGA_4_096V 0x0200 // ±4.096V (1LSB=0.125mV)
|
||||
#define ADS1115_PGA_2_048V 0x0400 // ±2.048V (1LSB=0.0625mV) (默认)
|
||||
#define ADS1115_PGA_1_024V 0x0600 // ±1.024V (1LSB=0.03125mV)
|
||||
#define ADS1115_PGA_0_512V 0x0800 // ±0.512V (1LSB=0.015625mV)
|
||||
#define ADS1115_PGA_0_256V 0x0A00 // ±0.256V (1LSB=0.0078125mV)
|
||||
|
||||
// --- 数据速率 ---
|
||||
#define ADS1115_DR_8SPS 0x0000 // 8 SPS
|
||||
#define ADS1115_DR_16SPS 0x0020 // 16 SPS
|
||||
#define ADS1115_DR_32SPS 0x0040 // 32 SPS
|
||||
#define ADS1115_DR_64SPS 0x0060 // 64 SPS
|
||||
#define ADS1115_DR_128SPS 0x0080 // 128 SPS (默认)
|
||||
#define ADS1115_DR_250SPS 0x00A0 // 250 SPS
|
||||
#define ADS1115_DR_475SPS 0x00C0 // 475 SPS
|
||||
#define ADS1115_DR_860SPS 0x00E0 // 860 SPS
|
||||
|
||||
// --- 比较器队列 (COMP_QUE) ---
|
||||
#define ADS1115_COMP_QUE_1 0x0000 // 1 次后触发
|
||||
#define ADS1115_COMP_QUE_2 0x0001 // 2 次后触发
|
||||
#define ADS1115_COMP_QUE_4 0x0002 // 4 次后触发
|
||||
#define ADS1115_COMP_QUE_DISABLE 0x0003 // 禁用比较器 (默认)
|
||||
|
||||
// --- 操作模式 ---
|
||||
#define ADS1115_MODE_CONTINUOUS 0x0000 // 连续转换
|
||||
#define ADS1115_MODE_SINGLE 0x0100 // 单次转换 (默认, 省电)
|
||||
|
||||
// --- 启动转换 ---
|
||||
#define ADS1115_OS_START 0x8000 // 启动单次转换
|
||||
|
||||
// ============================================================
|
||||
// 结构体: ADS1115 配置上下文
|
||||
// ============================================================
|
||||
typedef struct {
|
||||
int fd; // I2C 文件描述符
|
||||
uint8_t addr; // I2C 设备地址
|
||||
uint16_t config; // 当前配置值
|
||||
double fs_voltage; // 满量程电压 (取决于 PGA)
|
||||
} ads1115_t;
|
||||
|
||||
// ============================================================
|
||||
// PGA 对应的满量程电压 (单位: V)
|
||||
// ============================================================
|
||||
static const double pga_fs[] = {
|
||||
[0] = 6.144, // ADS1115_PGA_6_144V
|
||||
[1] = 4.096, // ADS1115_PGA_4_096V
|
||||
[2] = 2.048, // ADS1115_PGA_2_048V
|
||||
[3] = 1.024, // ADS1115_PGA_1_024V
|
||||
[4] = 0.512, // ADS1115_PGA_0_512V
|
||||
[5] = 0.256, // ADS1115_PGA_0_256V
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// PGA 增益对应的满量程电压名
|
||||
// ============================================================
|
||||
static const char *pga_names[] = {
|
||||
[0] = "±6.144V",
|
||||
[1] = "±4.096V",
|
||||
[2] = "±2.048V",
|
||||
[3] = "±1.024V",
|
||||
[4] = "±0.512V",
|
||||
[5] = "±0.256V",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 数据速率字符串
|
||||
// ============================================================
|
||||
static const char *dr_names[] = {
|
||||
[0] = "8 SPS",
|
||||
[1] = "16 SPS",
|
||||
[2] = "32 SPS",
|
||||
[3] = "64 SPS",
|
||||
[4] = "128 SPS",
|
||||
[5] = "250 SPS",
|
||||
[6] = "475 SPS",
|
||||
[7] = "860 SPS",
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 毫秒延时
|
||||
// ============================================================
|
||||
static void delay_ms(int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 微秒延时
|
||||
// ============================================================
|
||||
static void delay_us(int us)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = us / 1000000;
|
||||
ts.tv_nsec = (us % 1000000) * 1000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 初始化
|
||||
// ============================================================
|
||||
static int i2c_init(ads1115_t *dev, const char *i2c_dev, uint8_t addr)
|
||||
{
|
||||
dev->fd = open(i2c_dev, O_RDWR);
|
||||
if (dev->fd < 0) {
|
||||
perror("打开 I2C 设备失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ioctl(dev->fd, I2C_SLAVE, addr) < 0) {
|
||||
perror("设置 I2C 从机地址失败");
|
||||
close(dev->fd);
|
||||
dev->fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
dev->addr = addr;
|
||||
dev->config = 0;
|
||||
printf("I2C 初始化完成: 设备=%s, 地址=0x%02X\n", i2c_dev, addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 写 2 字节 (大端)
|
||||
// ============================================================
|
||||
static int i2c_write_word(ads1115_t *dev, uint8_t reg, uint16_t value)
|
||||
{
|
||||
uint8_t buf[3];
|
||||
buf[0] = reg;
|
||||
buf[1] = (value >> 8) & 0xFF; // 高字节
|
||||
buf[2] = value & 0xFF; // 低字节
|
||||
|
||||
if (write(dev->fd, buf, 3) != 3) {
|
||||
perror("I2C 写失败");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// I2C 读 2 字节 (大端)
|
||||
// ============================================================
|
||||
static int i2c_read_word(ads1115_t *dev, uint8_t reg, uint16_t *value)
|
||||
{
|
||||
// 先写入寄存器地址
|
||||
if (write(dev->fd, ®, 1) != 1) {
|
||||
perror("I2C 设置寄存器地址失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 读取 2 字节
|
||||
uint8_t buf[2];
|
||||
if (read(dev->fd, buf, 2) != 2) {
|
||||
perror("I2C 读失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*value = ((uint16_t)buf[0] << 8) | buf[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写配置寄存器
|
||||
// ============================================================
|
||||
static int ads1115_write_config(ads1115_t *dev, uint16_t config)
|
||||
{
|
||||
dev->config = config;
|
||||
return i2c_write_word(dev, ADS1115_REG_CONFIG, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读配置寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_config(ads1115_t *dev, uint16_t *config)
|
||||
{
|
||||
return i2c_read_word(dev, ADS1115_REG_CONFIG, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读转换结果寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_conversion(ads1115_t *dev, int16_t *value)
|
||||
{
|
||||
uint16_t raw;
|
||||
int ret = i2c_read_word(dev, ADS1115_REG_CONVERSION, &raw);
|
||||
if (ret == 0) {
|
||||
*value = (int16_t)raw;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读阈值寄存器
|
||||
// ============================================================
|
||||
static int ads1115_read_thresh(ads1115_t *dev, int16_t *lo, int16_t *hi)
|
||||
{
|
||||
uint16_t raw_lo, raw_hi;
|
||||
if (i2c_read_word(dev, ADS1115_REG_LO_THRESH, &raw_lo) < 0) return -1;
|
||||
if (i2c_read_word(dev, ADS1115_REG_HI_THRESH, &raw_hi) < 0) return -1;
|
||||
*lo = (int16_t)raw_lo;
|
||||
*hi = (int16_t)raw_hi;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写阈值寄存器
|
||||
// ============================================================
|
||||
static int ads1115_write_thresh(ads1115_t *dev, int16_t lo, int16_t hi)
|
||||
{
|
||||
if (i2c_write_word(dev, ADS1115_REG_LO_THRESH, (uint16_t)lo) < 0) return -1;
|
||||
if (i2c_write_word(dev, ADS1115_REG_HI_THRESH, (uint16_t)hi) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 初始化 ADS1115
|
||||
//
|
||||
// pga_idx: 0~5 对应 ADS1115_PGA_xxx
|
||||
// dr_idx: 0~7 对应 ADS1115_DR_xxx
|
||||
// ============================================================
|
||||
static int ads1115_init(ads1115_t *dev, int pga_idx, int dr_idx)
|
||||
{
|
||||
if (pga_idx < 0 || pga_idx > 5) pga_idx = 2; // 默认 ±2.048V
|
||||
if (dr_idx < 0 || dr_idx > 7) dr_idx = 4; // 默认 128SPS
|
||||
|
||||
dev->fs_voltage = pga_fs[pga_idx];
|
||||
|
||||
// 构建配置:
|
||||
// - 单次转换模式
|
||||
// - 禁用比较器
|
||||
// - 默认通道 AIN0-GND
|
||||
uint16_t config = ADS1115_MUX_AIN0_GND // 单端 AIN0
|
||||
| (pga_idx << 9) // PGA
|
||||
| ADS1115_MODE_SINGLE // 单次模式
|
||||
| (dr_idx << 5) // 数据速率
|
||||
| ADS1115_COMP_QUE_DISABLE; // 禁用比较器
|
||||
|
||||
int ret = ads1115_write_config(dev, config);
|
||||
if (ret < 0) return ret;
|
||||
|
||||
printf("ADS1115 初始化完成: ");
|
||||
printf("PGA=%s, 速率=%s, 满量程=%.3fV\n",
|
||||
pga_names[pga_idx], dr_names[dr_idx], dev->fs_voltage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取单端通道 (单次转换)
|
||||
// ch: 0~3 对应 AIN0~AIN3
|
||||
// ============================================================
|
||||
static double ads1115_read_single(ads1115_t *dev, int ch)
|
||||
{
|
||||
if (ch < 0 || ch > 3) {
|
||||
fprintf(stderr, "通道号无效: %d (有效: 0~3)\n", ch);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 1. 重写配置: 设置通道 + 启动转换
|
||||
uint16_t mux = ADS1115_MUX_AIN0_GND | (ch << 12); // AINch-GND
|
||||
uint16_t config = (dev->config & ~0x7000) // 清除 MUX 位
|
||||
| mux
|
||||
| ADS1115_OS_START; // 启动单次转换
|
||||
|
||||
ads1115_write_config(dev, config);
|
||||
|
||||
// 2. 等待转换完成 (OS 位被硬件清 0)
|
||||
// 根据数据速率等待足够时间
|
||||
uint16_t dr_idx = (config >> 5) & 0x07;
|
||||
int wait_ms = 1000 / (8 << dr_idx) + 1; // 保守估计
|
||||
delay_ms(wait_ms + 2);
|
||||
|
||||
// 3. 读转换值
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 4. 转换为电压
|
||||
// ADS1115 16位有符号, 满量程 = FS_VOLTAGE
|
||||
// V = raw * FS_VOLTAGE / 32768
|
||||
double voltage = (double)raw * dev->fs_voltage / 32768.0;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取差分通道 (单次转换)
|
||||
// pos: 正极通道 (0~3)
|
||||
// neg: 负极通道 (0~3)
|
||||
// ============================================================
|
||||
static double ads1115_read_diff(ads1115_t *dev, int pos, int neg)
|
||||
{
|
||||
if (pos < 0 || pos > 3 || neg < 0 || neg > 3) {
|
||||
fprintf(stderr, "通道号无效\n");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// 构建 MUX 值 (差分)
|
||||
// AIN0-AIN1: 0, AIN0-AIN3: 1, AIN1-AIN3: 2, AIN2-AIN3: 3
|
||||
uint16_t mux_val;
|
||||
if (pos == 0 && neg == 1) mux_val = 0;
|
||||
else if (pos == 0 && neg == 3) mux_val = 1;
|
||||
else if (pos == 1 && neg == 3) mux_val = 2;
|
||||
else if (pos == 2 && neg == 3) mux_val = 3;
|
||||
else {
|
||||
fprintf(stderr, "不支持的差分组合: AIN%d - AIN%d\n", pos, neg);
|
||||
fprintf(stderr, "支持: 0-1, 0-3, 1-3, 2-3\n");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
uint16_t mux = mux_val << 12;
|
||||
uint16_t config = (dev->config & ~0x7000)
|
||||
| mux
|
||||
| ADS1115_OS_START;
|
||||
|
||||
ads1115_write_config(dev, config);
|
||||
|
||||
uint16_t dr_idx = (config >> 5) & 0x07;
|
||||
int wait_ms = 1000 / (8 << dr_idx) + 1;
|
||||
delay_ms(wait_ms + 2);
|
||||
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double voltage = (double)raw * dev->fs_voltage / 32768.0;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续模式读数 (需先配置为连续模式)
|
||||
// ============================================================
|
||||
static int ads1115_start_continuous(ads1115_t *dev, int ch)
|
||||
{
|
||||
if (ch < 0 || ch > 3) return -1;
|
||||
|
||||
uint16_t config = (dev->config & ~0x7100) // 清除 MUX+MODE 位
|
||||
| (ADS1115_MUX_AIN0_GND | (ch << 12)) // 通道
|
||||
| ADS1115_MODE_CONTINUOUS // 连续模式
|
||||
| ADS1115_COMP_QUE_DISABLE;
|
||||
|
||||
return ads1115_write_config(dev, config);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续模式读数
|
||||
// ============================================================
|
||||
static double ads1115_read_continuous(ads1115_t *dev)
|
||||
{
|
||||
int16_t raw = 0;
|
||||
if (ads1115_read_conversion(dev, &raw) < 0) {
|
||||
return 0.0;
|
||||
}
|
||||
return (double)raw * dev->fs_voltage / 32768.0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 扫描所有单端通道
|
||||
// ============================================================
|
||||
static void ads1115_scan_all(ads1115_t *dev)
|
||||
{
|
||||
printf("\n===== 单端通道扫描 =====\n");
|
||||
const char *ch_names[] = {"AIN0", "AIN1", "AIN2", "AIN3"};
|
||||
for (int ch = 0; ch < 4; ch++) {
|
||||
double v = ads1115_read_single(dev, ch);
|
||||
printf(" %s: %+.6f V\n", ch_names[ch], v);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印当前配置
|
||||
// ============================================================
|
||||
static void ads1115_print_config(ads1115_t *dev)
|
||||
{
|
||||
uint16_t config;
|
||||
if (ads1115_read_config(dev, &config) < 0) return;
|
||||
|
||||
int mux = (config >> 12) & 0x07;
|
||||
int pga = (config >> 9) & 0x07;
|
||||
int mode = (config >> 8) & 0x01;
|
||||
int dr = (config >> 5) & 0x07;
|
||||
int comp = config & 0x03;
|
||||
|
||||
printf("\n===== ADS1115 寄存器 =====\n");
|
||||
printf(" 配置寄存器: 0x%04X\n", config);
|
||||
printf(" MUX: %d (", mux);
|
||||
switch (mux) {
|
||||
case 0: printf("AIN0-AIN1"); break;
|
||||
case 1: printf("AIN0-AIN3"); break;
|
||||
case 2: printf("AIN1-AIN3"); break;
|
||||
case 3: printf("AIN2-AIN3"); break;
|
||||
case 4: printf("AIN0-GND"); break;
|
||||
case 5: printf("AIN1-GND"); break;
|
||||
case 6: printf("AIN2-GND"); break;
|
||||
case 7: printf("AIN3-GND"); break;
|
||||
}
|
||||
printf(")\n");
|
||||
printf(" PGA: %d -> %s (满量程 %.3fV)\n", pga,
|
||||
(pga <= 5) ? pga_names[pga] : "未知",
|
||||
(pga <= 5) ? pga_fs[pga] : 0);
|
||||
printf(" 模式: %s\n", mode ? "单次" : "连续");
|
||||
printf(" 速率: %d -> %s\n", dr, dr_names[dr & 0x07]);
|
||||
printf(" 比较器: %s\n", comp == 3 ? "禁用" : "启用");
|
||||
|
||||
// 读阈值
|
||||
int16_t lo, hi;
|
||||
if (ads1115_read_thresh(dev, &lo, &hi) == 0) {
|
||||
printf(" 阈值: LO=%d (%.6fV), HI=%d (%.6fV)\n",
|
||||
lo, (double)lo * dev->fs_voltage / 32768.0,
|
||||
hi, (double)hi * dev->fs_voltage / 32768.0);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印转换结果
|
||||
// ============================================================
|
||||
static void print_reading(const char *label, double voltage)
|
||||
{
|
||||
printf(" %s: %+.6f V (%.3f mV)\n", label, voltage, voltage * 1000.0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 打印帮助
|
||||
// ============================================================
|
||||
static void print_help(const char *prog)
|
||||
{
|
||||
printf("\n用法: %s [I2C地址] [PGA] [数据速率]\n", prog);
|
||||
printf("\n参数 (可选):\n");
|
||||
printf(" I2C地址 0x48~0x4B (默认 0x48)\n");
|
||||
printf(" PGA 0~5 (默认 2 = ±2.048V)\n");
|
||||
printf(" 0=±6.144V, 1=±4.096V, 2=±2.048V\n");
|
||||
printf(" 3=±1.024V, 4=±0.512V, 5=±0.256V\n");
|
||||
printf(" 数据速率 0~7 (默认 4 = 128SPS)\n");
|
||||
printf(" 0=8, 1=16, 2=32, 3=64\n");
|
||||
printf(" 4=128, 5=250, 6=475, 7=860 (SPS)\n");
|
||||
printf("\n示例:\n");
|
||||
printf(" %s\n", prog);
|
||||
printf(" %s 0x49\n", prog);
|
||||
printf(" %s 0x48 2 6\n", prog);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
ads1115_t dev;
|
||||
uint8_t addr = ADS1115_ADDR;
|
||||
int pga = 2; // ±2.048V
|
||||
int dr = 4; // 128 SPS
|
||||
|
||||
// 解析命令行参数
|
||||
if (argc >= 2) {
|
||||
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
|
||||
print_help(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
addr = (uint8_t)strtol(argv[1], NULL, 0);
|
||||
}
|
||||
if (argc >= 3) pga = atoi(argv[2]);
|
||||
if (argc >= 4) dr = atoi(argv[3]);
|
||||
|
||||
printf("ADS1115 Linux I2C 采集示例\n");
|
||||
printf("============================\n\n");
|
||||
|
||||
// 1. 初始化 I2C
|
||||
if (i2c_init(&dev, I2C_DEVICE, addr) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. 初始化 ADS1115
|
||||
if (ads1115_init(&dev, pga, dr) < 0) {
|
||||
close(dev.fd);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 3. 单通道测试 (AIN0)
|
||||
printf("\n===== 单通道测试 (AIN0, 连续读 10 次) =====\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
double v = ads1115_read_single(&dev, 0);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d]", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 4. 扫描所有单端通道
|
||||
ads1115_scan_all(&dev);
|
||||
|
||||
// 5. 差分模式示例
|
||||
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double v = ads1115_read_diff(&dev, 0, 1);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d]", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 6. 连续模式测试
|
||||
printf("\n===== 连续模式测试 (AIN0, 5 次) =====\n");
|
||||
if (ads1115_start_continuous(&dev, 0) == 0) {
|
||||
delay_ms(50); // 等第一次转换完成
|
||||
for (int i = 0; i < 5; i++) {
|
||||
double v = ads1115_read_continuous(&dev);
|
||||
char label[32];
|
||||
snprintf(label, sizeof(label), "[%d] 连续", i);
|
||||
print_reading(label, v);
|
||||
delay_ms(10);
|
||||
}
|
||||
} else {
|
||||
printf(" 启动连续模式失败\n");
|
||||
}
|
||||
|
||||
// 7. 打印配置信息
|
||||
ads1115_print_config(&dev);
|
||||
|
||||
// 8. 清理
|
||||
close(dev.fd);
|
||||
printf("\n程序结束.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
/**
|
||||
* ads1256_linux.c
|
||||
*
|
||||
* Linux C 通过 SPI 采集 ADS1256 示例
|
||||
* 适用平台: Raspberry Pi / 任何 Linux + SPI 设备
|
||||
* 编译: gcc -o ads1256 ads1256_linux.c -lwiringPi (树莓派用 wiringPi)
|
||||
* gcc -o ads1256 ads1256_linux.c (通用 /dev/spidev)
|
||||
*
|
||||
* ADS1256: TI 24位 8通道 ΔΣ ADC, 最高 30kSPS
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
#include <time.h>
|
||||
|
||||
// ============================================================
|
||||
// 配置参数 - 按需修改
|
||||
// ============================================================
|
||||
#define SPI_DEVICE "/dev/spidev0.0" // SPI 设备路径
|
||||
#define SPI_SPEED 1920000 // SPI 时钟 (1.92MHz, ADS1256 最大支持 2MHz)
|
||||
#define SPI_MODE 1 // SPI 模式: CPOL=0, CPHA=1 (模式1)
|
||||
#define SPI_BITS 8 // 8 位传输
|
||||
|
||||
// ADS1256 片选用 GPIO (如果使用硬件 CS 则不需要)
|
||||
// 若用 /dev/spidev 的硬件 CS, 这部分不需要手动控制
|
||||
// #define CS_PIN 8 // 例如 wiringPi 的 GPIO 8
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 寄存器地址
|
||||
// ============================================================
|
||||
#define ADS1256_REG_STATUS 0x00
|
||||
#define ADS1256_REG_MUX 0x01
|
||||
#define ADS1256_REG_ADCON 0x02
|
||||
#define ADS1256_REG_DRATE 0x03
|
||||
#define ADS1256_REG_IO 0x04
|
||||
#define ADS1256_REG_OFC0 0x05
|
||||
#define ADS1256_REG_OFC1 0x06
|
||||
#define ADS1256_REG_OFC2 0x07
|
||||
#define ADS1256_REG_FSC0 0x08
|
||||
#define ADS1256_REG_FSC1 0x09
|
||||
#define ADS1256_REG_FSC2 0x0A
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 命令定义
|
||||
// ============================================================
|
||||
#define ADS1256_CMD_WAKEUP 0x00 // 退出待机
|
||||
#define ADS1256_CMD_RDATA 0x01 // 读数据
|
||||
#define ADS1256_CMD_RDATAC 0x03 // 连续读数据
|
||||
#define ADS1256_CMD_SDATAC 0x0F // 停止连续读
|
||||
#define ADS1256_CMD_RREG 0x10 // 读寄存器 (| 寄存器地址)
|
||||
#define ADS1256_CMD_WREG 0x50 // 写寄存器 (| 寄存器地址)
|
||||
#define ADS1256_CMD_SELFCAL 0xF0 // 自校准
|
||||
#define ADS1256_CMD_SELFOCAL 0xF1 // 自失调校准
|
||||
#define ADS1256_CMD_SELFGCAL 0xF2 // 自增益校准
|
||||
#define ADS1256_CMD_SYSOCAL 0xF3 // 系统失调校准
|
||||
#define ADS1256_CMD_SYSGCAL 0xF4 // 系统增益校准
|
||||
#define ADS1256_CMD_SYNC 0xFC // 同步
|
||||
#define ADS1256_CMD_STANDBY 0xFD // 待机
|
||||
#define ADS1256_CMD_RESET 0xFE // 复位
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 数据速率 (DRATE 寄存器值)
|
||||
// ============================================================
|
||||
#define ADS1256_DRATE_30000 0xF0 // 30,000 SPS
|
||||
#define ADS1256_DRATE_15000 0xE0 // 15,000 SPS
|
||||
#define ADS1256_DRATE_7500 0xD0 // 7,500 SPS
|
||||
#define ADS1256_DRATE_3750 0xC0 // 3,750 SPS
|
||||
#define ADS1256_DRATE_2000 0xB0 // 2,000 SPS
|
||||
#define ADS1256_DRATE_1000 0xA0 // 1,000 SPS
|
||||
#define ADS1256_DRATE_500 0x90 // 500 SPS
|
||||
#define ADS1256_DRATE_100 0x80 // 100 SPS
|
||||
#define ADS1256_DRATE_60 0x70 // 60 SPS
|
||||
#define ADS1256_DRATE_50 0x60 // 50 SPS
|
||||
#define ADS1256_DRATE_30 0x50 // 30 SPS
|
||||
#define ADS1256_DRATE_25 0x40 // 25 SPS
|
||||
#define ADS1256_DRATE_15 0x30 // 15 SPS
|
||||
#define ADS1256_DRATE_10 0x20 // 10 SPS
|
||||
#define ADS1256_DRATE_5 0x10 // 5 SPS
|
||||
|
||||
// ============================================================
|
||||
// PGA 增益 (ADCON 寄存器的低 3 位)
|
||||
// ============================================================
|
||||
#define ADS1256_PGA_1 0x00 // ±5V
|
||||
#define ADS1256_PGA_2 0x01 // ±2.5V
|
||||
#define ADS1256_PGA_4 0x02 // ±1.25V
|
||||
#define ADS1256_PGA_8 0x03 // ±0.625V
|
||||
#define ADS1256_PGA_16 0x04 // ±0.3125V
|
||||
#define ADS1256_PGA_32 0x05 // ±0.15625V
|
||||
#define ADS1256_PGA_64 0x06 // ±0.078125V
|
||||
|
||||
// 参考电压 (V)
|
||||
#define ADS1256_VREF 2.5
|
||||
|
||||
// ============================================================
|
||||
// 全局变量
|
||||
// ============================================================
|
||||
static int spi_fd = -1; // SPI 文件描述符
|
||||
static uint8_t spi_txbuf[32];
|
||||
static uint8_t spi_rxbuf[32];
|
||||
|
||||
// ============================================================
|
||||
// SPI 初始化
|
||||
// ============================================================
|
||||
int spi_init(const char *device, uint32_t speed, uint8_t mode, uint8_t bits)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spi_fd = open(device, O_RDWR);
|
||||
if (spi_fd < 0) {
|
||||
perror("打开 SPI 设备失败");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置 SPI 模式
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_MODE, &mode);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 模式失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置位数
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 位数失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 设置速度
|
||||
ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
|
||||
if (ret < 0) {
|
||||
perror("设置 SPI 速度失败");
|
||||
close(spi_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 读取并验证设置
|
||||
uint8_t rb_mode, rb_bits;
|
||||
uint32_t rb_speed;
|
||||
|
||||
ioctl(spi_fd, SPI_IOC_RD_MODE, &rb_mode);
|
||||
ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &rb_bits);
|
||||
ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &rb_speed);
|
||||
|
||||
printf("SPI 初始化完成: 模式=%d, 位数=%d, 速度=%d Hz\n",
|
||||
rb_mode, rb_bits, rb_speed);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 收发 (全双工)
|
||||
// ============================================================
|
||||
int spi_transfer(uint8_t *tx, uint8_t *rx, int len)
|
||||
{
|
||||
struct spi_ioc_transfer tr = {
|
||||
.tx_buf = (unsigned long)tx,
|
||||
.rx_buf = (unsigned long)rx,
|
||||
.len = (uint32_t)len,
|
||||
.delay_usecs = 0,
|
||||
.speed_hz = SPI_SPEED,
|
||||
.bits_per_word = SPI_BITS,
|
||||
.cs_change = 0,
|
||||
};
|
||||
|
||||
int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &tr);
|
||||
if (ret < 0) {
|
||||
perror("SPI 传输失败");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 发送单字节
|
||||
// ============================================================
|
||||
static inline void spi_write_byte(uint8_t byte)
|
||||
{
|
||||
spi_txbuf[0] = byte;
|
||||
spi_transfer(spi_txbuf, spi_rxbuf, 1);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 读单字节
|
||||
// ============================================================
|
||||
static inline uint8_t spi_read_byte(void)
|
||||
{
|
||||
spi_txbuf[0] = 0xFF; // MOSI 发 0xFF 以产生时钟
|
||||
spi_transfer(spi_txbuf, spi_rxbuf, 1);
|
||||
return spi_rxbuf[0];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 等待 DRDY 引脚变低 (数据就绪)
|
||||
//
|
||||
// 注意: 这里用延时模拟等待,实际应接 GPIO 中断或轮询
|
||||
// 若 DRDY 接了 GPIO (如 wiringPi 的 pin 25),可改为:
|
||||
// while(digitalRead(DRDY_PIN));
|
||||
// ============================================================
|
||||
void wait_drdy(void)
|
||||
{
|
||||
// 实际项目中用 GPIO 轮询更准确
|
||||
// 这里用延时估算
|
||||
usleep(100); // 100us,根据采样率调整
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 复位
|
||||
// ============================================================
|
||||
void ads1256_reset(void)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_RESET);
|
||||
usleep(1000); // 等待复位完成
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP); // 退出待机
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 写 ADS1256 寄存器
|
||||
// ============================================================
|
||||
void ads1256_write_reg(uint8_t reg, uint8_t value)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_WREG | reg);
|
||||
spi_write_byte(0x00); // 后续字节数 - 1 (0 = 1 byte)
|
||||
spi_write_byte(value);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读 ADS1256 寄存器
|
||||
// ============================================================
|
||||
uint8_t ads1256_read_reg(uint8_t reg)
|
||||
{
|
||||
spi_write_byte(ADS1256_CMD_RREG | reg);
|
||||
spi_write_byte(0x00); // 后续字节数 - 1
|
||||
return spi_read_byte();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 设置输入通道 (MUX 寄存器)
|
||||
//
|
||||
// mux:
|
||||
// 单端模式: POS = channel, NEG = 0x08 (AINCOM)
|
||||
// 差分模式: POS = channel_p, NEG = channel_n
|
||||
// ============================================================
|
||||
void ads1256_set_channel(uint8_t pos, uint8_t neg)
|
||||
{
|
||||
uint8_t mux = ((pos & 0x07) << 4) | (neg & 0x07);
|
||||
ads1256_write_reg(ADS1256_REG_MUX, mux);
|
||||
usleep(1);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// ADS1256 初始化
|
||||
// ============================================================
|
||||
int ads1256_init(void)
|
||||
{
|
||||
printf("正在初始化 ADS1256...\n");
|
||||
|
||||
// 1. 复位 ADS1256
|
||||
ads1256_reset();
|
||||
usleep(10000);
|
||||
|
||||
// 2. 配置状态寄存器
|
||||
// BIT7: ID (只读)
|
||||
// BIT6: ID (只读)
|
||||
// BIT5: ORDER = 0 (MSB 优先)
|
||||
// BIT4: ACAL = 1 (自动校准)
|
||||
// BIT3: BUFEN = 0 (关闭缓冲)
|
||||
// BIT2: DRB = 0 (DRDY 低有效)
|
||||
// BIT1-0: 保留
|
||||
ads1256_write_reg(ADS1256_REG_STATUS, 0x14); // 0b00010100
|
||||
|
||||
// 3. 配置 MUX (默认: AIN0 + AINCOM)
|
||||
ads1256_set_channel(0, 0x08);
|
||||
|
||||
// 4. 配置 ADCON (A/D 控制寄存器)
|
||||
// BIT7-5: CLK1-0 = 00 (时钟输出关), SDCS = 0
|
||||
// BIT4-3: 保留
|
||||
// BIT2-0: PGA = 001 (增益 ×2)
|
||||
ads1256_write_reg(ADS1256_REG_ADCON, ADS1256_PGA_2);
|
||||
|
||||
// 5. 设置数据速率 (1000 SPS)
|
||||
ads1256_write_reg(ADS1256_REG_DRATE, ADS1256_DRATE_1000);
|
||||
|
||||
// 6. 自校准
|
||||
printf("正在进行自校准...\n");
|
||||
spi_write_byte(ADS1256_CMD_SELFCAL);
|
||||
|
||||
// 等待校准完成 (约 400ms @ 1000SPS)
|
||||
usleep(500000);
|
||||
|
||||
printf("ADS1256 初始化完成!\n");
|
||||
|
||||
// 读寄存器验证
|
||||
uint8_t status = ads1256_read_reg(ADS1256_REG_STATUS);
|
||||
uint8_t mux = ads1256_read_reg(ADS1256_REG_MUX);
|
||||
uint8_t adcon = ads1256_read_reg(ADS1256_REG_ADCON);
|
||||
uint8_t drate = ads1256_read_reg(ADS1256_REG_DRATE);
|
||||
|
||||
printf("寄存器状态: STATUS=0x%02X, MUX=0x%02X, ADCON=0x%02X, DRATE=0x%02X\n",
|
||||
status, mux, adcon, drate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 单次读取 24 位数据
|
||||
//
|
||||
// 返回: 24 位有符号整数 (二进制补码)
|
||||
// ============================================================
|
||||
int32_t ads1256_read_data(void)
|
||||
{
|
||||
uint8_t buf[3];
|
||||
|
||||
// 发送 RDATA 命令
|
||||
wait_drdy();
|
||||
spi_write_byte(ADS1256_CMD_RDATA);
|
||||
|
||||
// 读 3 字节 (24 位)
|
||||
buf[0] = spi_read_byte();
|
||||
buf[1] = spi_read_byte();
|
||||
buf[2] = spi_read_byte();
|
||||
|
||||
// 组合成 32 位有符号数 (符号扩展)
|
||||
int32_t value;
|
||||
value = ((int32_t)buf[0] << 16) |
|
||||
((int32_t)buf[1] << 8) |
|
||||
((int32_t)buf[2]);
|
||||
|
||||
// 24 位有符号数符号扩展
|
||||
if (value & 0x800000) {
|
||||
value |= 0xFF000000;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 将原始值转换为电压
|
||||
// ============================================================
|
||||
double ads1256_raw_to_voltage(int32_t raw, uint8_t pga)
|
||||
{
|
||||
// ADS1256 满量程 = ±VREF / PGA
|
||||
// 24 位: -2^23 ~ 2^23-1
|
||||
double voltage = (double)raw * ADS1256_VREF / (double)(1 << 23) / (double)pga;
|
||||
return voltage;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取指定单端通道
|
||||
// ============================================================
|
||||
int32_t ads1256_read_channel(uint8_t ch)
|
||||
{
|
||||
ads1256_set_channel(ch, 0x08); // AINch vs AINCOM
|
||||
usleep(1);
|
||||
|
||||
// 发送 SYNC + WAKEUP 以启动转换
|
||||
spi_write_byte(ADS1256_CMD_SYNC);
|
||||
usleep(1);
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP);
|
||||
|
||||
// 等待转换完成 (根据采样率调整)
|
||||
// 1000 SPS -> 约 1ms
|
||||
usleep(2000);
|
||||
|
||||
return ads1256_read_data();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 读取指定差分通道
|
||||
// ============================================================
|
||||
int32_t ads1256_read_diff(uint8_t pos, uint8_t neg)
|
||||
{
|
||||
ads1256_set_channel(pos, neg);
|
||||
usleep(1);
|
||||
|
||||
spi_write_byte(ADS1256_CMD_SYNC);
|
||||
usleep(1);
|
||||
spi_write_byte(ADS1256_CMD_WAKEUP);
|
||||
|
||||
usleep(2000);
|
||||
|
||||
return ads1256_read_data();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 连续扫描所有 8 个单端通道
|
||||
// ============================================================
|
||||
void ads1256_scan_all(void)
|
||||
{
|
||||
printf("\n===== 多通道扫描 =====\n");
|
||||
for (int ch = 0; ch < 8; ch++) {
|
||||
int32_t raw = ads1256_read_channel(ch);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2); // PGA=2
|
||||
printf("通道 AIN%d: raw = %7d, 电压 = %+.6f V\n", ch, raw, volts);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SPI 关闭
|
||||
// ============================================================
|
||||
void spi_close(void)
|
||||
{
|
||||
if (spi_fd >= 0) {
|
||||
close(spi_fd);
|
||||
spi_fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 毫秒延时
|
||||
// ============================================================
|
||||
void delay_ms(int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 主函数
|
||||
// ============================================================
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
printf("ADS1256 Linux SPI 采集示例\n");
|
||||
printf("===========================\n\n");
|
||||
|
||||
// 1. 初始化 SPI
|
||||
if (spi_init(SPI_DEVICE, SPI_SPEED, SPI_MODE, SPI_BITS) < 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 2. 初始化 ADS1256
|
||||
if (ads1256_init() < 0) {
|
||||
spi_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("\n===== 单通道测试 (AIN0) =====\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int32_t raw = ads1256_read_channel(0);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2);
|
||||
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 3. 扫描所有通道
|
||||
ads1256_scan_all();
|
||||
|
||||
// 4. 差分模式示例 (AIN0 - AIN1)
|
||||
printf("\n===== 差分测试 (AIN0 - AIN1) =====\n");
|
||||
for (int i = 0; i < 5; i++) {
|
||||
int32_t raw = ads1256_read_diff(0, 1);
|
||||
double volts = ads1256_raw_to_voltage(raw, 2);
|
||||
printf("[%d] raw=%7d, voltage=%+.6f V\n", i, raw, volts);
|
||||
delay_ms(10);
|
||||
}
|
||||
|
||||
// 5. 清理
|
||||
spi_close();
|
||||
printf("\n程序结束.\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,590 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
图片标注工具
|
||||
用于手动标注图像中的目标区域并保存位置坐标(百分比格式)到txt文件
|
||||
支持矩形框标注、预览、编辑和删除功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, simpledialog
|
||||
from PIL import Image, ImageTk, ImageDraw
|
||||
import cv2
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Tuple
|
||||
import threading
|
||||
|
||||
|
||||
class ImageAnnotationTool:
|
||||
"""图片标注工具 - 支持手动矩形框标注"""
|
||||
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("图片标注工具")
|
||||
self.root.geometry("1200x800")
|
||||
|
||||
# 数据存储
|
||||
self.current_image_path = None
|
||||
self.current_image = None
|
||||
self.image_pil = None
|
||||
self.image_tk = None
|
||||
self.zoom_factor = 1.0
|
||||
self.image_display = None
|
||||
self.annotations = [] # 存储标注:[(x1, y1, x2, y2), ...] # 移除了label字段
|
||||
|
||||
# 标注状态
|
||||
self.drawing = False
|
||||
self.start_x = None
|
||||
self.start_y = None
|
||||
self.current_rect = None
|
||||
self.selected_annotation = -1 # 当前选中的标注索引
|
||||
self.drag_mode = False # 是否在拖拽模式
|
||||
self.drag_start = None # 拖拽起始点
|
||||
|
||||
# 缩放和平移
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
self.pan_start_x = 0
|
||||
self.pan_start_y = 0
|
||||
self.panning = False
|
||||
|
||||
# 创建UI
|
||||
self.setup_ui()
|
||||
|
||||
# 绑定事件
|
||||
self.setup_bindings()
|
||||
|
||||
# 更新状态
|
||||
self.update_status()
|
||||
|
||||
def setup_ui(self):
|
||||
"""设置用户界面"""
|
||||
# 创建主框架
|
||||
main_frame = ttk.Frame(self.root)
|
||||
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||||
|
||||
# 左侧控制面板
|
||||
control_frame = ttk.Frame(main_frame, width=250)
|
||||
control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 5))
|
||||
control_frame.pack_propagate(False)
|
||||
|
||||
# 文件操作
|
||||
file_group = ttk.LabelFrame(control_frame, text="文件操作", padding=5)
|
||||
file_group.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
ttk.Button(file_group, text="打开图片", command=self.open_image).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(file_group, text="保存标注", command=self.save_annotations).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(file_group, text="加载标注", command=self.load_annotations).pack(fill=tk.X, pady=2)
|
||||
|
||||
# 标注操作
|
||||
anno_group = ttk.LabelFrame(control_frame, text="标注操作", padding=5)
|
||||
anno_group.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
ttk.Button(anno_group, text="清除当前标注", command=self.clear_current_annotation).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(anno_group, text="删除所有标注", command=self.clear_all_annotations).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(anno_group, text="删除选中标注", command=self.delete_selected_annotation).pack(fill=tk.X, pady=2)
|
||||
|
||||
# 标注列表
|
||||
list_group = ttk.LabelFrame(control_frame, text="标注列表", padding=5)
|
||||
list_group.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||
|
||||
# 创建列表框和滚动条
|
||||
list_frame = ttk.Frame(list_group)
|
||||
list_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.annotation_listbox = tk.Listbox(list_frame, height=15)
|
||||
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.annotation_listbox.yview)
|
||||
self.annotation_listbox.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.annotation_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
self.annotation_listbox.bind('<<ListboxSelect>>', self.on_annotation_select)
|
||||
|
||||
# 信息显示
|
||||
info_group = ttk.LabelFrame(control_frame, text="信息", padding=5)
|
||||
info_group.pack(fill=tk.X)
|
||||
|
||||
self.info_label = ttk.Label(info_group, text="未加载图片", wraplength=220)
|
||||
self.info_label.pack(fill=tk.X, pady=2)
|
||||
|
||||
self.status_label = ttk.Label(info_group, text="状态: 就绪", wraplength=220)
|
||||
self.status_label.pack(fill=tk.X, pady=2)
|
||||
|
||||
# 右侧画布区域
|
||||
canvas_frame = ttk.Frame(main_frame)
|
||||
canvas_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# 工具栏
|
||||
toolbar = ttk.Frame(canvas_frame)
|
||||
toolbar.pack(fill=tk.X, pady=(0, 5))
|
||||
|
||||
ttk.Button(toolbar, text="放大", command=self.zoom_in).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="缩小", command=self.zoom_out).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="适应窗口", command=self.fit_to_window).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="重置视图", command=self.reset_view).pack(side=tk.LEFT, padx=2)
|
||||
|
||||
self.draw_mode_var = tk.BooleanVar(value=True)
|
||||
ttk.Checkbutton(toolbar, text="绘制模式", variable=self.draw_mode_var).pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 画布
|
||||
self.canvas = tk.Canvas(canvas_frame, bg='gray', cursor='cross')
|
||||
self.canvas.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def setup_bindings(self):
|
||||
"""设置事件绑定"""
|
||||
self.canvas.bind('<ButtonPress-1>', self.on_mouse_down)
|
||||
self.canvas.bind('<B1-Motion>', self.on_mouse_move)
|
||||
self.canvas.bind('<ButtonRelease-1>', self.on_mouse_up)
|
||||
self.canvas.bind('<Control-ButtonPress-1>', self.on_pan_start)
|
||||
self.canvas.bind('<Control-B1-Motion>', self.on_pan_move)
|
||||
self.canvas.bind('<Control-ButtonRelease-1>', self.on_pan_end)
|
||||
self.root.bind('<Delete>', lambda e: self.delete_selected_annotation())
|
||||
self.root.bind('<Control-s>', lambda e: self.save_annotations())
|
||||
self.root.bind('<Control-o>', lambda e: self.open_image())
|
||||
|
||||
def open_image(self):
|
||||
"""打开图片文件"""
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="选择图片文件",
|
||||
filetypes=[
|
||||
("图片文件", "*.jpg *.jpeg *.png *.bmp *.tiff"),
|
||||
("所有文件", "*.*")
|
||||
]
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
# 加载图片
|
||||
self.current_image_path = file_path
|
||||
self.image_pil = Image.open(file_path)
|
||||
|
||||
# 重置状态
|
||||
self.annotations = []
|
||||
self.selected_annotation = -1
|
||||
self.zoom_factor = 1.0
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
|
||||
# 适应窗口
|
||||
self.fit_to_window()
|
||||
|
||||
# 更新标注列表
|
||||
self.update_annotation_list()
|
||||
|
||||
# 更新信息
|
||||
self.update_info()
|
||||
self.update_status("图片已加载")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"无法加载图片: {e}")
|
||||
|
||||
def fit_to_window(self):
|
||||
"""调整图片适应窗口"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
canvas_width = self.canvas.winfo_width()
|
||||
canvas_height = self.canvas.winfo_height()
|
||||
|
||||
if canvas_width <= 1 or canvas_height <= 1:
|
||||
canvas_width = 800
|
||||
canvas_height = 600
|
||||
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
# 计算缩放比例
|
||||
scale_x = canvas_width / img_width
|
||||
scale_y = canvas_height / img_height
|
||||
self.zoom_factor = min(scale_x, scale_y)
|
||||
|
||||
# 计算偏移量使图片居中
|
||||
scaled_width = img_width * self.zoom_factor
|
||||
scaled_height = img_height * self.zoom_factor
|
||||
self.offset_x = (canvas_width - scaled_width) / 2
|
||||
self.offset_y = (canvas_height - scaled_height) / 2
|
||||
|
||||
self.display_image()
|
||||
|
||||
def reset_view(self):
|
||||
"""重置视图"""
|
||||
self.zoom_factor = 1.0
|
||||
self.offset_x = 0
|
||||
self.offset_y = 0
|
||||
self.fit_to_window()
|
||||
|
||||
def zoom_in(self):
|
||||
"""放大"""
|
||||
self.zoom_factor *= 1.2
|
||||
self.display_image()
|
||||
|
||||
def zoom_out(self):
|
||||
"""缩小"""
|
||||
self.zoom_factor /= 1.2
|
||||
self.display_image()
|
||||
|
||||
def display_image(self):
|
||||
"""显示图片"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
# 缩放图片
|
||||
img_width, img_height = self.image_pil.size
|
||||
new_width = int(img_width * self.zoom_factor)
|
||||
new_height = int(img_height * self.zoom_factor)
|
||||
|
||||
scaled_image = self.image_pil.resize((new_width, new_height), Image.Resampling.LANCZOS)
|
||||
|
||||
# 转换为PhotoImage
|
||||
self.image_tk = ImageTk.PhotoImage(scaled_image)
|
||||
|
||||
# 清除画布
|
||||
self.canvas.delete("all")
|
||||
|
||||
# 显示图片
|
||||
self.canvas.create_image(self.offset_x, self.offset_y, anchor=tk.NW, image=self.image_tk)
|
||||
|
||||
# 绘制标注
|
||||
self.draw_annotations()
|
||||
|
||||
def draw_annotations(self):
|
||||
"""绘制所有标注"""
|
||||
if not self.image_pil:
|
||||
return
|
||||
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
# 转换坐标
|
||||
canvas_x1 = x1 * self.zoom_factor + self.offset_x
|
||||
canvas_y1 = y1 * self.zoom_factor + self.offset_y
|
||||
canvas_x2 = x2 * self.zoom_factor + self.offset_x
|
||||
canvas_y2 = y2 * self.zoom_factor + self.offset_y
|
||||
|
||||
# 确定颜色
|
||||
if idx == self.selected_annotation:
|
||||
color = "red"
|
||||
width = 3
|
||||
else:
|
||||
color = "blue"
|
||||
width = 2
|
||||
|
||||
# 绘制矩形
|
||||
self.canvas.create_rectangle(canvas_x1, canvas_y1, canvas_x2, canvas_y2,
|
||||
outline=color, width=width, tags=f"rect_{idx}")
|
||||
|
||||
# 绘制序号
|
||||
self.canvas.create_text(canvas_x1 + 5, canvas_y1 + 15,
|
||||
text=str(idx + 1), anchor=tk.NW,
|
||||
fill=color, font=("Arial", 10, "bold"),
|
||||
tags=f"label_{idx}")
|
||||
|
||||
def canvas_to_image_coords(self, canvas_x, canvas_y):
|
||||
"""将画布坐标转换为图片坐标"""
|
||||
img_x = (canvas_x - self.offset_x) / self.zoom_factor
|
||||
img_y = (canvas_y - self.offset_y) / self.zoom_factor
|
||||
return img_x, img_y
|
||||
|
||||
def on_mouse_down(self, event):
|
||||
"""鼠标按下事件"""
|
||||
if self.draw_mode_var.get():
|
||||
# 绘制模式
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
if 0 <= img_x <= self.image_pil.size[0] and 0 <= img_y <= self.image_pil.size[1]:
|
||||
self.drawing = True
|
||||
self.start_x = img_x
|
||||
self.start_y = img_y
|
||||
else:
|
||||
# 选择模式 - 查找最近的标注
|
||||
self.find_nearest_annotation(event.x, event.y)
|
||||
|
||||
def find_nearest_annotation(self, canvas_x, canvas_y):
|
||||
"""查找最近的标注"""
|
||||
if not self.annotations:
|
||||
return
|
||||
|
||||
min_dist = float('inf')
|
||||
nearest_idx = -1
|
||||
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
# 转换坐标
|
||||
cx1 = x1 * self.zoom_factor + self.offset_x
|
||||
cy1 = y1 * self.zoom_factor + self.offset_y
|
||||
cx2 = x2 * self.zoom_factor + self.offset_x
|
||||
cy2 = y2 * self.zoom_factor + self.offset_y
|
||||
|
||||
# 计算到矩形边界的距离
|
||||
if cx1 <= canvas_x <= cx2 and cy1 <= canvas_y <= cy2:
|
||||
# 点在矩形内
|
||||
dist = 0
|
||||
else:
|
||||
# 计算到最近边的距离
|
||||
dx = max(cx1 - canvas_x, 0, canvas_x - cx2)
|
||||
dy = max(cy1 - canvas_y, 0, canvas_y - cy2)
|
||||
dist = (dx**2 + dy**2)**0.5
|
||||
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
nearest_idx = idx
|
||||
|
||||
# 如果距离足够近,选中该标注
|
||||
if min_dist < 20: # 20像素阈值
|
||||
self.selected_annotation = nearest_idx
|
||||
self.annotation_listbox.selection_clear(0, tk.END)
|
||||
self.annotation_listbox.selection_set(nearest_idx)
|
||||
self.annotation_listbox.see(nearest_idx)
|
||||
self.display_image()
|
||||
self.update_status(f"选中标注 #{nearest_idx + 1}")
|
||||
|
||||
def on_mouse_move(self, event):
|
||||
"""鼠标移动事件"""
|
||||
if self.drawing:
|
||||
# 绘制临时矩形
|
||||
if self.current_rect:
|
||||
self.canvas.delete(self.current_rect)
|
||||
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
canvas_x1 = self.start_x * self.zoom_factor + self.offset_x
|
||||
canvas_y1 = self.start_y * self.zoom_factor + self.offset_y
|
||||
canvas_x2 = img_x * self.zoom_factor + self.offset_x
|
||||
canvas_y2 = img_y * self.zoom_factor + self.offset_y
|
||||
|
||||
self.current_rect = self.canvas.create_rectangle(
|
||||
canvas_x1, canvas_y1, canvas_x2, canvas_y2,
|
||||
outline="green", width=2, dash=(5, 5)
|
||||
)
|
||||
|
||||
def on_mouse_up(self, event):
|
||||
"""鼠标释放事件"""
|
||||
if self.drawing:
|
||||
# 完成绘制
|
||||
img_x, img_y = self.canvas_to_image_coords(event.x, event.y)
|
||||
|
||||
# 确保坐标正确排序
|
||||
x1 = min(self.start_x, img_x)
|
||||
y1 = min(self.start_y, img_y)
|
||||
x2 = max(self.start_x, img_x)
|
||||
y2 = max(self.start_y, img_y)
|
||||
|
||||
# 检查矩形大小
|
||||
if x2 - x1 > 5 and y2 - y1 > 5:
|
||||
# 添加标注(只保存坐标,不保存标签)
|
||||
self.annotations.append((x1, y1, x2, y2))
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status(f"已添加标注 #{len(self.annotations)}")
|
||||
else:
|
||||
self.update_status("标注太小,已忽略")
|
||||
|
||||
# 清理绘制状态
|
||||
self.drawing = False
|
||||
if self.current_rect:
|
||||
self.canvas.delete(self.current_rect)
|
||||
self.current_rect = None
|
||||
|
||||
def on_pan_start(self, event):
|
||||
"""开始平移"""
|
||||
self.panning = True
|
||||
self.pan_start_x = event.x
|
||||
self.pan_start_y = event.y
|
||||
|
||||
def on_pan_move(self, event):
|
||||
"""平移移动"""
|
||||
if self.panning:
|
||||
dx = event.x - self.pan_start_x
|
||||
dy = event.y - self.pan_start_y
|
||||
self.offset_x += dx
|
||||
self.offset_y += dy
|
||||
self.pan_start_x = event.x
|
||||
self.pan_start_y = event.y
|
||||
self.display_image()
|
||||
|
||||
def on_pan_end(self, event):
|
||||
"""结束平移"""
|
||||
self.panning = False
|
||||
|
||||
def update_annotation_list(self):
|
||||
"""更新标注列表"""
|
||||
self.annotation_listbox.delete(0, tk.END)
|
||||
for idx, (x1, y1, x2, y2) in enumerate(self.annotations):
|
||||
width = x2 - x1
|
||||
height = y2 - y1
|
||||
list_text = f"{idx + 1}. 矩形 ({width:.0f}x{height:.0f})"
|
||||
self.annotation_listbox.insert(tk.END, list_text)
|
||||
|
||||
def on_annotation_select(self, event):
|
||||
"""标注列表选择事件"""
|
||||
selection = self.annotation_listbox.curselection()
|
||||
if selection:
|
||||
self.selected_annotation = selection[0]
|
||||
self.display_image()
|
||||
self.update_status(f"已选中标注 #{self.selected_annotation + 1}")
|
||||
|
||||
def delete_selected_annotation(self):
|
||||
"""删除选中的标注"""
|
||||
if self.selected_annotation >= 0 and self.selected_annotation < len(self.annotations):
|
||||
del self.annotations[self.selected_annotation]
|
||||
self.selected_annotation = -1
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status("已删除选中标注")
|
||||
|
||||
def clear_current_annotation(self):
|
||||
"""清除当前标注"""
|
||||
self.selected_annotation = -1
|
||||
self.display_image()
|
||||
self.update_status("已清除选中状态")
|
||||
|
||||
def clear_all_annotations(self):
|
||||
"""删除所有标注"""
|
||||
if messagebox.askyesno("确认", "确定要删除所有标注吗?"):
|
||||
self.annotations = []
|
||||
self.selected_annotation = -1
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status("已删除所有标注")
|
||||
|
||||
def save_annotations(self):
|
||||
"""保存标注到文件(仅TXT格式,纯坐标,格式:top right bottom left)"""
|
||||
if not self.current_image_path:
|
||||
messagebox.showwarning("警告", "请先加载图片")
|
||||
return
|
||||
|
||||
if not self.annotations:
|
||||
if not messagebox.askyesno("确认", "没有标注要保存,是否继续?"):
|
||||
return
|
||||
|
||||
try:
|
||||
# 获取图像尺寸
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
# 生成保存路径:图片文件名.txt(同目录下)
|
||||
image_dir = os.path.dirname(self.current_image_path)
|
||||
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
|
||||
file_path = os.path.join(image_dir, f"{image_basename}.txt")
|
||||
|
||||
# 保存标注(纯坐标格式,top right bottom left)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
for x1, y1, x2, y2 in self.annotations:
|
||||
# 转换为百分比坐标(top, right, bottom, left)
|
||||
top_pct = y1 / img_height
|
||||
right_pct = x2 / img_width
|
||||
bottom_pct = y2 / img_height
|
||||
left_pct = x1 / img_width
|
||||
|
||||
# 写入坐标数据(保留6位小数,空格分隔)
|
||||
f.write(f"{top_pct:.6f} {right_pct:.6f} {bottom_pct:.6f} {left_pct:.6f}\n")
|
||||
|
||||
self.update_status(f"已保存 {len(self.annotations)} 个标注到 {os.path.basename(file_path)}")
|
||||
messagebox.showinfo("成功", f"标注已保存到:\n{file_path}\n\n格式: top right bottom left (百分比坐标)")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"保存失败: {e}")
|
||||
|
||||
def load_annotations(self):
|
||||
"""加载标注文件(TXT格式,纯坐标,格式:top right bottom left)"""
|
||||
if not self.current_image_path:
|
||||
messagebox.showwarning("警告", "请先加载图片")
|
||||
return
|
||||
|
||||
# 默认加载同目录下的同名txt文件
|
||||
image_dir = os.path.dirname(self.current_image_path)
|
||||
image_basename = os.path.splitext(os.path.basename(self.current_image_path))[0]
|
||||
default_file = os.path.join(image_dir, f"{image_basename}.txt")
|
||||
|
||||
if os.path.exists(default_file):
|
||||
file_path = default_file
|
||||
else:
|
||||
file_path = filedialog.askopenfilename(
|
||||
title="选择标注文件",
|
||||
initialdir=image_dir,
|
||||
filetypes=[
|
||||
("文本文件", "*.txt"),
|
||||
("所有文件", "*.*")
|
||||
]
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
self.load_txt_annotations(file_path)
|
||||
self.update_annotation_list()
|
||||
self.display_image()
|
||||
self.update_status(f"已加载 {len(self.annotations)} 个标注")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"加载失败: {e}")
|
||||
|
||||
def load_txt_annotations(self, file_path):
|
||||
"""加载TXT格式的标注文件(纯坐标格式,top right bottom left)"""
|
||||
self.annotations = []
|
||||
img_width, img_height = self.image_pil.size
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
for line_num, line in enumerate(f, 1):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split()
|
||||
if len(parts) == 4:
|
||||
try:
|
||||
top_pct = float(parts[0])
|
||||
right_pct = float(parts[1])
|
||||
bottom_pct = float(parts[2])
|
||||
left_pct = float(parts[3])
|
||||
|
||||
# 验证百分比坐标范围
|
||||
if all(0 <= v <= 1 for v in [top_pct, right_pct, bottom_pct, left_pct]):
|
||||
# 转换为绝对坐标
|
||||
x1 = left_pct * img_width
|
||||
y1 = top_pct * img_height
|
||||
x2 = right_pct * img_width
|
||||
y2 = bottom_pct * img_height
|
||||
|
||||
self.annotations.append((x1, y1, x2, y2))
|
||||
else:
|
||||
print(f"警告: 第{line_num}行坐标超出范围 [0,1]: {line}")
|
||||
except ValueError as e:
|
||||
print(f"警告: 第{line_num}行格式错误: {line}, 错误: {e}")
|
||||
else:
|
||||
print(f"警告: 第{line_num}行格式错误,需要4个坐标值: {line}")
|
||||
|
||||
def update_info(self):
|
||||
"""更新信息显示"""
|
||||
if self.current_image_path and self.image_pil:
|
||||
img_width, img_height = self.image_pil.size
|
||||
filename = os.path.basename(self.current_image_path)
|
||||
self.info_label.config(text=f"图片: {filename}\n尺寸: {img_width}x{img_height}\n标注: {len(self.annotations)}个")
|
||||
else:
|
||||
self.info_label.config(text="未加载图片")
|
||||
|
||||
def update_status(self, message=None):
|
||||
"""更新状态栏"""
|
||||
if message:
|
||||
self.status_label.config(text=f"状态: {message}")
|
||||
else:
|
||||
self.status_label.config(text="状态: 就绪")
|
||||
|
||||
def update(self):
|
||||
"""更新界面"""
|
||||
self.update_info()
|
||||
self.root.after(1000, self.update)
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
root = tk.Tk()
|
||||
app = ImageAnnotationTool(root)
|
||||
app.update()
|
||||
root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,82 @@
|
||||
#include <stdio.h>
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
|
||||
int main() {
|
||||
printf("========== Lua Type Constants ==========\n");
|
||||
printf("LUA_TNONE = %d\n", LUA_TNONE);
|
||||
printf("LUA_TNIL = %d\n", LUA_TNIL);
|
||||
printf("LUA_TBOOLEAN = %d\n", LUA_TBOOLEAN);
|
||||
printf("LUA_TLIGHTUSERDATA = %d\n", LUA_TLIGHTUSERDATA);
|
||||
printf("LUA_TNUMBER = %d\n", LUA_TNUMBER);
|
||||
printf("LUA_TSTRING = %d\n", LUA_TSTRING);
|
||||
printf("LUA_TTABLE = %d\n", LUA_TTABLE);
|
||||
printf("LUA_TFUNCTION = %d\n", LUA_TFUNCTION);
|
||||
printf("LUA_TUSERDATA = %d\n", LUA_TUSERDATA);
|
||||
printf("LUA_TTHREAD = %d\n", LUA_TTHREAD);
|
||||
|
||||
printf("\n========== Testing lua_type() ==========\n");
|
||||
|
||||
// 创建 Lua 状态机
|
||||
lua_State *L = luaL_newstate();
|
||||
|
||||
if (L == NULL) {
|
||||
printf("Failed to create Lua state\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 测试各种类型压栈后的 lua_type 返回值
|
||||
printf("\n1. 压入 nil:\n");
|
||||
lua_pushnil(L);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TNIL = %d)\n", lua_type(L, -1), LUA_TNIL);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n2. 压入 boolean (true):\n");
|
||||
lua_pushboolean(L, 1);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TBOOLEAN = %d)\n", lua_type(L, -1), LUA_TBOOLEAN);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n3. 压入 number:\n");
|
||||
lua_pushnumber(L, 3.14159);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TNUMBER = %d)\n", lua_type(L, -1), LUA_TNUMBER);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n4. 压入 string:\n");
|
||||
lua_pushstring(L, "hello");
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TSTRING = %d)\n", lua_type(L, -1), LUA_TSTRING);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n5. 创建并压入 table:\n");
|
||||
lua_newtable(L);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TTABLE = %d)\n", lua_type(L, -1), LUA_TTABLE);
|
||||
lua_pop(L, 1);
|
||||
|
||||
printf("\n6. 压入函数 (C 函数):\n");
|
||||
lua_pushcfunction(L, lua_gettop);
|
||||
printf(" lua_type(L, -1) = %d (应为 LUA_TFUNCTION = %d)\n", lua_type(L, -1), LUA_TFUNCTION);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// 测试无效索引
|
||||
printf("\n7. 测试无效索引:\n");
|
||||
printf(" lua_type(L, 100) = %d (应为 LUA_TNONE = %d)\n", lua_type(L, 100), LUA_TNONE);
|
||||
|
||||
// 测试 lua_typename 函数
|
||||
printf("\n========== Testing lua_typename() ==========\n");
|
||||
printf("LUA_TTABLE 的类型名称: \"%s\"\n", lua_typename(L, LUA_TTABLE));
|
||||
printf("所有类型常量对应的名称:\n");
|
||||
printf(" LUA_TNONE -> \"%s\"\n", lua_typename(L, LUA_TNONE));
|
||||
printf(" LUA_TNIL -> \"%s\"\n", lua_typename(L, LUA_TNIL));
|
||||
printf(" LUA_TBOOLEAN -> \"%s\"\n", lua_typename(L, LUA_TBOOLEAN));
|
||||
printf(" LUA_TNUMBER -> \"%s\"\n", lua_typename(L, LUA_TNUMBER));
|
||||
printf(" LUA_TSTRING -> \"%s\"\n", lua_typename(L, LUA_TSTRING));
|
||||
printf(" LUA_TTABLE -> \"%s\"\n", lua_typename(L, LUA_TTABLE));
|
||||
printf(" LUA_TFUNCTION -> \"%s\"\n", lua_typename(L, LUA_TFUNCTION));
|
||||
printf(" LUA_TUSERDATA -> \"%s\"\n", lua_typename(L, LUA_TUSERDATA));
|
||||
printf(" LUA_TTHREAD -> \"%s\"\n", lua_typename(L, LUA_TTHREAD));
|
||||
|
||||
// 关闭 Lua 状态机
|
||||
lua_close(L);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <linux/spi/spidev.h>
|
||||
int main () {
|
||||
printf("int: %lu\n", sizeof(int));
|
||||
|
||||
printf("O_RDWR: %i\n", O_RDWR);
|
||||
|
||||
printf("pthread_t: %lu\n", sizeof(pthread_t));
|
||||
printf("pthread_mutex_t: %lu\n", sizeof(pthread_mutex_t));
|
||||
printf("pthread_mutexattr_t: %lu\n", sizeof(pthread_mutexattr_t));
|
||||
|
||||
printf("SPI_IOC_MAGIC: 0x%02X ('%c')\n", SPI_IOC_MAGIC, SPI_IOC_MAGIC);
|
||||
printf("SPI_IOC_WR_MODE: 0x%08X\n", SPI_IOC_WR_MODE);
|
||||
printf("SPI_IOC_RD_MODE: 0x%08X\n", SPI_IOC_RD_MODE);
|
||||
printf("SPI_IOC_WR_BITS_PER_WORD = 0x%08X\n", SPI_IOC_WR_BITS_PER_WORD);
|
||||
printf("SPI_IOC_RD_BITS_PER_WORD = 0x%08X\n", SPI_IOC_RD_BITS_PER_WORD);
|
||||
printf("SPI_IOC_WR_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_WR_MAX_SPEED_HZ);
|
||||
printf("SPI_IOC_RD_MAX_SPEED_HZ: 0x%08X\n", SPI_IOC_RD_MAX_SPEED_HZ);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
print("Hello from script file!")
|
||||
name = "Lua"
|
||||
print("Welcome to " .. name)
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 接收一个参数(表),返回另一个表
|
||||
function process(data)
|
||||
local name = data.name
|
||||
local age = data.age
|
||||
local city = data.city
|
||||
|
||||
-- 处理逻辑
|
||||
-- 修改:将 %d 改为 %s,因为 age 实际传入的是字符串
|
||||
local result_message = string.format("Hello %s, age %s from %s", name, age, city)
|
||||
|
||||
-- 返回字典
|
||||
return {
|
||||
result = "success",
|
||||
message = result_message,
|
||||
code = 200
|
||||
}
|
||||
end
|
||||
|
||||
-- 脚本主体:接收传入的参数,调用处理函数,返回结果
|
||||
-- 注意:传入的参数在 ... 中(可变参数)
|
||||
local input = ... -- 获取第一个参数(我们的字典)
|
||||
return process(input)
|
||||
Reference in new issue
Block a user