Rdzleo 93f0e19d1d 初始化项目:精灵吊坠 RTC 语音助手 + VEML7700 石头同频匹配
ESP32-S3 吊坠设备固件,集成火山引擎 RTC 语音助手、蓝牙配网、
VEML7700 环境光传感器驱动及石头同频匹配交友功能。

VEML7700 驱动:
- 基于 ESP-IDF i2c_master API 实现,复用项目 I2cDevice 基类
- 支持 ALS + White 双通道、自动量程、Vishay 非线性校正
- 3 次采样取中位数过滤偶发异常

石头同频匹配算法(双维度):
- 维度1:光谱比值 ALS/White(石头固有光学特征,不随光照强度变化)
- 维度2:亮度等级(5级对数划分,排除极端环境差异)
- 比值阈值 15%,实测同石头姿势变化波动 1.6%~9.6%,安全余量充足

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 11:43:57 +08:00

446 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "veml7700.h"
#include <cstring>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define TAG "VEML7700"
// ============================================================================
// 增益对应的系数表用于Lux计算
// ============================================================================
static const float gain_factors[] = {
1.0f, // GAIN_1 (x1)
2.0f, // GAIN_2 (x2)
0.125f, // GAIN_D8 (x1/8)
0.25f, // GAIN_D4 (x1/4)
};
// 积分时间对应的系数表
// 索引通过 it 枚举值映射
static float GetItFactor(veml7700_it_t it) {
switch (it) {
case VEML7700_IT_25MS: return 0.25f;
case VEML7700_IT_50MS: return 0.5f;
case VEML7700_IT_100MS: return 1.0f;
case VEML7700_IT_200MS: return 2.0f;
case VEML7700_IT_400MS: return 4.0f;
case VEML7700_IT_800MS: return 8.0f;
default: return 1.0f;
}
}
// 积分时间对应的等待时间ms
static uint32_t GetItDelayMs(veml7700_it_t it) {
switch (it) {
case VEML7700_IT_25MS: return 25;
case VEML7700_IT_50MS: return 50;
case VEML7700_IT_100MS: return 100;
case VEML7700_IT_200MS: return 200;
case VEML7700_IT_400MS: return 400;
case VEML7700_IT_800MS: return 800;
default: return 100;
}
}
// ============================================================================
// 构造函数
// ============================================================================
VEML7700::VEML7700(i2c_master_bus_handle_t i2c_bus, uint8_t addr)
: I2cDevice(i2c_bus, addr),
gain_(VEML7700_GAIN_1),
it_(VEML7700_IT_100MS),
pers_(VEML7700_PERS_1),
int_enable_(false),
shutdown_(true),
als_conf_(0x0001) // 默认关机状态
{
}
// ============================================================================
// 16-bit 寄存器读写
// VEML7700 协议:写 = [cmd] + [data_low] + [data_high]
// 读 = [cmd] → [data_low] + [data_high]
// ============================================================================
esp_err_t VEML7700::WriteReg16(uint8_t cmd, uint16_t value) {
uint8_t buffer[3] = {
cmd,
(uint8_t)(value & 0xFF), // 低字节
(uint8_t)((value >> 8) & 0xFF) // 高字节
};
esp_err_t ret = i2c_master_transmit(i2c_device_, buffer, 3, 100);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "写寄存器 0x%02X 失败: %s", cmd, esp_err_to_name(ret));
}
return ret;
}
esp_err_t VEML7700::ReadReg16(uint8_t cmd, uint16_t* value) {
uint8_t rx[2] = {0};
esp_err_t ret = i2c_master_transmit_receive(i2c_device_, &cmd, 1, rx, 2, 100);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "读寄存器 0x%02X 失败: %s", cmd, esp_err_to_name(ret));
*value = 0;
return ret;
}
*value = (uint16_t)(rx[0] | (rx[1] << 8));
return ESP_OK;
}
// ============================================================================
// 更新ALS配置寄存器
// ALS_CONF 位域:
// [15:13] 保留
// [12:11] ALS_GAIN
// [10] 保留
// [9:6] ALS_IT
// [5:4] ALS_PERS
// [3] 保留
// [2] ALS_INT_EN
// [1] 保留
// [0] ALS_SD (1=关机, 0=运行)
// ============================================================================
esp_err_t VEML7700::UpdateALSConf() {
als_conf_ = 0;
als_conf_ |= ((uint16_t)gain_ & 0x03) << 11;
als_conf_ |= ((uint16_t)it_ & 0x0F) << 6;
als_conf_ |= ((uint16_t)pers_ & 0x03) << 4;
als_conf_ |= int_enable_ ? (1 << 2) : 0;
als_conf_ |= shutdown_ ? 1 : 0;
return WriteReg16(VEML7700_REG_ALS_CONF, als_conf_);
}
// ============================================================================
// 初始化
// ============================================================================
esp_err_t VEML7700::Init() {
ESP_LOGI(TAG, "初始化 VEML7700 环境光传感器...");
// 先关机再配置datasheet建议
shutdown_ = true;
esp_err_t ret = UpdateALSConf();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "VEML7700 初始化失败I2C通信错误");
return ret;
}
// 设置默认参数增益x1积分时间100ms
gain_ = VEML7700_GAIN_1;
it_ = VEML7700_IT_100MS;
pers_ = VEML7700_PERS_1;
int_enable_ = false;
// 关闭省电模式
ret = SetPowerSaving(VEML7700_PSM_1, false);
if (ret != ESP_OK) return ret;
// 唤醒传感器
shutdown_ = false;
ret = UpdateALSConf();
if (ret != ESP_OK) return ret;
// 等待首次采样完成
vTaskDelay(pdMS_TO_TICKS(GetSampleDelayMs() + 10));
ESP_LOGI(TAG, "VEML7700 初始化成功 (增益:x1, 积分时间:100ms)");
return ESP_OK;
}
// ============================================================================
// 配置接口
// ============================================================================
esp_err_t VEML7700::SetGain(veml7700_gain_t gain) {
gain_ = gain;
return UpdateALSConf();
}
esp_err_t VEML7700::SetIntegrationTime(veml7700_it_t it) {
it_ = it;
return UpdateALSConf();
}
esp_err_t VEML7700::SetPersistence(veml7700_pers_t pers) {
pers_ = pers;
return UpdateALSConf();
}
esp_err_t VEML7700::SetInterruptEnable(bool enable) {
int_enable_ = enable;
return UpdateALSConf();
}
esp_err_t VEML7700::Shutdown(bool sd) {
shutdown_ = sd;
return UpdateALSConf();
}
esp_err_t VEML7700::SetPowerSaving(veml7700_psm_t mode, bool enable) {
uint16_t psm_val = 0;
psm_val |= ((uint16_t)mode & 0x03) << 1;
psm_val |= enable ? 1 : 0;
return WriteReg16(VEML7700_REG_PSM, psm_val);
}
esp_err_t VEML7700::SetThresholdHigh(uint16_t threshold) {
return WriteReg16(VEML7700_REG_ALS_WH, threshold);
}
esp_err_t VEML7700::SetThresholdLow(uint16_t threshold) {
return WriteReg16(VEML7700_REG_ALS_WL, threshold);
}
// ============================================================================
// 数据读取
// ============================================================================
esp_err_t VEML7700::ReadALSRaw(uint16_t* raw) {
return ReadReg16(VEML7700_REG_ALS, raw);
}
esp_err_t VEML7700::ReadWhiteRaw(uint16_t* raw) {
return ReadReg16(VEML7700_REG_WHITE, raw);
}
// ============================================================================
// 分辨率计算
// 基准分辨率增益x1 + 积分时间100ms = 0.0576 lux/count
// 其他配置按比例缩放
// ============================================================================
float VEML7700::GetResolution() const {
float base_resolution = 0.0576f; // lux/count @ gain=1, it=100ms
float g = gain_factors[gain_];
float t = GetItFactor(it_);
return base_resolution / (g * t);
}
// ============================================================================
// 非线性校正Vishay应用笔记推荐的多项式补偿
// 当 Lux > 1000 时,传感器响应偏离线性,需要校正
// ============================================================================
float VEML7700::CorrectLux(float lux) const {
// Vishay官方多项式校正公式Horner法
// lux_corrected = lux * (1.0023 + lux * (8.1488e-5 + lux * (-9.3924e-9 + lux * 6.0135e-13)))
return lux * (1.0023f + lux * (8.1488e-5f + lux * (-9.3924e-9f + lux * 6.0135e-13f)));
}
esp_err_t VEML7700::ReadALSLux(float* lux) {
uint16_t raw = 0;
esp_err_t ret = ReadALSRaw(&raw);
if (ret != ESP_OK) {
*lux = 0;
return ret;
}
float raw_lux = (float)raw * GetResolution();
*lux = CorrectLux(raw_lux);
return ESP_OK;
}
esp_err_t VEML7700::ReadWhiteLux(float* lux) {
uint16_t raw = 0;
esp_err_t ret = ReadWhiteRaw(&raw);
if (ret != ESP_OK) {
*lux = 0;
return ret;
}
float raw_lux = (float)raw * GetResolution();
*lux = CorrectLux(raw_lux);
return ESP_OK;
}
esp_err_t VEML7700::ReadAll(veml7700_data_t* data) {
esp_err_t ret = ReadALSRaw(&data->als_raw);
if (ret != ESP_OK) return ret;
ret = ReadWhiteRaw(&data->white_raw);
if (ret != ESP_OK) return ret;
float resolution = GetResolution();
float als_raw_lux = (float)data->als_raw * resolution;
float white_raw_lux = (float)data->white_raw * resolution;
data->als_lux = CorrectLux(als_raw_lux);
data->white_lux = CorrectLux(white_raw_lux);
return ESP_OK;
}
// ============================================================================
// 中断状态读取
// ============================================================================
esp_err_t VEML7700::ReadInterruptStatus(bool* high_triggered, bool* low_triggered) {
uint16_t status = 0;
esp_err_t ret = ReadReg16(VEML7700_REG_ALS_INT, &status);
if (ret != ESP_OK) return ret;
*high_triggered = (status >> 14) & 0x01;
*low_triggered = (status >> 15) & 0x01;
return ESP_OK;
}
// ============================================================================
// 获取采样等待时间
// ============================================================================
uint32_t VEML7700::GetSampleDelayMs() const {
return GetItDelayMs(it_);
}
// ============================================================================
// 自动量程实现
// 算法参考 Vishay 应用笔记和 tedyapo/arduino-VEML7700 驱动
//
// 策略:从高灵敏度(长积分+高增益)开始测量
// - 如果计数值太高(>10000接近饱和降低积分时间
// - 如果计数值太低(<200精度不够提高灵敏度
// - 找到计数值在 200~10000 之间的最优参数组合
//
// 增益遍历顺序(灵敏度从高到低): x2 → x1 → x(1/4) → x(1/8)
// 积分时间遍历顺序从100ms开始往上探再往下降
// ============================================================================
// 增益搜索顺序:灵敏度从高到低
static const veml7700_gain_t kGainOrder[] = {
VEML7700_GAIN_2, VEML7700_GAIN_1, VEML7700_GAIN_D4, VEML7700_GAIN_D8
};
static const int kGainOrderCount = sizeof(kGainOrder) / sizeof(kGainOrder[0]);
// 积分时间搜索顺序从100ms开始向上到800ms
static const veml7700_it_t kItOrderUp[] = {
VEML7700_IT_100MS, VEML7700_IT_200MS, VEML7700_IT_400MS, VEML7700_IT_800MS
};
static const int kItOrderUpCount = sizeof(kItOrderUp) / sizeof(kItOrderUp[0]);
// 积分时间搜索顺序从100ms开始向下到25ms
static const veml7700_it_t kItOrderDown[] = {
VEML7700_IT_100MS, VEML7700_IT_50MS, VEML7700_IT_25MS
};
static const int kItOrderDownCount = sizeof(kItOrderDown) / sizeof(kItOrderDown[0]);
// 自动量程计数阈值
static const uint16_t kAutoCountLow = 200; // 低于此值精度不足
static const uint16_t kAutoCountHigh = 10000; // 高于此值接近饱和
esp_err_t VEML7700::ApplyConfigAndWait(veml7700_gain_t gain, veml7700_it_t it) {
// 先关机
shutdown_ = true;
gain_ = gain;
it_ = it;
esp_err_t ret = UpdateALSConf();
if (ret != ESP_OK) return ret;
// 开机
shutdown_ = false;
ret = UpdateALSConf();
if (ret != ESP_OK) return ret;
// 等待 2 倍积分时间确保新数据就绪datasheet 建议)
vTaskDelay(pdMS_TO_TICKS(GetItDelayMs(it) * 2 + 10));
return ESP_OK;
}
esp_err_t VEML7700::AutoRangeMeasure(ReadRawFunc read_func, veml7700_auto_data_t* result) {
uint16_t counts = 0;
esp_err_t ret;
// 阶段1从中等灵敏度开始100ms + x2增益逐步提高积分时间
for (int it_idx = 0; it_idx < kItOrderUpCount; it_idx++) {
for (int g_idx = 0; g_idx < kGainOrderCount; g_idx++) {
ret = ApplyConfigAndWait(kGainOrder[g_idx], kItOrderUp[it_idx]);
if (ret != ESP_OK) return ret;
ret = (this->*read_func)(&counts);
if (ret != ESP_OK) return ret;
// 计数值在合理范围内,直接返回
if (counts >= kAutoCountLow && counts <= kAutoCountHigh) {
goto done;
}
// 计数值太高,需要降低灵敏度
if (counts > kAutoCountHigh) {
// 尝试降低积分时间
goto reduce_sensitivity;
}
// 计数值太低,尝试下一个更高的增益(循环继续)
}
}
// 用最高灵敏度仍然太低,直接用当前结果(弱光环境)
goto done;
reduce_sensitivity:
// 阶段2计数值过高逐步缩短积分时间
for (int it_idx = 1; it_idx < kItOrderDownCount; it_idx++) {
ret = ApplyConfigAndWait(gain_, kItOrderDown[it_idx]);
if (ret != ESP_OK) return ret;
ret = (this->*read_func)(&counts);
if (ret != ESP_OK) return ret;
if (counts <= kAutoCountHigh) {
goto done;
}
}
// 最短积分时间 + 当前增益仍然太高,降低增益
for (int g_idx = 0; g_idx < kGainOrderCount; g_idx++) {
if (kGainOrder[g_idx] == gain_) continue; // 跳过当前增益
// 只尝试更低灵敏度的增益
float current_factor = gain_factors[gain_];
float try_factor = gain_factors[kGainOrder[g_idx]];
if (try_factor >= current_factor) continue;
ret = ApplyConfigAndWait(kGainOrder[g_idx], VEML7700_IT_25MS);
if (ret != ESP_OK) return ret;
ret = (this->*read_func)(&counts);
if (ret != ESP_OK) return ret;
if (counts <= kAutoCountHigh) {
goto done;
}
}
// 最低灵敏度仍然饱和,用当前结果(极强光环境)
done:
result->raw = counts;
result->gain = gain_;
result->it = it_;
float resolution = GetResolution();
float raw_lux = (float)counts * resolution;
result->lux = CorrectLux(raw_lux);
ESP_LOGI(TAG, "自动量程完成: gain=%d, it=%d, raw=%u, lux=%.2f",
gain_, it_, counts, result->lux);
return ESP_OK;
}
esp_err_t VEML7700::ReadAutoALSLux(veml7700_auto_data_t* result) {
return AutoRangeMeasure(&VEML7700::ReadALSRaw, result);
}
esp_err_t VEML7700::ReadAutoWhiteLux(veml7700_auto_data_t* result) {
return AutoRangeMeasure(&VEML7700::ReadWhiteRaw, result);
}
esp_err_t VEML7700::ReadAutoAll(veml7700_auto_data_t* als_result, veml7700_auto_data_t* white_result) {
// 先用ALS通道做自动量程找到最优参数
esp_err_t ret = AutoRangeMeasure(&VEML7700::ReadALSRaw, als_result);
if (ret != ESP_OK) return ret;
// 用同一组参数直接读取White通道不再重新调参避免重复等待
uint16_t white_raw = 0;
ret = ReadWhiteRaw(&white_raw);
if (ret != ESP_OK) return ret;
white_result->raw = white_raw;
white_result->gain = gain_;
white_result->it = it_;
float resolution = GetResolution();
float raw_lux = (float)white_raw * resolution;
white_result->lux = CorrectLux(raw_lux);
return ESP_OK;
}