/* * @file lsense.cpp * @brief Light sensor implementation: software I2C, detection, and channel * readout for the six TCS-style light sensors on three buses. * * Created: 21.09.2025 * Author: ThePetrovich * * Copyright YKSA - Sakha Aerospace Systems, LLC. * See the LICENSE file for details. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include "config.h" #include "iodefs.h" #include "lsense.h" static SoftwareI2C g_i2c_buses[NUM_BUSES]; typedef union __attribute__((packed)) { struct __attribute__((packed)) { uint16_t red; uint16_t green; uint16_t blue; uint16_t reserved; uint16_t ir; uint16_t green2; } data; uint8_t bytes[LSENSE_DATA_SIZE]; } light_sensor_data_t; static light_sensor_data_t g_sensor_data[NUM_BUSES][SENSORS_PER_BUS]; static uint8_t g_sensor_addresses[NUM_BUSES][SENSORS_PER_BUS]; static uint8_t g_sensors_detected_total = 0; static uint8_t g_sensors_detected_per_bus[NUM_BUSES]; static void configure_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr) { uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr; i2c_bus.beginTransmission(addr); i2c_bus.write(0x41); i2c_bus.endTransmission(); i2c_bus.beginTransmission(addr); i2c_bus.write(0x41); i2c_bus.write(0b00101101); /* IR gain x1, RGB gain x1, 35ms mode */ i2c_bus.write(0b00010000); /* RGB_EN = 1, measurement active */ i2c_bus.endTransmission(); } static void read_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr, light_sensor_data_t *sensor_data) { if (sensor_data == nullptr) return; uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr; i2c_bus.beginTransmission(addr); i2c_bus.write(0x50); i2c_bus.endTransmission(); i2c_bus.requestFrom(addr, (uint8_t)LSENSE_DATA_SIZE); for (uint8_t i = 0; i < LSENSE_DATA_SIZE; i++) { sensor_data->bytes[i] = i2c_bus.read(); } i2c_bus.endTransmission(); } static uint8_t detect_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr) { uint8_t response = 0; uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr; i2c_bus.beginTransmission(addr); i2c_bus.write(0x92); i2c_bus.endTransmission(); i2c_bus.requestFrom(addr, (uint8_t)1); if (i2c_bus.available()) { response = i2c_bus.read(); if (response == 0xFF) { response = 0; } } return response; } static void initialize_i2c_bus(int bus_number, bool pin_order_inverted) { uint8_t sda_pin, scl_pin; switch (bus_number) { case BUS_X: sda_pin = pin_order_inverted ? SCL1 : SDA1; scl_pin = pin_order_inverted ? SDA1 : SCL1; break; case BUS_Y: sda_pin = pin_order_inverted ? SCL0 : SDA0; scl_pin = pin_order_inverted ? SDA0 : SCL0; break; case BUS_Z: sda_pin = pin_order_inverted ? SCL2 : SDA2; scl_pin = pin_order_inverted ? SDA2 : SCL2; break; default: return; } SoftwareI2C *i2c_bus = &g_i2c_buses[bus_number]; i2c_bus->begin(sda_pin, scl_pin); configure_light_sensor(*i2c_bus, 0); configure_light_sensor(*i2c_bus, 1); } static void detect_sensors_on_bus(int bus_number) { if (bus_number < 0 || bus_number >= NUM_BUSES) return; g_sensors_detected_per_bus[bus_number] = 0; initialize_i2c_bus(bus_number, false); g_sensor_addresses[bus_number][SENSOR_NEG] = detect_light_sensor(g_i2c_buses[bus_number], 0); if (g_sensor_addresses[bus_number][SENSOR_NEG] == LSENSE_EXPECTED_ID) { g_sensors_detected_total++; g_sensors_detected_per_bus[bus_number]++; } g_sensor_addresses[bus_number][SENSOR_POS] = detect_light_sensor(g_i2c_buses[bus_number], 1); if (g_sensor_addresses[bus_number][SENSOR_POS] == LSENSE_EXPECTED_ID) { g_sensors_detected_total++; g_sensors_detected_per_bus[bus_number]++; } if (g_sensors_detected_per_bus[bus_number] == 0) { initialize_i2c_bus(bus_number, true); g_sensor_addresses[bus_number][SENSOR_NEG] = detect_light_sensor(g_i2c_buses[bus_number], 0); if (g_sensor_addresses[bus_number][SENSOR_NEG] == LSENSE_EXPECTED_ID) { g_sensors_detected_total++; g_sensors_detected_per_bus[bus_number]++; } g_sensor_addresses[bus_number][SENSOR_POS] = detect_light_sensor(g_i2c_buses[bus_number], 1); if (g_sensor_addresses[bus_number][SENSOR_POS] == LSENSE_EXPECTED_ID) { g_sensors_detected_total++; g_sensors_detected_per_bus[bus_number]++; } } } void lsense_detect_all(void) { g_sensors_detected_total = 0; detect_sensors_on_bus(BUS_X); detect_sensors_on_bus(BUS_Y); detect_sensors_on_bus(BUS_Z); } void lsense_get_presence(lsense_presence_t *out) { if (out == nullptr) return; out->total_detected = g_sensors_detected_total; out->bus_x_count = g_sensors_detected_per_bus[BUS_X]; out->bus_x_neg_addr = g_sensor_addresses[BUS_X][SENSOR_NEG]; out->bus_x_pos_addr = g_sensor_addresses[BUS_X][SENSOR_POS]; out->bus_y_count = g_sensors_detected_per_bus[BUS_Y]; out->bus_y_neg_addr = g_sensor_addresses[BUS_Y][SENSOR_NEG]; out->bus_y_pos_addr = g_sensor_addresses[BUS_Y][SENSOR_POS]; out->bus_z_count = g_sensors_detected_per_bus[BUS_Z]; out->bus_z_neg_addr = g_sensor_addresses[BUS_Z][SENSOR_NEG]; out->bus_z_pos_addr = g_sensor_addresses[BUS_Z][SENSOR_POS]; } static uint16_t extract_channel(const light_sensor_data_t *d, uint8_t channel) { switch (channel) { case 0: return d->data.red; case 1: return d->data.green; case 2: return d->data.blue; case 3: return d->data.ir; case 4: return d->data.green2; case 5: /* visible: R+G+B+G2 */ return (uint16_t)((uint32_t)d->data.red + d->data.green + d->data.blue + d->data.green2); case 6: /* all: R+G+B+G2+IR */ return (uint16_t)((uint32_t)d->data.red + d->data.green + d->data.blue + d->data.green2 + d->data.ir); default: return 0; } } void lsense_read_channel(uint8_t channel, lsense_channel_values_t *out) { if (out == nullptr) return; for (int bus = 0; bus < NUM_BUSES; bus++) { read_light_sensor(g_i2c_buses[bus], 0, &g_sensor_data[bus][SENSOR_NEG]); read_light_sensor(g_i2c_buses[bus], 1, &g_sensor_data[bus][SENSOR_POS]); } out->values[0] = extract_channel(&g_sensor_data[BUS_X][SENSOR_NEG], channel); out->values[1] = extract_channel(&g_sensor_data[BUS_X][SENSOR_POS], channel); out->values[2] = extract_channel(&g_sensor_data[BUS_Y][SENSOR_NEG], channel); out->values[3] = extract_channel(&g_sensor_data[BUS_Y][SENSOR_POS], channel); out->values[4] = extract_channel(&g_sensor_data[BUS_Z][SENSOR_NEG], channel); out->values[5] = extract_channel(&g_sensor_data[BUS_Z][SENSOR_POS], channel); }