From bf029ab0ec73a723a2d5ccf2b701e47ddb6c60fc Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:17:09 +1000 Subject: [PATCH 01/16] Added support for 16x16 LED Matrix --- micropython/modules/picographics/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/micropython/modules/picographics/README.md b/micropython/modules/picographics/README.md index 5d8ebbf06..1fedcfa01 100644 --- a/micropython/modules/picographics/README.md +++ b/micropython/modules/picographics/README.md @@ -82,6 +82,7 @@ Both the Interstate75 and Interstate75W support lots of different sizes of HUB75 The available display settings are listed here: +* 32 x 32 Matrix - `DISPLAY_INTERSTATE75_16X16` * 32 x 32 Matrix - `DISPLAY_INTERSTATE75_32X32` * 64 x 32 Matrix - `DISPLAY_INTERSTATE75_64X32` * 96 x 32 Matrix - `DISPLAY_INTERSTATE75_96X32` From dbf42031e1d3c910996f81601216e16ea97d6c31 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:17:45 +1000 Subject: [PATCH 02/16] Added support for 16x16 LED Matrix --- micropython/modules/picographics/picographics.c | 1 + 1 file changed, 1 insertion(+) diff --git a/micropython/modules/picographics/picographics.c b/micropython/modules/picographics/picographics.c index b1aeec19e..ef5106f5d 100644 --- a/micropython/modules/picographics/picographics.c +++ b/micropython/modules/picographics/picographics.c @@ -142,6 +142,7 @@ static const mp_map_elem_t picographics_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_DISPLAY_INKY_FRAME_4), MP_ROM_INT(DISPLAY_INKY_FRAME_4) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_GALACTIC_UNICORN), MP_ROM_INT(DISPLAY_GALACTIC_UNICORN) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_GFX_PACK), MP_ROM_INT(DISPLAY_GFX_PACK) }, + { MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_16X16), MP_ROM_INT(DISPLAY_INTERSTATE75_16X16) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_32X32), MP_ROM_INT(DISPLAY_INTERSTATE75_32X32) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_64X32), MP_ROM_INT(DISPLAY_INTERSTATE75_64X32) }, { MP_ROM_QSTR(MP_QSTR_DISPLAY_INTERSTATE75_96X32), MP_ROM_INT(DISPLAY_INTERSTATE75_96X32) }, From 0163ed27792c64bd517a3c64094ed4fb06c059c0 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:18:33 +1000 Subject: [PATCH 03/16] Added support for 16x16 LED Matrix --- micropython/modules/picographics/picographics.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/micropython/modules/picographics/picographics.cpp b/micropython/modules/picographics/picographics.cpp index 4fe1e2d04..bf2d8b7f0 100644 --- a/micropython/modules/picographics/picographics.cpp +++ b/micropython/modules/picographics/picographics.cpp @@ -131,6 +131,14 @@ bool get_display_settings(PicoGraphicsDisplay display, int &width, int &height, if(rotate == -1) rotate = (int)Rotation::ROTATE_0; if(pen_type == -1) pen_type = PEN_1BIT; break; + case DISPLAY_INTERSTATE75_16X16: + width = 16; + height = 16; + bus_type = BUS_PIO; + // Portrait to match labelling + if(rotate == -1) rotate = (int)Rotation::ROTATE_0; + if(pen_type == -1) pen_type = PEN_RGB888; + break; case DISPLAY_INTERSTATE75_32X32: width = 32; height = 32; From 35e17a9bfa2a58d147b5b82199c349b3e4c07e46 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:18:59 +1000 Subject: [PATCH 04/16] Added support for 16x16 LED Matrix --- micropython/modules/picographics/picographics.h | 1 + 1 file changed, 1 insertion(+) diff --git a/micropython/modules/picographics/picographics.h b/micropython/modules/picographics/picographics.h index 1812a875f..859f52f56 100644 --- a/micropython/modules/picographics/picographics.h +++ b/micropython/modules/picographics/picographics.h @@ -16,6 +16,7 @@ enum PicoGraphicsDisplay { DISPLAY_INKY_FRAME_4, DISPLAY_GALACTIC_UNICORN, DISPLAY_GFX_PACK, + DISPLAY_INTERSTATE75_16X16, DISPLAY_INTERSTATE75_32X32, DISPLAY_INTERSTATE75_64X32, DISPLAY_INTERSTATE75_96X32, From 62dfb7b252bd8d6cfc43a0d53ca891b4fc0c15de Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:19:32 +1000 Subject: [PATCH 05/16] Added support for 16x16 LED Matrix --- micropython/modules_py/interstate75.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropython/modules_py/interstate75.md b/micropython/modules_py/interstate75.md index 33974921c..694bb30b1 100644 --- a/micropython/modules_py/interstate75.md +++ b/micropython/modules_py/interstate75.md @@ -29,6 +29,7 @@ The version of Intersate75 you're using should be automatically detected. Check You can choose the HUB75 matrix display size that you wish to use by defining `display=` as one of the following: ```python +DISPLAY_INTERSTATE75_16X16 DISPLAY_INTERSTATE75_32X32 DISPLAY_INTERSTATE75_64X32 DISPLAY_INTERSTATE75_96X32 @@ -98,4 +99,4 @@ display.text("Hello World!", 0, 0) display.line(0, 0, 128, 64) board.update() # Update display with the above items ``` -All the picographics functions can be found [Here](../modules/picographics/README.md) \ No newline at end of file +All the picographics functions can be found [Here](../modules/picographics/README.md) From 0e263341f5301deb0559a00aef56bc0c514e9817 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Wed, 12 Jun 2024 15:20:23 +1000 Subject: [PATCH 06/16] Added support for 16x16 LED Matrix --- micropython/modules_py/interstate75.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/micropython/modules_py/interstate75.py b/micropython/modules_py/interstate75.py index 6792d8659..dfdd798eb 100644 --- a/micropython/modules_py/interstate75.py +++ b/micropython/modules_py/interstate75.py @@ -1,5 +1,5 @@ from pimoroni import RGBLED, Button -from picographics import PicoGraphics, DISPLAY_INTERSTATE75_32X32, DISPLAY_INTERSTATE75_64X32, DISPLAY_INTERSTATE75_96X32, DISPLAY_INTERSTATE75_96X48, DISPLAY_INTERSTATE75_128X32, DISPLAY_INTERSTATE75_64X64, DISPLAY_INTERSTATE75_128X64, DISPLAY_INTERSTATE75_192X64, DISPLAY_INTERSTATE75_256X64 +from picographics import PicoGraphics, DISPLAY_INTERSTATE75_16X16, DISPLAY_INTERSTATE75_32X32, DISPLAY_INTERSTATE75_64X32, DISPLAY_INTERSTATE75_96X32, DISPLAY_INTERSTATE75_96X48, DISPLAY_INTERSTATE75_128X32, DISPLAY_INTERSTATE75_64X64, DISPLAY_INTERSTATE75_128X64, DISPLAY_INTERSTATE75_192X64, DISPLAY_INTERSTATE75_256X64 from pimoroni_i2c import PimoroniI2C import hub75 import sys @@ -20,6 +20,7 @@ class Interstate75: LED_B_PIN = 18 # Display Types + DISPLAY_INTERSTATE75_16X16 = DISPLAY_INTERSTATE75_16X16 DISPLAY_INTERSTATE75_32X32 = DISPLAY_INTERSTATE75_32X32 DISPLAY_INTERSTATE75_64X32 = DISPLAY_INTERSTATE75_64X32 DISPLAY_INTERSTATE75_96X32 = DISPLAY_INTERSTATE75_96X32 From 1bef61ccf9f8778beeef694d539112aabb12ac21 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 15:01:06 +1000 Subject: [PATCH 07/16] Update hub75.cpp --- drivers/hub75/hub75.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index a894b70df..3d85e14a7 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 2.0f : 1.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 8.0f : 4.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); @@ -275,4 +275,4 @@ void Hub75::update(PicoGraphics *graphics) { } } } -} \ No newline at end of file +} From 6a274c2cbdeb5b5660cccb32c6d0b0157564769b Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 16:01:19 +1000 Subject: [PATCH 08/16] Update hub75.cpp --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 3d85e14a7..3de4e4f3f 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 8.0f : 4.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 28.0f : 56.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From 041e0421976d968458fd6f7ac39650bb5da60da3 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 16:09:16 +1000 Subject: [PATCH 09/16] Update hub75.cpp --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 3de4e4f3f..fc0b70b0e 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 28.0f : 56.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 1.0f : 2.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From 1da5d616e79feb75a8e62cab391d8e4a4e839946 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 17:54:47 +1000 Subject: [PATCH 10/16] Update hub75.cpp --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index fc0b70b0e..11528c9d0 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 1.0f : 2.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 2.0f : 1.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From d7ca5c308821956488410e2574f2c086c2226f0a Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 18:01:14 +1000 Subject: [PATCH 11/16] large clock divide --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 11528c9d0..5a972738d 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 2.0f : 1.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 128.0f : 64.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From 128f19e66b90c3565d768427858b3c292ffc331f Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Fri, 21 Jun 2024 18:08:12 +1000 Subject: [PATCH 12/16] Clock divide 32-16 --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 5a972738d..48b1f8c1c 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -131,7 +131,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 128.0f : 64.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 32.0f : 16.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From 2240b7ff91f09b0c22b7c65358178e98553bb1d7 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Sat, 22 Jun 2024 00:00:12 +1000 Subject: [PATCH 13/16] brightness = 1 --- drivers/hub75/hub75.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 48b1f8c1c..36f233124 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -42,6 +42,7 @@ Hub75::Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool if (width >= 128) brightness = 2; if (width >= 160) brightness = 1; } + brightness = 1; } void Hub75::set_color(uint x, uint y, Pixel c) { From db7474da8f7371c9ac07b93a9cee950b8635cce4 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Sat, 22 Jun 2024 00:07:50 +1000 Subject: [PATCH 14/16] reset --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index 36f233124..db9f66ed1 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -132,7 +132,7 @@ void Hub75::start(irq_handler_t handler) { hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); // Prevent flicker in Python caused by the smaller dataset just blasting through the PIO too quickly - pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 32.0f : 16.0f); + pio_sm_set_clkdiv(pio, sm_data, width <= 32 ? 2.0f : 1.0f); dma_channel = dma_claim_unused_channel(true); dma_channel_config config = dma_channel_get_default_config(dma_channel); From de6506271e9539388adcab9717e287393fda7b45 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Sat, 22 Jun 2024 00:27:11 +1000 Subject: [PATCH 15/16] reset all to normal --- drivers/hub75/hub75.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index db9f66ed1..33ee6e550 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -42,7 +42,7 @@ Hub75::Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool if (width >= 128) brightness = 2; if (width >= 160) brightness = 1; } - brightness = 1; + } void Hub75::set_color(uint x, uint y, Pixel c) { From d7b90c750617a889ec36b253dd61be7d71cfa5e4 Mon Sep 17 00:00:00 2001 From: mwilliams03 Date: Sat, 22 Jun 2024 12:35:13 +1000 Subject: [PATCH 16/16] Create interstate75_hello_world.cpp --- .../interstate75/interstate75_hello_world.cpp | 307 ++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 examples/interstate75/interstate75_hello_world.cpp diff --git a/examples/interstate75/interstate75_hello_world.cpp b/examples/interstate75/interstate75_hello_world.cpp new file mode 100644 index 000000000..648a685c4 --- /dev/null +++ b/examples/interstate75/interstate75_hello_world.cpp @@ -0,0 +1,307 @@ +#include +#include +#include +#include + +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include "hardware/vreg.h" + +#include "common/pimoroni_common.hpp" + +using namespace pimoroni; + +/* Interstate 75 - HUB75 from basic principles. + +While trying to get our I75 running I soon realised that I couldn't find any documentation or basics on the HUB75 protocol. + +Yes there were libraries and tidbits that were seemingly loosely related, but nothing really laying out clear steps to update the 32x32 display on my desk. + +So I wrote this. + +This code may not be technically correct, but the results are visually appealing and fast enough. + +More importantly, the code is legible and doesn't hide any implementation details beneath the inscrutable veneer of PIO. + +No interpolators, no DMA, no PIO here- if you want a beautifully optimised library for HUB75 output then this... isn't it. + +Just pure C. Pure CPU. Running on RP2040s Core1. + +*/ + +// Display size in pixels +// Should be either 64x64 or 32x32 but perhaps 64x32 an other sizes will work. +// Note: this example uses only 5 address lines so it's limited to 32*2 pixels. +const uint8_t WIDTH = 64; +const uint8_t HEIGHT = 64; + +// Settings below are correct for I76, change them to suit your setup: + +// Top half of display - 16 rows on a 32x32 panel +const uint PIN_R0 = 0; +const uint PIN_G0 = 1; +const uint PIN_B0 = 2; + +// Bottom half of display - 16 rows on a 64x64 panel +const uint PIN_R1 = 3; +const uint PIN_G1 = 4; +const uint PIN_B1 = 5; + +// Address pins, 5 lines = 2^5 = 32 values (max 64x64 display) +const uint PIN_ROW_A = 6; +const uint PIN_ROW_B = 7; +const uint PIN_ROW_C = 8; +const uint PIN_ROW_D = 9; +const uint PIN_ROW_E = 10; + +// Sundry things +const uint PIN_CLK = 11; // Clock +const uint PIN_STB = 12; // Strobe/Latch +const uint PIN_OE = 13; // Output Enable + +const bool CLK_POLARITY = 1; +const bool STB_POLARITY = 1; +const bool OE_POLARITY = 0; + +// User buttons and status LED +const uint PIN_SW_A = 14; +const uint PIN_SW_USER = 23; + +const uint PIN_LED_R = 16; +const uint PIN_LED_G = 17; +const uint PIN_LED_B = 18; + +// This gamma table is used to correct our 8-bit (0-255) colours up to 11-bit, +// allowing us to gamma correct without losing dynamic range. +const uint16_t GAMMA[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 47, 50, + 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, 79, 82, 85, 88, 91, 94, + 98, 101, 105, 108, 112, 115, 119, 123, 127, 131, 135, 139, 143, 147, 151, 155, + 160, 164, 169, 173, 178, 183, 187, 192, 197, 202, 207, 212, 217, 223, 228, 233, + 239, 244, 250, 255, 261, 267, 273, 279, 285, 291, 297, 303, 309, 316, 322, 328, + 335, 342, 348, 355, 362, 369, 376, 383, 390, 397, 404, 412, 419, 427, 434, 442, + 449, 457, 465, 473, 481, 489, 497, 505, 513, 522, 530, 539, 547, 556, 565, 573, + 582, 591, 600, 609, 618, 628, 637, 646, 656, 665, 675, 685, 694, 704, 714, 724, + 734, 744, 755, 765, 775, 786, 796, 807, 817, 828, 839, 850, 861, 872, 883, 894, + 905, 917, 928, 940, 951, 963, 975, 987, 998, 1010, 1022, 1035, 1047, 1059, 1071, 1084, + 1096, 1109, 1122, 1135, 1147, 1160, 1173, 1186, 1199, 1213, 1226, 1239, 1253, 1266, 1280, 1294, + 1308, 1321, 1335, 1349, 1364, 1378, 1392, 1406, 1421, 1435, 1450, 1465, 1479, 1494, 1509, 1524, + 1539, 1554, 1570, 1585, 1600, 1616, 1631, 1647, 1663, 1678, 1694, 1710, 1726, 1743, 1759, 1775, + 1791, 1808, 1824, 1841, 1858, 1875, 1891, 1908, 1925, 1943, 1960, 1977, 1994, 2012, 2029, 2047}; + + +// We don't *need* to make Pixel a fancy struct with RGB values, but it helps. +#pragma pack(push, 1) +struct alignas(4) Pixel { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t _; + constexpr Pixel() : r(0), g(0), b(0), _(0) {}; + constexpr Pixel(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b), _(0) {}; + constexpr Pixel(uint r, uint g, uint b) : r(r), g(g), b(b), _(0) {}; + constexpr Pixel(float r, float g, float b) : r((uint8_t)(r * 255.0f)), g((uint8_t)(g * 255.0f)), b((uint8_t)(b * 255.0f)), _(0) {}; +}; +#pragma pack(pop) + +// Create our front and back buffers. +// We'll draw into the frontbuffer and then copy everything into the backbuffer which will be used to refresh the screen. +// Double-buffering the display avoids screen tearing with fast animations or slow update rates. +Pixel backbuffer[WIDTH][HEIGHT]; +Pixel frontbuffer[WIDTH][HEIGHT]; + +// Basic function to convert Hue, Saturation and Value to an RGB colour +Pixel hsv_to_rgb(float h, float s, float v) { + if(h < 0.0f) { + h = 1.0f + fmod(h, 1.0f); + } + + int i = int(h * 6); + float f = h * 6 - i; + + v = v * 255.0f; + + float sv = s * v; + float fsv = f * sv; + + auto p = uint8_t(-sv + v); + auto q = uint8_t(-fsv + v); + auto t = uint8_t(fsv - sv + v); + + uint8_t bv = uint8_t(v); + + switch (i % 6) { + default: + case 0: return Pixel(bv, t, p); + case 1: return Pixel(q, bv, p); + case 2: return Pixel(p, bv, t); + case 3: return Pixel(p, q, bv); + case 4: return Pixel(t, p, bv); + case 5: return Pixel(bv, p, q); + } +} + +// Required for FM6126A-based displays which need some register config/init to work properly +void FM6126A_write_register(uint16_t value, uint8_t position) { + uint8_t threshold = WIDTH - position; + for(auto i = 0u; i < WIDTH; i++) { + auto j = i % 16; + bool b = value & (1 << j); + gpio_put(PIN_R0, b); + gpio_put(PIN_G0, b); + gpio_put(PIN_B0, b); + gpio_put(PIN_R1, b); + gpio_put(PIN_G1, b); + gpio_put(PIN_B1, b); + + // Assert strobe/latch if i > threshold + // This somehow indicates to the FM6126A which register we want to write :| + gpio_put(PIN_STB, i > threshold); + gpio_put(PIN_CLK, CLK_POLARITY); + sleep_us(10); + gpio_put(PIN_CLK, !CLK_POLARITY); + } +} + +void hub75_display_update() { + // Ridiculous register write nonsense for the FM6126A-based 64x64 matrix + FM6126A_write_register(0b1111111111111110, 12); + FM6126A_write_register(0b0000001000000000, 13); + + while (true) { + // 0. Copy the contents of the front buffer into our backbuffer for output to the display. + // This uses another whole backbuffer worth of memory, but prevents visual tearing at low frequencies. + memcpy((uint8_t *)backbuffer, (uint8_t *)frontbuffer, WIDTH * HEIGHT * sizeof(Pixel)); + + // Step through 0b00000001, 0b00000010, 0b00000100 etc + for(auto bit = 1u; bit < 1 << 11; bit <<= 1) { + // Since the display is in split into two equal halves, we step through y from 0 to HEIGHT / 2 + for(auto y = 0u; y < HEIGHT / 2; y++) { + + // 1. Shift out pixel data + // Shift out WIDTH pixels to the top and bottom half of the display + for(auto x = 0u; x < WIDTH; x++) { + // Get the current pixel for top/bottom half + // This is easy since we just need the pixels at X/Y and X/Y+HEIGHT/2 + Pixel pixel_top = backbuffer[x][y]; + Pixel pixel_bottom = backbuffer[x][y + HEIGHT / 2]; + + // Gamma correct the colour values from 8-bit to 11-bit + uint16_t pixel_top_b = GAMMA[pixel_top.b]; + uint16_t pixel_top_g = GAMMA[pixel_top.g]; + uint16_t pixel_top_r = GAMMA[pixel_top.r]; + + uint16_t pixel_bottom_b = GAMMA[pixel_bottom.b]; + uint16_t pixel_bottom_g = GAMMA[pixel_bottom.g]; + uint16_t pixel_bottom_r = GAMMA[pixel_bottom.r]; + + // Set the clock low while we set up the data pins + gpio_put(PIN_CLK, !CLK_POLARITY); + + // Top half + gpio_put(PIN_R0, (bool)(pixel_top_r & bit)); + gpio_put(PIN_G0, (bool)(pixel_top_g & bit)); + gpio_put(PIN_B0, (bool)(pixel_top_b & bit)); + + // Bottom half + gpio_put(PIN_R1, (bool)(pixel_bottom_r & bit)); + gpio_put(PIN_G1, (bool)(pixel_bottom_g & bit)); + gpio_put(PIN_B1, (bool)(pixel_bottom_b & bit)); + + // Wiggle the clock + // The gamma correction above will ensure our clock stays asserted + // for some small amount of time, avoiding the need for an explicit delay. + gpio_put(PIN_CLK, CLK_POLARITY); + } + + + // 2. Set address pins + // Set the address pins to reflect the row to light up: 0 through 15 for 32x32 pixel panels + // We decode our 5-bit row address out onto the 5 GPIO pins by masking each bit in turn. + gpio_put_masked(0b11111 << PIN_ROW_A, y << PIN_ROW_A); + + // 3. Assert latch/strobe signal (STB) + // This latches all the values we've just clocked into the column shift registers. + // The values will appear on the output pins, ready for the display to be driven. + gpio_put(PIN_STB, STB_POLARITY); + + // 4. Asset the output-enable signal (OE) + // This turns on the display for a brief period to light the selected rows/columns. + gpio_put(PIN_OE, OE_POLARITY); + + // 4. Delay + // Delay for a period of time coressponding to "bit"'s significance + for(auto s = 0u; s < bit; ++s) { + // The basic premise here is that "bit" will step through the values: + // 1, 2, 4, 8, 16, 32, 64, etc in sequence. + // If we plug this number into a delay loop, we'll get different magnitudes + // of delay which correspond exactly to the significance of each bit. + // The longer we delay here, the slower the overall panel refresh rate will be. + // But we need to delay *just enough* that we're not under-driving the panel and + // losing out on brightness. + asm volatile("nop \nnop"); // Batman! + } + + // 5. De-assert latch/strobe signal (STB) + output-enable signal (OE) + // Ready to go again! + gpio_put(PIN_STB, !STB_POLARITY); + gpio_put(PIN_OE, !OE_POLARITY); + + // 6. GOTO 1. + } + sleep_us(1); + } + } +} + +int main() { + // 1.3v allows overclock to ~280000-300000 but YMMV. Faster clock = faster screen update rate! + // vreg_set_voltage(VREG_VOLTAGE_1_30); + // sleep_ms(100); + + // 200MHz is roughly about the lower limit for driving a 64x64 display smoothly. + // Just don't look at it out of the corner of your eye. + set_sys_clock_khz(200000, false); + + // Set up allllll the GPIO + gpio_init(PIN_R0); gpio_set_function(PIN_R0, GPIO_FUNC_SIO); gpio_set_dir(PIN_R0, true); + gpio_init(PIN_G0); gpio_set_function(PIN_G0, GPIO_FUNC_SIO); gpio_set_dir(PIN_G0, true); + gpio_init(PIN_B0); gpio_set_function(PIN_B0, GPIO_FUNC_SIO); gpio_set_dir(PIN_B0, true); + + gpio_init(PIN_R1); gpio_set_function(PIN_R1, GPIO_FUNC_SIO); gpio_set_dir(PIN_R1, true); + gpio_init(PIN_G1); gpio_set_function(PIN_G1, GPIO_FUNC_SIO); gpio_set_dir(PIN_G1, true); + gpio_init(PIN_B1); gpio_set_function(PIN_B1, GPIO_FUNC_SIO); gpio_set_dir(PIN_B1, true); + + gpio_init(PIN_ROW_A); gpio_set_function(PIN_ROW_A, GPIO_FUNC_SIO); gpio_set_dir(PIN_ROW_A, true); + gpio_init(PIN_ROW_B); gpio_set_function(PIN_ROW_B, GPIO_FUNC_SIO); gpio_set_dir(PIN_ROW_B, true); + gpio_init(PIN_ROW_C); gpio_set_function(PIN_ROW_C, GPIO_FUNC_SIO); gpio_set_dir(PIN_ROW_C, true); + gpio_init(PIN_ROW_D); gpio_set_function(PIN_ROW_D, GPIO_FUNC_SIO); gpio_set_dir(PIN_ROW_D, true); + gpio_init(PIN_ROW_E); gpio_set_function(PIN_ROW_E, GPIO_FUNC_SIO); gpio_set_dir(PIN_ROW_E, true); + + gpio_init(PIN_CLK); gpio_set_function(PIN_CLK, GPIO_FUNC_SIO); gpio_set_dir(PIN_CLK, true); + gpio_init(PIN_STB); gpio_set_function(PIN_STB, GPIO_FUNC_SIO); gpio_set_dir(PIN_STB, true); + gpio_init(PIN_OE); gpio_set_function(PIN_OE, GPIO_FUNC_SIO); gpio_set_dir(PIN_OE, true); + + // Launch the display update routine on Core 1, it's hungry for cycles! + multicore_launch_core1(hub75_display_update); + + // Basic loop to draw something to the screen. + // This gets the distance from the middle of the display and uses it to paint a circular colour cycle. + while (true) { + float offset = millis() / 10000.0f; + for(auto x = 0u; x < WIDTH; x++) { + for(auto y = 0u; y < HEIGHT; y++) { + // Center our rainbow circles + float x1 = ((int)x - WIDTH / 2); + float y1 = ((int)y - HEIGHT / 2); + // Get hue as the distance from the display center as float from 0.0 to 1.0f. + float h = float(x1*x1 + y1*y1) / float(WIDTH*WIDTH + HEIGHT*HEIGHT); + // Offset our hue to animate the effect + h -= offset; + frontbuffer[x][y] = hsv_to_rgb(h, 1.0f, 1.0f); + } + } + } +}