16static constexpr uint8_t REG_ENABLE = 0x80;
17static constexpr uint8_t REG_ATIME = 0x81;
18static constexpr uint8_t REG_ASTATUS = 0x94;
19static constexpr uint8_t ENABLE_PON = 0x01;
20static constexpr uint8_t ENABLE_SP_EN = 0x02;
21static constexpr uint8_t ENABLE_SMUX_EN = 0x10;
22static constexpr uint8_t CFG0_REG_BANK = 0x10;
23static constexpr uint8_t BANK_SPLIT = 0x80;
24static constexpr uint8_t STATUS2_AVALID = 0x40;
25static constexpr uint8_t ASTATUS_ASAT = 0x80;
26static constexpr uint8_t LED_ACT = 0x80;
27static constexpr uint8_t CONFIG_LED_SEL = 0x08;
29static constexpr uint8_t AS7341_CFG6 = 0xAF;
30static constexpr uint8_t AS7341_SMUX_CMD_WRITE = 0x10;
31static constexpr uint8_t AS7341_SMUX_RAM = 0x00;
32static constexpr uint8_t AS7343_CHAIN_CMD = 0xE4;
33static constexpr uint8_t AS7343_CHAIN_SMUX = 0xE7;
34static constexpr uint8_t AS7343_CHAINS = 3;
37template<
typename T>
static T pgm(
const T &field) {
43static constexpr uint8_t NO_SLOT = 0xFF;
44static constexpr uint8_t CLEAR_BAND = NUM_BANDS - 1;
45static constexpr uint8_t MAX_SMUX_LEN = 20;
46static constexpr uint8_t MAX_ADC_SLOTS = 18;
47static constexpr uint32_t MIN_COLLECTION_TIMEOUT_MS = 30 * 1000;
48static constexpr uint32_t MAX_ADC_COUNT = 65535;
50PROGMEM_STRING_TABLE(BandNames,
"F1",
"F2",
"FZ",
"F3",
"F4",
"FY",
"F5",
"FXL",
"F6",
"F7",
"F8",
"NIR",
"Clear");
53static const char AS7341_NAME[] PROGMEM =
"AS7341";
54static constexpr uint8_t AS7341_ADC_SLOTS = 6;
55static constexpr uint8_t AS7341_SMUX_LEN = 20;
56static constexpr uint8_t AS7341_SMUX_STEPS = 2;
57static const RegValue AS7341_INIT[] PROGMEM = {{0x70, 0x00}};
58static const uint8_t AS7341_SMUX[] PROGMEM = {
59 0x30, 0x01, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x50, 0x00,
60 0x00, 0x00, 0x20, 0x04, 0x00, 0x30, 0x01, 0x50, 0x00, 0x06,
61 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x10, 0x03, 0x50, 0x10,
62 0x03, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x50, 0x00, 0x06,
64static_assert(
sizeof(AS7341_SMUX) == AS7341_SMUX_STEPS * AS7341_SMUX_LEN && AS7341_SMUX_LEN <= MAX_SMUX_LEN);
65static constexpr uint8_t N = NO_SLOT;
66static const uint8_t AS7341_READOUT[] PROGMEM = {
67 0, 1, N, 2, 3, N, N, N, N, N, N, N, N,
68 N, N, N, N, N, N, 0, N, 1, 2, 3, 5, 4,
70static_assert(
sizeof(AS7341_READOUT) == AS7341_SMUX_STEPS * NUM_BANDS && AS7341_ADC_SLOTS <= MAX_ADC_SLOTS);
73static const uint16_t AS7341_WAVELENGTHS[] PROGMEM = {
74 415, 445, ABSENT_NM, 480, 515, ABSENT_NM, 555, ABSENT_NM, 590, 630, 680, 910, WIDEBAND_NM,
77static_assert(
sizeof(AS7341_WAVELENGTHS) == NUM_BANDS *
sizeof(uint16_t));
80static const float AS7341_GAIN_CORRECTION[] PROGMEM = {1.0577f, 1.0491f, 1.0479f, 1.0491f, 1.0207f, 1.0158f, 1.0109f,
81 1.0000f, 1.0003f, 0.9873f, 0.9593f, 0.0f, 0.0f};
82static_assert(
sizeof(AS7341_GAIN_CORRECTION) == GAIN_COUNT *
sizeof(
float));
85static const ChannelContribution AS7341_CONTRIBUTIONS[] PROGMEM = {
86 {0.005261294f, 5.936230324f, 0.020518273f},
87 {0.242615252f, 3.572528495f, 0.013525490f},
89 {0.174482552f, 2.914113437f, 0.011585145f},
90 {1.479499075f, 2.452851227f, 0.010604986f},
92 {1.906818555f, 1.940656433f, 0.008890531f},
94 {1.162058142f, 1.533976606f, 0.007441544f},
95 {0.462374845f, 1.819838250f, 0.009393590f},
96 {-0.017794572f, 1.232942274f, 0.006890812f},
97 {-0.260730901f, -1.102368000f, -0.004647911f},
98 {-0.022604075f, -0.033304813f, -0.000129106f},
100static const ChannelTristimulus AS7341_TRISTIMULUS[] PROGMEM = {
101 {0.39814f, 0.01396f, 1.95010f},
102 {1.29540f, 0.16748f, 6.45490f},
104 {0.36956f, 0.23538f, 2.78010f},
105 {0.10902f, 1.42750f, 0.18501f},
107 {0.71942f, 1.88670f, 0.15325f},
109 {1.78180f, 1.14200f, 0.09539f},
110 {1.10110f, 0.46497f, 0.10563f},
111 {-0.03991f, -0.02702f, 0.08866f},
112 {-0.27597f, -0.24468f, -0.61140f},
113 {-0.02347f, -0.01993f, -0.00938f},
116static_assert(
sizeof(AS7341_CONTRIBUTIONS) == NUM_BANDS *
sizeof(ChannelContribution));
117static_assert(
sizeof(AS7341_TRISTIMULUS) == NUM_BANDS *
sizeof(ChannelTristimulus));
122 .readout = AS7341_READOUT,
123 .wavelengths = AS7341_WAVELENGTHS,
124 .gain_correction = AS7341_GAIN_CORRECTION,
125 .contributions = AS7341_CONTRIBUTIONS,
126 .tristimulus = AS7341_TRISTIMULUS,
134 .id_value = 0x09 << 2,
137 .adc_slots = AS7341_ADC_SLOTS,
138 .smux_steps = AS7341_SMUX_STEPS,
139 .integration_cycles = 1,
140 .smux_len = AS7341_SMUX_LEN,
142 .gain_correction_per_band =
false,
143 .extra_clear_slot = NO_SLOT,
148static const char AS7343_NAME[] PROGMEM =
"AS7343";
149static constexpr uint8_t AS7343_ADC_SLOTS = 18;
150static constexpr uint8_t AS7343_SMUX_LEN = 10;
151static const char TCS3448_NAME[] PROGMEM =
"TCS3448";
153static const RegValue AS7343_INIT[] PROGMEM = {{0xF5, 0x00}, {0xDF, 0xA1}, {0x65, 0xF2}, {0xC9, 0xC8},
154 {0xD6, 0x62}, {0xD7, 0x99}, {0xE0, 0x64}, {0xE2, 0x21}};
155static const uint8_t AS7343_SMUX[] PROGMEM = {0x00, 0x04, 0x65, 0x02, 0x00, 0x05, 0x00, 0x01, 0x00, 0x30,
156 0x00, 0x00, 0x60, 0x20, 0x04, 0x50, 0x03, 0x00, 0x01, 0x05,
157 0x05, 0x00, 0x60, 0x00, 0x30, 0x00, 0x40, 0x10, 0x20, 0x00};
159static const uint8_t AS7343_READOUT[] PROGMEM = {12, 6, 0, 7, 8, 1, 15, 2, 9, 13, 14, 3, 10};
160static_assert(
sizeof(AS7343_SMUX) == AS7343_CHAINS * AS7343_SMUX_LEN && AS7343_SMUX_LEN <= MAX_SMUX_LEN);
161static_assert(
sizeof(AS7343_READOUT) == NUM_BANDS && AS7343_ADC_SLOTS <= MAX_ADC_SLOTS);
163static const uint16_t AS7343_WAVELENGTHS[] PROGMEM = {
164 405, 425, 450, 475, 515, 555, 550, 600, 640, 690, 745, 855, WIDEBAND_NM,
167static_assert(
sizeof(AS7343_WAVELENGTHS) == NUM_BANDS *
sizeof(uint16_t));
170static const float AS7343_GAIN_CORRECTION[] PROGMEM = {
171 1.149f, 1.100f, 1.060f, 1.070f, 1.063f, 1.051f, 1.062f, 1.056f, 1.049f, 1.040f, 1.080f, 1.038f, 1.065f,
172 1.090f, 1.128f, 1.064f, 1.071f, 1.063f, 1.050f, 1.068f, 1.055f, 1.047f, 1.039f, 1.075f, 1.038f, 1.085f,
173 1.083f, 1.086f, 1.062f, 1.070f, 1.062f, 1.049f, 1.057f, 1.053f, 1.045f, 1.038f, 1.063f, 1.037f, 1.069f,
174 1.059f, 1.068f, 1.056f, 1.066f, 1.058f, 1.046f, 1.051f, 1.051f, 1.044f, 1.036f, 1.059f, 1.035f, 1.053f,
175 1.100f, 1.109f, 1.096f, 1.108f, 1.099f, 1.089f, 1.091f, 1.092f, 1.082f, 1.078f, 1.100f, 1.076f, 1.088f,
176 1.099f, 1.109f, 1.096f, 1.108f, 1.099f, 1.089f, 1.091f, 1.092f, 1.082f, 1.078f, 1.100f, 1.075f, 1.087f,
177 1.088f, 1.096f, 1.085f, 1.097f, 1.087f, 1.078f, 1.079f, 1.080f, 1.071f, 1.067f, 1.087f, 1.064f, 1.076f,
178 1.083f, 1.091f, 1.078f, 1.090f, 1.079f, 1.072f, 1.072f, 1.073f, 1.064f, 1.062f, 1.080f, 1.057f, 1.069f,
179 1.076f, 1.084f, 1.072f, 1.085f, 1.074f, 1.066f, 1.062f, 1.067f, 1.055f, 1.056f, 1.074f, 1.051f, 1.061f,
180 1.067f, 1.074f, 1.063f, 1.075f, 1.064f, 1.059f, 1.055f, 1.058f, 1.049f, 1.051f, 1.064f, 1.044f, 1.053f,
181 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f, 1.000f,
182 1.033303857f, 1.034763813f, 1.029855013f, 1.036911249f, 0.988150537f, 1.029003501f, 1.021363258f,
183 0.98762089f, 1.021713376f, 1.040108204f, 0.987417698f, 1.019481421f, 0.987270057f,
184 0.986859143f, 0.980877221f, 0.9469769f, 0.985535324f, 0.926243961f, 0.959814787f, 1.007963896f,
185 0.942685187f, 1.007630587f, 1.025460005f, 0.928696275f, 1.001296639f, 1.041275263f,
188static_assert(
sizeof(AS7343_GAIN_CORRECTION) == GAIN_COUNT * NUM_BANDS *
sizeof(
float));
190static const ChannelContribution AS7343_CONTRIBUTIONS[] PROGMEM = {
191 {-0.028559089f, 0.450293995f, 0.001266925f},
192 {-0.042150948f, 3.566379984f, 0.012515925f},
193 {0.000661721f, 2.877424672f, 0.010243005f},
194 {-0.108791278f, 2.273837581f, 0.009376157f},
195 {-0.167172180f, 1.519298503f, 0.006017556f},
196 {5.726660294f, 4.199345859f, 0.019357905f},
198 {-0.241492014f, 1.185259160f, 0.006516325f},
199 {-0.047665296f, 1.759469826f, 0.009780714f},
200 {-0.012516990f, 1.702006611f, 0.009950228f},
201 {-0.026424974f, -1.059196137f, -0.005425656f},
202 {-0.007565221f, -0.064827881f, -0.000232267f},
205static const ChannelTristimulus AS7343_TRISTIMULUS[] PROGMEM = {
206 {-0.07879f, -0.03269f, -0.31295f},
207 {-0.12235f, -0.01297f, -0.57885f},
208 {1.99879f, -0.04011f, 10.00197f},
209 {-0.33364f, -0.06889f, -0.31281f},
210 {-0.06795f, -0.17453f, -0.19657f},
211 {-0.45419f, 5.69083f, -0.11077f},
213 {5.27242f, -0.22956f, -0.06132f},
214 {-0.05072f, -0.04762f, -0.03536f},
215 {-0.04666f, -0.01295f, -0.06025f},
216 {-0.03931f, -0.02797f, -0.12174f},
221static_assert(
sizeof(AS7343_CONTRIBUTIONS) == NUM_BANDS *
sizeof(ChannelContribution));
222static_assert(
sizeof(AS7343_TRISTIMULUS) == NUM_BANDS *
sizeof(ChannelTristimulus));
223static constexpr ChipInfo as7343_chip(
const char *name) {
228 .readout = AS7343_READOUT,
229 .wavelengths = AS7343_WAVELENGTHS,
230 .gain_correction = AS7343_GAIN_CORRECTION,
231 .contributions = AS7343_CONTRIBUTIONS,
232 .tristimulus = AS7343_TRISTIMULUS,
233 .init_count =
sizeof(AS7343_INIT) /
sizeof(RegValue),
243 .adc_slots = AS7343_ADC_SLOTS,
245 .integration_cycles = 3,
246 .smux_len = AS7343_SMUX_LEN,
248 .gain_correction_per_band =
true,
249 .extra_clear_slot = 4,
252const ChipInfo
AS7343_CHIP PROGMEM = as7343_chip(AS7343_NAME);
253const ChipInfo
TCS3448_CHIP PROGMEM = as7343_chip(TCS3448_NAME);
258 const bool low =
reg < BANK_SPLIT;
271 data = value ? (data | mask) : (data & ~mask);
296 const uint8_t id_reg = pgm(chip->
id_reg);
307 for (uint8_t i = 0, n = pgm(chip->
init_count); ok && i < n; i++) {
315 const uint8_t *smux = pgm(chip->
smux);
317 for (uint8_t i = 0; ok && i < AS7343_CHAINS *
len; i++) {
337 " Glass attenuation factor: %.3f",
338 LOG_STR_ARG(pgm(this->
chip_->
name)), gain, this->atime_, this->astep_, this->glass_attenuation_factor_);
339 LOG_I2C_DEVICE(
this);
340 LOG_UPDATE_INTERVAL(
this);
341 for (uint8_t band = 0; band < NUM_BANDS; band++) {
343 const char *name = LOG_STR_ARG(BandNames::get_log_str(band, BandNames::LAST_INDEX));
344 snprintf(label,
sizeof(label),
"Counts %s", name);
346 snprintf(label,
sizeof(label),
"Basic counts %s", name);
349 LOG_SENSOR(
" ",
"Saturation level", this->saturation_level_sensor_);
350 LOG_SENSOR(
" ",
"Illuminance", this->illuminance_sensor_);
351 LOG_SENSOR(
" ",
"Irradiance photopic", this->irradiance_photopic_sensor_);
352 LOG_SENSOR(
" ",
"Irradiance PAR", this->irradiance_par_sensor_);
353 LOG_SENSOR(
" ",
"PPFD", this->ppfd_sensor_);
354 LOG_SENSOR(
" ",
"Color temperature", this->color_temperature_sensor_);
370 if (enable && led_sel_reg != 0) {
371 ok = this->
update_bits_(led_sel_reg, CONFIG_LED_SEL,
true);
373 ok = this->
update_bits_(led_reg, LED_ACT, enable) && ok;
374 if (!enable && led_sel_reg != 0) {
375 ok = this->
update_bits_(led_sel_reg, CONFIG_LED_SEL,
false) && ok;
380 ESP_LOGW(TAG,
"Failed to switch the LED");
386 ESP_LOGW(TAG,
"Skipping update, previous measurement still running");
406 return this->
write_byte(REG_ENABLE, ENABLE_PON | ENABLE_SP_EN);
409 std::array<uint8_t, MAX_SMUX_LEN> image{};
413 return this->
write_byte(AS7341_CFG6, AS7341_SMUX_CMD_WRITE) &&
415 this->
write_byte(REG_ENABLE, ENABLE_PON | ENABLE_SMUX_EN);
421 switch (this->state_) {
424 if (this->
read_byte(REG_ENABLE, &value) && !(value & ENABLE_SMUX_EN)) {
427 if (!this->
write_byte(REG_ENABLE, ENABLE_PON | ENABLE_SP_EN)) {
453 std::array<uint8_t, 1 + 2 * MAX_ADC_SLOTS> frame{};
455 !this->write_byte(REG_ENABLE, ENABLE_PON)) {
459 if (frame[0] & ASTATUS_ASAT) {
460 ESP_LOGV(TAG,
"Step %u saturated", this->
step_);
462 auto slot_value = [&frame](uint8_t slot) {
return encode_uint16(frame[2 + 2 * slot], frame[1 + 2 * slot]); };
465 for (uint8_t band = 0; band < NUM_BANDS; band++) {
467 if (slot == NO_SLOT) {
470 const uint16_t
raw = slot_value(slot);
472 uint16_t counts =
raw;
473 if (band == CLEAR_BAND && extra_clear != NO_SLOT) {
474 const uint16_t extra = slot_value(extra_clear);
502 const float gain_x =
static_cast<float>(1 << this->
reading_gain_) / 2.0f;
504 if (gain_x <= 0.0f || integration_ms <= 0.0f) {
507 const float inv_exposure = 1.0f / (gain_x * integration_ms);
509 const uint16_t *wavelengths = pgm(chip->
wavelengths);
514 for (uint8_t band = 0; band < NUM_BANDS; band++) {
515 const uint16_t wavelength = pgm(wavelengths[band]);
516 if (wavelength == ABSENT_NM) {
519 float basic_count = this->
raw_counts_[band] * inv_exposure;
521 basic_count = std::max(basic_count, 0.0f);
522 basic_count *= pgm(gain_correction[per_band ? correction_base + band : correction_base]);
528 if (wavelength == WIDEBAND_NM) {
530 }
else if (wavelength >= VISIBLE_MIN_NM && wavelength <= VISIBLE_MAX_NM) {
537 float divisor = 1.0f;
552 return divisor > 0.0f ? divisor : 1.0f;
560 for (uint8_t band = 0; band < NUM_BANDS; band++) {
572 float irradiance_photopic = 0.0f;
573 float irradiance_par = 0.0f;
577 for (uint8_t band = 0; band < NUM_BANDS; band++) {
583 ppfd += contribution.
ppfd * basic_count;
585 irradiance_photopic = std::max(irradiance_photopic, 0.0f);
586 irradiance_par = std::max(irradiance_par, 0.0f);
587 ppfd = std::max(ppfd, 0.0f) * 1e3f;
588 const float illuminance = irradiance_photopic * LUMENS_PER_WATT;
590 ESP_LOGV(TAG,
"Photopic irradiance %.4f, PAR %.4f, PPFD %.4f, illuminance %.2f lx", irradiance_photopic,
591 irradiance_par, ppfd, illuminance);
592 if (this->illuminance_sensor_ !=
nullptr) {
593 this->illuminance_sensor_->publish_state(illuminance);
595 if (this->irradiance_photopic_sensor_ !=
nullptr) {
596 this->irradiance_photopic_sensor_->publish_state(irradiance_photopic);
598 if (this->irradiance_par_sensor_ !=
nullptr) {
599 this->irradiance_par_sensor_->publish_state(irradiance_par);
601 if (this->ppfd_sensor_ !=
nullptr) {
602 this->ppfd_sensor_->publish_state(ppfd);
612 for (uint8_t band = 0; band < NUM_BANDS; band++) {
616 tri_x += tristimulus.
x * basic_count;
617 tri_y += tristimulus.
y * basic_count;
618 tri_z += tristimulus.
z * basic_count;
622 ESP_LOGV(TAG,
"XYZ %.4f, %.4f, %.4f -> CCT %.0f K", tri_x, tri_y, tri_z, color_temperature);
623 if (this->color_temperature_sensor_ !=
nullptr) {
624 this->color_temperature_sensor_->publish_state(color_temperature);
633 char rgb_hex[7] =
"000000";
634 const float sum = tri_x + tri_y + tri_z;
646 for (uint8_t band = 0; band < NUM_BANDS; band++) {
647 if (this->
sensors_[band] !=
nullptr) {
654 if (this->saturation_level_sensor_ !=
nullptr) {
657 const float level = max_adc == 0 ? 0.0f : 100.0f * this->
peak_raw_count_ / max_adc;
658 ESP_LOGV(TAG,
"Highest ADC count %u of %u (%.1f%%)", this->
peak_raw_count_,
static_cast<unsigned>(max_adc), level);
659 this->saturation_level_sensor_->publish_state(level);
664 ESP_LOGW(TAG,
"%s", LOG_STR_ARG(reason));