diff --git a/CMakeLists.txt b/CMakeLists.txt index a460633..9930c34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(ensaht src/bmp.c src/aht.c src/ens.c + src/opt4048.c ) # Add the standard include files to the build diff --git a/include/opt4048.h b/include/opt4048.h new file mode 100644 index 0000000..282cb48 --- /dev/null +++ b/include/opt4048.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2026 A.M. Rowsell + +#ifndef OPT4048_H +#define OPT4048_H + +#include "hardware/i2c.h" + +#define OPT_ADDR 0x44 + +/* Structure to hold the calculated color and lux data */ +typedef struct { + float x; + float y; + float z; + float lux; +} OPT4048_Data; + +OPT4048_Data calculate_color_data(float ch0, float ch1, float ch2); +int initOPT(i2c_inst_t *i2c); +int getOPTData(i2c_inst_t *i2c, float *ch0, float *ch1, float *ch2); + +#endif // OPT4048_H diff --git a/src/opt4048.c b/src/opt4048.c new file mode 100644 index 0000000..d736841 --- /dev/null +++ b/src/opt4048.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2026 A.M. Rowsell + +#include "include/opt4048.h" + +OPT4048_Data calculate_color_data(float ch0, float ch1, float ch2) { + OPT4048_Data result; + + const float m0x = 2.34892992e-04f; + const float m1x = 4.07467441e-05f; + const float m2x = 9.28619404e-05f; + + const float m0y = -1.89652390e-05f; + const float m1y = 1.98958202e-04f; + const float m2y = -1.69739553e-05f; + + const float m0z = 1.20811684e-05f; + const float m1z = -1.58848115e-05f; + const float m2z = 6.74021520e-04f; + + const float m1l = 2.15e-3f; + + result.x = (ch0 * m0x) + (ch1 * m1x) + (ch2 * m2x); + result.y = (ch0 * m0y) + (ch1 * m1y) + (ch2 * m2y); + result.z = (ch0 * m0z) + (ch1 * m1z) + (ch2 * m2z); + + result.lux = ch1 * m1l; + + return result; +} + +int initOPT(i2c_inst_t *i2c) { + int result = 0; + int bytesWritten = 0; + // all registers are 16 bits! + // Need to init regs as follows: + // write setup to CONFIG (0x0A/0x0B) + // this sets it to 100ms sample time, auto-range lux + static const uint8_t config[5] = {0x0A, 0b00110010, 0b00001000, 0b10000000, + 0b00011101}; + // we'll be doing single-shot measurements + bytesWritten = i2c_write_blocking(i2c, OPT_ADDR, config, 4, false); + result = (bytesWritten == 4) ? 0 : 1; + return result; +} + +int getOPTData(i2c_inst_t *i2c, float *ch0, float *ch1, float *ch2) { + int result = 0; + int bytesWritten = 0, bytesRead = 0; + uint8_t lightData[16] = {0}; + uint32_t mantissaCH0 = 0; + uint32_t mantissaCH1 = 0; + uint32_t mantissaCH2 = 0; + uint8_t exponentCH0 = 0; + uint8_t exponentCH1 = 0; + uint8_t exponentCH2 = 0; + static const uint8_t triggerMeas[2] = {0x0B, 0b00101000}; + static const uint8_t dataReg = 0x00; + // force a one-shot measurement + bytesWritten = i2c_write_blocking(i2c, OPT_ADDR, triggerMeas, 2, false); + if (bytesWritten != 2) { + result = 1; + } + sleep_ms(410); // 4 channels @ 100ms each plus a little buffer time + + bytesWritten = i2c_write_blocking(i2c, OPT_ADDR, &dataReg, 1, true); + if (bytesWritten != 1) { + result = 1; + } + bytesRead = i2c_read_blocking(i2c, OPT_ADDR, lightData, 16, false); + if (bytesRead != 16) { + result = 1; + } + // now take all the measurement data and convert them to the proper + // representation + mantissaCH0 = ((uint32_t)(lightData[0] & 0x0FUL) << 16) | + ((uint32_t)lightData[1] << 8) | (uint32_t)lightData[2]; + mantissaCH1 = ((uint32_t)(lightData[4] & 0x0FUL) << 16) | + ((uint32_t)lightData[5] << 8) | (uint32_t)lightData[6]; + mantissaCH2 = ((uint32_t)(lightData[8] & 0x0FUL) << 16) | + ((uint32_t)lightData[9] << 8) | (uint32_t)lightData[10]; + exponentCH0 = (lightData[0] & 0xF0) >> 4; + exponentCH1 = (lightData[4] & 0xF0) >> 4; + exponentCH2 = (lightData[8] & 0xF0) >> 4; + + *ch0 = (float)(mantissaCH0 << exponentCH0); + *ch1 = (float)(mantissaCH1 << exponentCH1); + *ch2 = (float)(mantissaCH2 << exponentCH2); + + return result; +}