diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index 6f84a3db..6e236a94 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(st7789) add_subdirectory(msa301) add_subdirectory(rv3028) add_subdirectory(vl53l1x) +add_subdirectory(is31fl3731) diff --git a/drivers/is31fl3731/CMakeLists.txt b/drivers/is31fl3731/CMakeLists.txt new file mode 100644 index 00000000..008bef61 --- /dev/null +++ b/drivers/is31fl3731/CMakeLists.txt @@ -0,0 +1 @@ +include(is31fl3731.cmake) diff --git a/drivers/is31fl3731/is31fl3731.cmake b/drivers/is31fl3731/is31fl3731.cmake new file mode 100644 index 00000000..c3c2b7b7 --- /dev/null +++ b/drivers/is31fl3731/is31fl3731.cmake @@ -0,0 +1,10 @@ +set(DRIVER_NAME is31fl3731) +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/is31fl3731/is31fl3731.cpp b/drivers/is31fl3731/is31fl3731.cpp new file mode 100644 index 00000000..2980193e --- /dev/null +++ b/drivers/is31fl3731/is31fl3731.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include + +#include "is31fl3731.hpp" + +namespace pimoroni { + + constexpr uint8_t CONFIG_BANK = 0x0b; + constexpr uint8_t NUM_PIXELS = 144; + constexpr uint8_t NUM_FRAMES = 8; + + constexpr uint8_t ENABLE_OFFSET = 0x00; + constexpr uint8_t COLOR_OFFSET = 0x24; + + constexpr uint8_t GAMMA[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, + 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, + 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, + 19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, + 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, + 40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89, + 90, 91, 93, 94, 95, 96, 98, 99, 100, 102, 103, 104, 106, 107, 109, 110, + 111, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134, + 135, 137, 138, 140, 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 158, 160, + 162, 163, 165, 167, 169, 170, 172, 174, 176, 178, 179, 181, 183, 185, 187, 189, + 191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, + 222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255}; + + enum mode { + PICTURE = 0x00, + AUTOPLAY = 0x08, + AUDIOPLAY = 0x18 + }; + + enum reg : uint8_t { + MODE = 0x00, + FRAME = 0x01, + AUTPLAY1 = 0x02, + AUTOPLAY2 = 0x03, + BLINK = 0x05, + AUDIOSYNC = 0x06, + BREATH1 = 0x07, + BREATH2 = 0x08, + SHUTDOWN = 0x0a, + GAIN = 0x0b, + ADC = 0x0c, + BANK = 0xfd + }; + + bool IS31FL3731::init() { + i2c_init(i2c, 100000); + + gpio_set_function(sda, GPIO_FUNC_I2C); gpio_pull_up(sda); + gpio_set_function(scl, GPIO_FUNC_I2C); gpio_pull_up(scl); + + i2c_reg_write_uint8(reg::SHUTDOWN, 0b00000001); + + i2c_reg_write_uint8(reg::BANK, CONFIG_BANK); + + i2c_reg_write_uint8(reg::MODE, mode::PICTURE); + + i2c_reg_write_uint8(reg::AUDIOSYNC, 0); + + clear(); + + return true; + } + + i2c_inst_t* IS31FL3731::get_i2c() const { + return i2c; + } + + int IS31FL3731::get_sda() const { + return sda; + } + + int IS31FL3731::get_scl() const { + return scl; + } + + void IS31FL3731::clear() { + for(auto i = 0u; i < sizeof(buf); i++) { + buf[i] = 0; + } + } + + void IS31FL3731::i2c_reg_write_uint8(uint8_t reg, uint8_t value) { + uint8_t buffer[2] = {reg, value}; + i2c_write_blocking(i2c, address, buffer, 2, false); + } + + int16_t IS31FL3731::i2c_reg_read_int16(uint8_t reg) { + int16_t value; + i2c_write_blocking(i2c, address, ®, 1, true); + i2c_read_blocking(i2c, address, (uint8_t *)&value, 2, false); + return value; + } + + void IS31FL3731::enable(std::initializer_list pattern, uint8_t frame) { + i2c_reg_write_uint8(reg::BANK, frame); + uint8_t enable_buf[19]; + enable_buf[0] = ENABLE_OFFSET; + uint8_t offset = 1; + for(auto byte : pattern) { + enable_buf[offset] = byte; + offset++; + } + i2c_write_blocking(i2c, address, enable_buf, sizeof(enable_buf), false); + } + + void IS31FL3731::set(uint8_t index, uint8_t brightness) { + buf[index + 1] = GAMMA[brightness]; + } + + void IS31FL3731::update(uint8_t frame) { + i2c_reg_write_uint8(reg::BANK, frame); + buf[0] = COLOR_OFFSET; + i2c_write_blocking(i2c, address, buf, sizeof(buf), false); + i2c_reg_write_uint8(reg::BANK, CONFIG_BANK); // Switch back to config bank + i2c_reg_write_uint8(reg::FRAME, frame); // Set the desired frame as active + } +} \ No newline at end of file diff --git a/drivers/is31fl3731/is31fl3731.hpp b/drivers/is31fl3731/is31fl3731.hpp new file mode 100644 index 00000000..a3ce53b4 --- /dev/null +++ b/drivers/is31fl3731/is31fl3731.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "hardware/i2c.h" +#include "hardware/gpio.h" +#include + +namespace pimoroni { + + class IS31FL3731 { + //-------------------------------------------------- + // Constants + //-------------------------------------------------- + public: + static const uint8_t DEFAULT_I2C_ADDRESS = 0x74; + static const uint8_t I2C_ADDRESS_ALTERNATE1 = 0x75; + static const uint8_t I2C_ADDRESS_ALTERNATE2 = 0x76; + static const uint8_t I2C_ADDRESS_ALTERNATE3 = 0x77; + static const uint8_t DEFAULT_SDA_PIN = 20; + static const uint8_t DEFAULT_SCL_PIN = 21; + + + //-------------------------------------------------- + // 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; + + uint8_t buf[145]; + + + //-------------------------------------------------- + // Constructors/Destructor + //-------------------------------------------------- + public: + IS31FL3731() {} + + IS31FL3731(uint8_t address) : + address(address) {} + + IS31FL3731(i2c_inst_t *i2c, uint8_t address, uint8_t sda, uint8_t scl) : + i2c(i2c), address(address), sda(sda), scl(scl) {} + + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + bool init(); + + i2c_inst_t* get_i2c() const; + int get_sda() const; + int get_scl() const; + + void enable(std::initializer_list pattern, uint8_t frame = 0); + void set(uint8_t index, uint8_t brightness); + void update(uint8_t frame = 0); + void clear(); + + private: + void i2c_reg_write_uint8(uint8_t reg, uint8_t value); + int16_t i2c_reg_read_int16(uint8_t reg); + }; + +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d3886bfb..908711e3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,6 @@ add_subdirectory(breakout_roundlcd) +add_subdirectory(breakout_rgbmatrix5x5) +add_subdirectory(breakout_matrix11x7) add_subdirectory(pico_display) add_subdirectory(pico_unicorn) add_subdirectory(pico_unicorn_plasma) diff --git a/examples/breakout_matrix11x7/CMakeLists.txt b/examples/breakout_matrix11x7/CMakeLists.txt new file mode 100644 index 00000000..32f084f5 --- /dev/null +++ b/examples/breakout_matrix11x7/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME matrix11x7_demo) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c breakout_matrix11x7) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_matrix11x7/demo.cpp b/examples/breakout_matrix11x7/demo.cpp new file mode 100644 index 00000000..ee203063 --- /dev/null +++ b/examples/breakout_matrix11x7/demo.cpp @@ -0,0 +1,35 @@ +#include "pico/stdlib.h" + +#include "breakout_matrix11x7.hpp" + +using namespace pimoroni; + +BreakoutMatrix11x7 matrix11x7(0x75); + +int main() { + gpio_init(PICO_DEFAULT_LED_PIN); + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); + + matrix11x7.init(); + + while(true) { + for(auto x = 0; x < matrix11x7.WIDTH; x++) { + for(auto y = 0; y < matrix11x7.HEIGHT; y++) { + matrix11x7.set_pixel(x, y, ((x + y) & 0b1) * 128); + } + } + matrix11x7.update(0); + gpio_put(PICO_DEFAULT_LED_PIN, true); + sleep_ms(1000); + for(auto x = 0; x < matrix11x7.WIDTH; x++) { + for(auto y = 0; y < matrix11x7.HEIGHT; y++) { + matrix11x7.set_pixel(x, y, ((x + y + 1) & 0b1) * 128); + } + } + matrix11x7.update(0); + gpio_put(PICO_DEFAULT_LED_PIN, false); + sleep_ms(1000); + } + + return 0; +} diff --git a/examples/breakout_rgbmatrix5x5/CMakeLists.txt b/examples/breakout_rgbmatrix5x5/CMakeLists.txt new file mode 100644 index 00000000..10f73f85 --- /dev/null +++ b/examples/breakout_rgbmatrix5x5/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME rgbmatrix5x5_demo) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib hardware_i2c breakout_rgbmatrix5x5) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_rgbmatrix5x5/demo.cpp b/examples/breakout_rgbmatrix5x5/demo.cpp new file mode 100644 index 00000000..0981b67f --- /dev/null +++ b/examples/breakout_rgbmatrix5x5/demo.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#include "breakout_rgbmatrix5x5.hpp" + +using namespace pimoroni; + +BreakoutRGBMatrix5x5 rgbmatrix5x5; + +int main() { + rgbmatrix5x5.init(); + + const pimoroni::RGBLookup colors[4] = { + {255, 0, 0}, + {0, 255, 0}, + {0, 0, 255}, + {128, 128, 128} + }; + + uint8_t col = 0; + while(1) { + pimoroni::RGBLookup color = colors[col]; + for(auto x = 0u; x < 5; x++) { + for(auto y = 0u; y < 5; y++) { + rgbmatrix5x5.set_pixel(x, y, color.r, color.g, color.b); + } + } + rgbmatrix5x5.update(0); + sleep_ms(1000); + col++; + col %= 4; + } + return 0; +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 42167b00..91ca9308 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -1,8 +1,10 @@ add_subdirectory(breakout_roundlcd) +add_subdirectory(breakout_rgbmatrix5x5) +add_subdirectory(breakout_matrix11x7) add_subdirectory(pico_graphics) add_subdirectory(pico_display) add_subdirectory(pico_unicorn) add_subdirectory(pico_scroll) add_subdirectory(pico_explorer) add_subdirectory(pico_rgb_keypad) -add_subdirectory(pico_wireless) \ No newline at end of file +add_subdirectory(pico_wireless) diff --git a/libraries/breakout_matrix11x7/CMakeLists.txt b/libraries/breakout_matrix11x7/CMakeLists.txt new file mode 100644 index 00000000..d8d27fb9 --- /dev/null +++ b/libraries/breakout_matrix11x7/CMakeLists.txt @@ -0,0 +1 @@ +include(breakout_matrix11x7.cmake) diff --git a/libraries/breakout_matrix11x7/breakout_matrix11x7.cmake b/libraries/breakout_matrix11x7/breakout_matrix11x7.cmake new file mode 100644 index 00000000..94895bba --- /dev/null +++ b/libraries/breakout_matrix11x7/breakout_matrix11x7.cmake @@ -0,0 +1,11 @@ +set(LIB_NAME breakout_matrix11x7) +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 is31fl3731) diff --git a/libraries/breakout_matrix11x7/breakout_matrix11x7.cpp b/libraries/breakout_matrix11x7/breakout_matrix11x7.cpp new file mode 100644 index 00000000..ed4b19f3 --- /dev/null +++ b/libraries/breakout_matrix11x7/breakout_matrix11x7.cpp @@ -0,0 +1,28 @@ +#include "breakout_matrix11x7.hpp" + +namespace pimoroni { + void BreakoutMatrix11x7::init() { + IS31FL3731::init(); + enable({ + 0b01111111, 0b01111111, + 0b01111111, 0b01111111, + 0b01111111, 0b01111111, + 0b01111111, 0b01111111, + 0b01111111, 0b01111111, + 0b01111111, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + 0b00000000, 0b00000000, + }, 0); + } + + uint8_t BreakoutMatrix11x7::lookup_pixel(uint8_t index) { + return lookup_table[index]; + } + + void BreakoutMatrix11x7::set_pixel(uint8_t x, uint8_t y, uint8_t c) { + uint8_t i = lookup_pixel(y * WIDTH + x); + set(i, c); + } + +} diff --git a/libraries/breakout_matrix11x7/breakout_matrix11x7.hpp b/libraries/breakout_matrix11x7/breakout_matrix11x7.hpp new file mode 100644 index 00000000..c78eaf4f --- /dev/null +++ b/libraries/breakout_matrix11x7/breakout_matrix11x7.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include "../../drivers/is31fl3731/is31fl3731.hpp" + +namespace pimoroni { + class BreakoutMatrix11x7 : public IS31FL3731 { + public: + static constexpr uint8_t WIDTH = 11; + static constexpr uint8_t HEIGHT = 7; + static constexpr int8_t DEFAULT_I2C_ADDRESS = 0x75; + static constexpr int8_t ALTERNATE_I2C_ADDRESS = 0x77; + + void init(); + + BreakoutMatrix11x7() : IS31FL3731(DEFAULT_I2C_ADDRESS) {}; + BreakoutMatrix11x7(uint8_t address) : IS31FL3731(address) {}; + BreakoutMatrix11x7(i2c_inst_t *i2c, uint8_t address, uint8_t sda, uint8_t scl) : IS31FL3731(i2c, address, sda, scl) {}; + + void set_pixel(uint8_t x, uint8_t y, uint8_t c); + + private: + uint8_t lookup_pixel(uint8_t index); + + // This wonderful lookup table maps the LEDs on Matrix 11x7 + const int8_t lookup_table[WIDTH * HEIGHT] = { + 6, 22, 38, 54, 70, 86, 14, 30, 46, 62, 78, + 5, 21, 37, 53, 69, 85, 13, 29, 45, 61, 77, + 4, 20, 36, 52, 68, 84, 12, 28, 44, 60, 76, + 3, 19, 35, 51, 67, 83, 11, 27, 43, 59, 75, + 2, 18, 34, 50, 66, 82, 10, 26, 42, 58, 74, + 1, 17, 33, 49, 65, 81, 9, 25, 41, 57, 73, + 0, 16, 32, 48, 64, 80, 8, 24, 40, 56, 72 + }; + }; +} diff --git a/libraries/breakout_rgbmatrix5x5/CMakeLists.txt b/libraries/breakout_rgbmatrix5x5/CMakeLists.txt new file mode 100644 index 00000000..f1070903 --- /dev/null +++ b/libraries/breakout_rgbmatrix5x5/CMakeLists.txt @@ -0,0 +1 @@ +include(breakout_rgbmatrix5x5.cmake) diff --git a/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cmake b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cmake new file mode 100644 index 00000000..daae1b7b --- /dev/null +++ b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cmake @@ -0,0 +1,11 @@ +set(LIB_NAME breakout_rgbmatrix5x5) +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 is31fl3731) diff --git a/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cpp b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cpp new file mode 100644 index 00000000..072cdf5f --- /dev/null +++ b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.cpp @@ -0,0 +1,35 @@ +#include "breakout_rgbmatrix5x5.hpp" + +namespace pimoroni { + + void BreakoutRGBMatrix5x5::init() { + IS31FL3731::init(); + enable({ + 0b00000000, 0b10111111, + 0b00111110, 0b00111110, + 0b00111111, 0b10111110, + 0b00000111, 0b10000110, + 0b00110000, 0b00110000, + 0b00111111, 0b10111110, + 0b00111111, 0b10111110, + 0b01111111, 0b11111110, + 0b01111111, 0b00000000 + }, 0); + } + + RGBLookup BreakoutRGBMatrix5x5::lookup_pixel(uint8_t index) { + return lookup_table[index]; + } + + void BreakoutRGBMatrix5x5::set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { + if (x == 1 || x == 3) { + y = 4 - y; + } + uint8_t index = y + (x * 5); + RGBLookup rgb = lookup_pixel(index); + set(rgb.r, r); + set(rgb.g, g); + set(rgb.b, b); + } + +} diff --git a/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.hpp b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.hpp new file mode 100644 index 00000000..b37da411 --- /dev/null +++ b/libraries/breakout_rgbmatrix5x5/breakout_rgbmatrix5x5.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "../../drivers/is31fl3731/is31fl3731.hpp" + +namespace pimoroni { + struct RGBLookup { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + class BreakoutRGBMatrix5x5 : public IS31FL3731 { + public: + static constexpr uint8_t WIDTH = 5; + static constexpr uint8_t HEIGHT = 5; + static constexpr int8_t DEFAULT_I2C_ADDRESS = 0x74; + static constexpr int8_t ALTERNATE_I2C_ADDRESS = 0x77; + + void init(); + void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b); + + BreakoutRGBMatrix5x5() : IS31FL3731(DEFAULT_I2C_ADDRESS) {}; + BreakoutRGBMatrix5x5(uint8_t address) : IS31FL3731(address) {}; + BreakoutRGBMatrix5x5(i2c_inst_t *i2c, uint8_t address, uint8_t sda, uint8_t scl) : IS31FL3731(i2c, address, sda, scl) {}; + private: + RGBLookup lookup_pixel(uint8_t index); + + // This wonderful lookup table maps the LEDs on RGB Matrix 5x5 + // from their 3x5x5 (remember, they're RGB) configuration to + // their specific location in the 144 pixel buffer. + const RGBLookup lookup_table[28] = { + {118, 69, 85}, + {117, 68, 101}, + {116, 84, 100}, + {115, 83, 99}, + {114, 82, 98}, + {113, 81, 97}, + {112, 80, 96}, + {134, 21, 37}, + {133, 20, 36}, + {132, 19, 35}, + {131, 18, 34}, + {130, 17, 50}, + {129, 33, 49}, + {128, 32, 48}, + {127, 47, 63}, + {121, 41, 57}, + {122, 25, 58}, + {123, 26, 42}, + {124, 27, 43}, + {125, 28, 44}, + {126, 29, 45}, + {15, 95, 111}, + {8, 89, 105}, + {9, 90, 106}, + {10, 91, 107}, + {11, 92, 108}, + {12, 76, 109}, + {13, 77, 93}, + }; + }; +}