diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 3d1f0f9c..9e56c8cd 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -11,3 +11,4 @@ add_subdirectory(vl53l1x) add_subdirectory(is31fl3731) add_subdirectory(fatfs) add_subdirectory(sdcard) +add_subdirectory(as7262) diff --git a/drivers/as7262/CMakeLists.txt b/drivers/as7262/CMakeLists.txt new file mode 100644 index 00000000..da4fb7fc --- /dev/null +++ b/drivers/as7262/CMakeLists.txt @@ -0,0 +1 @@ +include(as7262.cmake) diff --git a/drivers/as7262/as7262.cmake b/drivers/as7262/as7262.cmake new file mode 100644 index 00000000..65591866 --- /dev/null +++ b/drivers/as7262/as7262.cmake @@ -0,0 +1,10 @@ +set(DRIVER_NAME as7262) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${DRIVER_NAME}.cpp) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c) diff --git a/drivers/as7262/as7262.cpp b/drivers/as7262/as7262.cpp new file mode 100644 index 00000000..b2bbd218 --- /dev/null +++ b/drivers/as7262/as7262.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include + +#include "as7262.hpp" + +namespace pimoroni { + + /***** Device registers and masks here *****/ + + enum reg { + DEVICE = 0x00, + HW_VERSION = 0x01, + FW_VERSION = 0x02, // + 0x03 + CONTROL = 0x04, + INT_T = 0x05, + TEMP = 0x06, + LED_CONTROL = 0x07, + V_HIGH = 0x08, // Violet + V_LOW = 0x09, + B_HIGH = 0x0A, // Blue + B_LOW = 0x0B, + G_HIGH = 0x0C, // Green + G_LOW = 0x0D, + Y_HIGH = 0x0E, // Yellow + Y_LOW = 0x0F, + O_HIGH = 0x10, // Orange + O_LOW = 0x11, + R_HIGH = 0x12, // Red + R_LOW = 0x13, + V_CAL_F = 0x14, // -> 0x17 Float (Violet) + B_CAL_F = 0x18, // -> 0x1B Float (Blue) + G_CAL_F = 0x1C, // -> 0x1F Float (Green) + Y_CAL_F = 0x20, // -> 0x23 Float (Yellow) + O_CAL_F = 0x24, // -> 0x27 Float (Orange) + R_CAL_F = 0x28, // -> 0x27 Float (Red) + }; + + + bool AS7262::init() { + bool succeeded = false; + + i2c_init(i2c, 400000); + + gpio_set_function(sda, GPIO_FUNC_I2C); + gpio_pull_up(sda); + gpio_set_function(scl, GPIO_FUNC_I2C); + gpio_pull_up(scl); + + if(interrupt != PIN_UNUSED) { + gpio_set_function(interrupt, GPIO_FUNC_SIO); + gpio_set_dir(interrupt, GPIO_IN); + gpio_pull_up(interrupt); + } + + reset(); + + /***** Replace if(true) with any operations needed to initialise the device *****/ + if(true) { + succeeded = true; + } + + return succeeded; + } + + void AS7262::reset() { + i2c_reg_write_uint8(reg::CONTROL, 0b10000000); + sleep_ms(1000); + } + + void AS7262::set_gain(gain gain) { + uint8_t temp = i2c_reg_read_uint8(reg::CONTROL) & ~0b00110000; + temp |= (uint8_t)gain << 4; + i2c_reg_write_uint8(reg::CONTROL, temp); + } + + void AS7262::set_measurement_mode(measurement_mode mode) { + uint8_t temp = i2c_reg_read_uint8(reg::CONTROL) & ~0b00001100; + temp |= (uint8_t)mode << 2; + i2c_reg_write_uint8(reg::CONTROL, temp); + } + + void AS7262::set_indicator_current(indicator_current current) { + uint8_t temp = i2c_reg_read_uint8(reg::LED_CONTROL) & ~0b00000110; + temp |= (uint8_t)current << 1; + i2c_reg_write_uint8(reg::LED_CONTROL, temp); + } + + void AS7262::set_illumination_current(illumination_current current) { + uint8_t temp = i2c_reg_read_uint8(reg::LED_CONTROL) & ~0b00110000; + temp |= (uint8_t)current << 4; + i2c_reg_write_uint8(reg::LED_CONTROL, temp); + } + + void AS7262::set_leds(bool illumination, bool indicator) { + uint8_t temp = i2c_reg_read_uint8(reg::LED_CONTROL) & ~0b00001001; + temp |= indicator ? 1 : 0; + temp |= (illumination ? 1 : 0) << 3; + i2c_reg_write_uint8(reg::LED_CONTROL, temp); + } + + std::string AS7262::firmware_version() { + std::string buf; + uint16_t fw_version = i2c_reg_read_uint16(reg::FW_VERSION); + buf += std::to_string(fw_version); + return buf; + } + + bool AS7262::data_ready() { + return i2c_reg_read_uint8(reg::CONTROL) & 0b00000010; + } + + AS7262::reading AS7262::read() { + while(!data_ready()) {} + return AS7262::reading { + i2c_reg_read_float(reg::R_CAL_F), + i2c_reg_read_float(reg::O_CAL_F), + i2c_reg_read_float(reg::Y_CAL_F), + i2c_reg_read_float(reg::G_CAL_F), + i2c_reg_read_float(reg::B_CAL_F), + i2c_reg_read_float(reg::V_CAL_F) + }; + } + + uint8_t AS7262::temperature() { + return i2c_reg_read_uint8(reg::TEMP); + } + + // i2c IO wrappers around the weird virtual i2c nonsense + + void AS7262::i2c_reg_write_uint8(uint8_t reg, uint8_t value) { + _i2c_write(reg, &value, 1); + } + + float AS7262::i2c_reg_read_float(uint8_t reg) { + float value; + _i2c_read(reg, (uint8_t *)&value, 4); + return __builtin_bswap32(value); + } + + uint8_t AS7262::i2c_reg_read_uint8(uint8_t reg) { + uint8_t value; + _i2c_read(reg, &value, 1); + return value; + } + + uint16_t AS7262::i2c_reg_read_uint16(uint8_t reg) { + uint16_t value; + _i2c_read(reg, (uint8_t *)&value, 2); + return value; + } + + // Plumbing for virtual i2c + void AS7262::_i2c_reg_write_uint8(uint8_t reg, uint8_t value) { + uint8_t buffer[2] = {reg, value}; + i2c_write_blocking(i2c, address, buffer, 2, false); + } + + uint8_t AS7262::_i2c_reg_read_uint8(uint8_t reg) { + uint8_t value; + i2c_write_blocking(i2c, address, ®, 1, false); + i2c_read_blocking(i2c, address, (uint8_t *)&value, 1, false); + return value; + } + + uint8_t AS7262::_i2c_status() { + return _i2c_reg_read_uint8(0x00); + } + + int AS7262::_i2c_read(uint8_t reg, uint8_t *values, int len) { + for (auto i = 0u; i < len; i++){ + while((_i2c_status() & 0b10) != 0) {}; // Wait for write-ready + _i2c_reg_write_uint8(0x01, reg + i); // Set address pointer + while((_i2c_status() & 0b01) != 1) {}; // Wait for read-ready + values[i] = _i2c_reg_read_uint8(0x02); // Read *one* byte :| + } + return 0; + } + + int AS7262::_i2c_write(uint8_t reg, uint8_t *values, int len) { + for (auto i = 0u; i < len; i++){ + while((_i2c_status() & 0b10) != 0) {}; // Wait for write-ready + _i2c_reg_write_uint8(0x01, reg | 0x80); // Set address pointer + while ((_i2c_status() & 0b10) != 0) {}; // Wait for write-ready + _i2c_reg_write_uint8(0x01, values[i]); // Write *one* byte :| + } + return 0; + } +} \ No newline at end of file diff --git a/drivers/as7262/as7262.hpp b/drivers/as7262/as7262.hpp new file mode 100644 index 00000000..508c58d9 --- /dev/null +++ b/drivers/as7262/as7262.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "hardware/i2c.h" +#include "hardware/gpio.h" + +namespace pimoroni { + + class AS7262 { + //-------------------------------------------------- + // Constants + //-------------------------------------------------- + public: + static const uint8_t DEFAULT_I2C_ADDRESS = 0x49; + static const uint8_t DEFAULT_SDA_PIN = 20; + static const uint8_t DEFAULT_SCL_PIN = 21; + static const uint8_t DEFAULT_INT_PIN = 22; + static const uint8_t PIN_UNUSED = UINT8_MAX; + + /***** More public constants here *****/ + + private: + /***** Private constants here *****/ + + + //-------------------------------------------------- + // Enums + //-------------------------------------------------- + public: + enum class gain : uint8_t { + X1 = 0b00, + X3_7 = 0b01, + X16 = 0b10, + X64 = 0b11 + }; + + enum class illumination_current : uint8_t { + ma12 = 0b00, + ma25 = 0b01, + ma50 = 0b10, + ma100 = 0b11 + }; + + enum class indicator_current : uint8_t { + ma1 = 0b00, + ma2 = 0b01, + ma4 = 0b10, + ma8 = 0b11, + }; + + enum class measurement_mode : uint8_t { + cont_ygnv = 0b00, // yellow, green, blue, violet - continuous + cont_royg = 0b01, // red, orange, yellow, green - continuous + cont_roygbr = 0b10, // red, orange, yellow, green, violet - continuous + oneshot = 0b11 // everything - one-shot + }; + + + //-------------------------------------------------- + // Substructures + //-------------------------------------------------- + public: + struct reading { + float red; + float orange; + float yellow; + float green; + float blue; + float violet; + }; + + + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + private: + i2c_inst_t *i2c = i2c0; + + // interface pins with our standard defaults where appropriate + int8_t address = DEFAULT_I2C_ADDRESS; + int8_t sda = DEFAULT_SDA_PIN; + int8_t scl = DEFAULT_SCL_PIN; + int8_t interrupt = DEFAULT_INT_PIN; + + + //-------------------------------------------------- + // Constructors/Destructor + //-------------------------------------------------- + public: + AS7262() {} + + AS7262(i2c_inst_t *i2c, uint8_t sda, uint8_t scl, uint8_t interrupt = PIN_UNUSED) : + i2c(i2c), sda(sda), scl(scl), interrupt(interrupt) {} + + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + bool init(); + void reset(); + + uint8_t device_type(); + uint8_t hardware_version(); + std::string firmware_version(); + reading read(); + uint8_t temperature(); + + void set_gain(gain gain); + void set_measurement_mode(measurement_mode mode); + void set_indicator_current(indicator_current current); + void set_illumination_current(illumination_current current); + void set_leds(bool illumination, bool indicator); + + // Virtual i2c transfers, routed through read/write/status regs + uint8_t i2c_reg_read_uint8(uint8_t reg); + void i2c_reg_write_uint8(uint8_t reg, uint8_t value); + uint16_t i2c_reg_read_uint16(uint8_t reg); + float i2c_reg_read_float(uint8_t reg); + + private: + bool data_ready(); + uint8_t _i2c_status(); + int _i2c_read(uint8_t reg, uint8_t *values, int len); + int _i2c_write(uint8_t reg, uint8_t *values, int len); + + // *Real* single-byte i2c transfers + uint8_t _i2c_reg_read_uint8(uint8_t reg); + void _i2c_reg_write_uint8(uint8_t reg, uint8_t value); + }; + +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 27789320..61a41027 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,3 +19,4 @@ add_subdirectory(pico_tof_display) add_subdirectory(pico_trackball_display) add_subdirectory(pico_audio) add_subdirectory(pico_wireless) +add_subdirectory(breakout_as7262) diff --git a/examples/breakout_as7262/CMakeLists.txt b/examples/breakout_as7262/CMakeLists.txt new file mode 100644 index 00000000..49b75932 --- /dev/null +++ b/examples/breakout_as7262/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME as7262_demo) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c breakout_as7262) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_as7262/demo.cpp b/examples/breakout_as7262/demo.cpp new file mode 100644 index 00000000..7dd12b22 --- /dev/null +++ b/examples/breakout_as7262/demo.cpp @@ -0,0 +1,43 @@ +#include +#include +#include +#include "pico/stdlib.h" + +#include "breakout_as7262.hpp" + +using namespace pimoroni; + +BreakoutAS7262 as7262; + +int main() { + setup_default_uart(); + + as7262.init(); + + int16_t hw_version = as7262.i2c_reg_read_uint16(0x00); + int16_t fw_version = as7262.i2c_reg_read_uint16(0x02); + printf("%04x %04x \n", hw_version, fw_version); + + as7262.set_gain(AS7262::gain::X16); + as7262.set_measurement_mode(AS7262::measurement_mode::cont_roygbr); + as7262.set_illumination_current(AS7262::illumination_current::ma12); + as7262.set_indicator_current(AS7262::indicator_current::ma4); + //as7262.set_leds(false, false); + + while(true) { + + AS7262::reading reading = as7262.read(); + printf("R: %f O: %f Y: %f G: %f B: %f V: %f \n", + reading.red, + reading.orange, + reading.yellow, + reading.green, + reading.blue, + reading.violet + ); + + sleep_ms(1000); + } + + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 69378dab..9000c9b2 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory(breakout_rgbmatrix5x5) add_subdirectory(breakout_matrix11x7) add_subdirectory(breakout_trackball) add_subdirectory(breakout_sgp30) +add_subdirectory(breakout_as7262) add_subdirectory(pico_graphics) add_subdirectory(pico_display) add_subdirectory(pico_unicorn) diff --git a/libraries/breakout_as7262/CMakeLists.txt b/libraries/breakout_as7262/CMakeLists.txt new file mode 100644 index 00000000..db80dc37 --- /dev/null +++ b/libraries/breakout_as7262/CMakeLists.txt @@ -0,0 +1 @@ +include(breakout_as7262.cmake) diff --git a/libraries/breakout_as7262/breakout_as7262.cmake b/libraries/breakout_as7262/breakout_as7262.cmake new file mode 100644 index 00000000..37696acd --- /dev/null +++ b/libraries/breakout_as7262/breakout_as7262.cmake @@ -0,0 +1,11 @@ +set(LIB_NAME breakout_as7262) +add_library(${LIB_NAME} INTERFACE) + +target_sources(${LIB_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp +) + +target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +# Pull in pico libraries that we need +target_link_libraries(${LIB_NAME} INTERFACE pico_stdlib hardware_i2c as7262) diff --git a/libraries/breakout_as7262/breakout_as7262.cpp b/libraries/breakout_as7262/breakout_as7262.cpp new file mode 100644 index 00000000..fec97687 --- /dev/null +++ b/libraries/breakout_as7262/breakout_as7262.cpp @@ -0,0 +1,5 @@ +#include "breakout_as7262.hpp" + +namespace pimoroni { + +} diff --git a/libraries/breakout_as7262/breakout_as7262.hpp b/libraries/breakout_as7262/breakout_as7262.hpp new file mode 100644 index 00000000..7894124f --- /dev/null +++ b/libraries/breakout_as7262/breakout_as7262.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "../../drivers/as7262/as7262.hpp" + +namespace pimoroni { + + typedef AS7262 BreakoutAS7262; +}