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>
446 lines
15 KiB
C++
446 lines
15 KiB
C++
#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;
|
||
}
|