diff --git a/Adafruit_OPT4048.cpp b/Adafruit_OPT4048.cpp new file mode 100644 index 0000000..3707412 --- /dev/null +++ b/Adafruit_OPT4048.cpp @@ -0,0 +1,115 @@ +#include "Adafruit_OPT4048.h" + +/** + * @brief Construct a new Adafruit_OPT4048 object. + */ +Adafruit_OPT4048::Adafruit_OPT4048() { + i2c_dev = nullptr; +} + +/** + * @brief Destroy the Adafruit_OPT4048 object, frees I2C device. + */ +Adafruit_OPT4048::~Adafruit_OPT4048() { + if (i2c_dev) { + delete i2c_dev; + i2c_dev = nullptr; + } +} + +/** + * @brief Initialize the OPT4048 sensor over I2C. + * + * Deletes any existing I2C device instance, then creates a new one. + * + * @param addr I2C 7-bit address of the sensor (default OPT4048_DEFAULT_ADDR). + * @param wire Pointer to TwoWire instance (default &Wire). + * @return true if initialization was successful, false otherwise. + */ +bool Adafruit_OPT4048::begin(uint8_t addr, TwoWire *wire) { + // Clean up old instance if reinitializing + if (i2c_dev) { + delete i2c_dev; + i2c_dev = nullptr; + } + // Create I2C device + i2c_dev = new Adafruit_I2CDevice(addr, wire); + if (!i2c_dev || !i2c_dev->begin()) { + return false; + } + // Verify device ID to ensure correct chip is connected + { + Adafruit_BusIO_Register idreg(i2c_dev, OPT4048_REG_DEVICE_ID, 2, MSBFIRST); + uint16_t id = idreg.read(); + // Default reset device ID is 0x0821 + if (id != 0x0821) { + return false; + } + } + return true; +} +/** + * @brief Read all four channels, verify CRC, and return raw ADC code values. + * + * Reads registers for channels 0-3 in one burst, checks the CRC bits for each, + * and computes the 20-bit ADC code = mantissa << exponent. + * + * @param ch0 Pointer to store channel 0 ADC code. + * @param ch1 Pointer to store channel 1 ADC code. + * @param ch2 Pointer to store channel 2 ADC code. + * @param ch3 Pointer to store channel 3 ADC code. + * @return true if read succeeds and all CRC checks pass, false otherwise. + */ +bool Adafruit_OPT4048::getChannels(float *ch0, float *ch1, float *ch2, float *ch3) { + if (!i2c_dev) { + return false; + } + uint8_t buf[16]; + uint8_t reg = OPT4048_REG_CH0_MSB; + if (!i2c_dev->write_then_read(®, 1, buf, sizeof(buf))) { + return false; + } + for (uint8_t ch = 0; ch < 4; ch++) { + uint16_t msb = ((uint16_t)buf[4 * ch] << 8) | buf[4 * ch + 1]; + uint16_t lsb = ((uint16_t)buf[4 * ch + 2] << 8) | buf[4 * ch + 3]; + uint8_t exp = (msb >> 12) & 0x0F; + uint32_t mant = ((msb & 0x0FFF) << 8) | ((lsb >> 8) & 0xFF); + uint32_t code = mant << exp; + uint8_t crc = lsb & 0x0F; + // Compute CRC bits + uint8_t x0 = 0; + for (uint8_t i = 0; i < 4; i++) { + x0 ^= (exp >> i) & 1; + } + for (uint8_t i = 0; i < 20; i++) { + x0 ^= (mant >> i) & 1; + } + for (uint8_t i = 0; i < 4; i++) { + x0 ^= (crc >> i) & 1; + } + uint8_t x1 = ((crc >> 1) & 1) ^ ((crc >> 3) & 1); + for (uint8_t i = 1; i < 20; i += 2) { + x1 ^= (mant >> i) & 1; + } + x1 ^= (exp >> 1) & 1; + x1 ^= (exp >> 3) & 1; + uint8_t x2 = ((crc >> 3) & 1); + for (uint8_t i = 3; i < 20; i += 4) { + x2 ^= (mant >> i) & 1; + } + x2 ^= (exp >> 3) & 1; + uint8_t x3 = ((mant >> 3) & 1) ^ ((mant >> 11) & 1) ^ ((mant >> 19) & 1); + // Verify CRC + if (((crc & 1) != x0) || (((crc >> 1) & 1) != x1) || (((crc >> 2) & 1) != x2) || (((crc >> 3) & 1) != x3)) { + return false; + } + // Assign output + switch (ch) { + case 0: *ch0 = (float)code; break; + case 1: *ch1 = (float)code; break; + case 2: *ch2 = (float)code; break; + case 3: *ch3 = (float)code; break; + } + } + return true; +} \ No newline at end of file diff --git a/Adafruit_OPT4048.h b/Adafruit_OPT4048.h new file mode 100644 index 0000000..6868269 --- /dev/null +++ b/Adafruit_OPT4048.h @@ -0,0 +1,58 @@ +/* + This is a library for the OPT4048 ambient light sensor + Designed to work with Adafruit BusIO: https://github.com/adafruit/Adafruit_BusIO + + Written by Adafruit Industries, 2025. +*/ + +#ifndef ADAFRUIT_OPT4048_H +#define ADAFRUIT_OPT4048_H + +#include "Arduino.h" +#include +#include +#include + +// Default I2C address (ADDR pin connected to GND) +#define OPT4048_DEFAULT_ADDR 0x44 + +// Register addresses +#define OPT4048_REG_CH0_MSB 0x00 +#define OPT4048_REG_CH0_LSB 0x01 +#define OPT4048_REG_CH1_MSB 0x02 +#define OPT4048_REG_CH1_LSB 0x03 +#define OPT4048_REG_CH2_MSB 0x04 +#define OPT4048_REG_CH2_LSB 0x05 +#define OPT4048_REG_CH3_MSB 0x06 +#define OPT4048_REG_CH3_LSB 0x07 +#define OPT4048_REG_THRESHOLD_LOW 0x08 +#define OPT4048_REG_THRESHOLD_HIGH 0x09 +#define OPT4048_REG_CONFIG 0x0A +#define OPT4048_REG_THRESHOLD_CFG 0x0B +#define OPT4048_REG_STATUS 0x0C +#define OPT4048_REG_DEVICE_ID 0x11 + +/** + @brief Class that stores state and functions for interacting with the OPT4048 sensor. +*/ +class Adafruit_OPT4048 { +public: + Adafruit_OPT4048(); + ~Adafruit_OPT4048(); + + /** + * @brief Initialize the sensor with given I2C address and Wire instance + * + * @param addr I2C address, defaults to OPT4048_DEFAULT_ADDR + * @param wire Pointer to TwoWire instance, defaults to &Wire + * @return true on success, false on failure + */ + bool begin(uint8_t addr = OPT4048_DEFAULT_ADDR, TwoWire *wire = &Wire); + /** Read all four channels, verify CRC, and return raw ADC code values */ + bool getChannels(float *ch0, float *ch1, float *ch2, float *ch3); + +private: + Adafruit_I2CDevice *i2c_dev; +}; + +#endif // ADAFRUIT_OPT4048_H \ No newline at end of file diff --git a/examples/test_opt4048/test_opt4048.ino b/examples/test_opt4048/test_opt4048.ino new file mode 100644 index 0000000..a787739 --- /dev/null +++ b/examples/test_opt4048/test_opt4048.ino @@ -0,0 +1,29 @@ +/* + test_opt4048.ino + Example sketch for OPT4048 ambient light sensor +*/ + +#include +#include "Adafruit_OPT4048.h" + +Adafruit_OPT4048 sensor; + +void setup() { + Serial.begin(115200); + while (!Serial) { + delay(10); + } + Serial.println("OPT4048 Test"); + if (!sensor.begin()) { + Serial.println("Failed to find OPT4048 chip"); + while (1) { + delay(10); + } + } + Serial.println("OPT4048 found!"); +} + +void loop() { + // Nothing to do (testing only initialization) + delay(1000); +} \ No newline at end of file