diff --git a/micropython/modules/hub75/hub75.c b/micropython/modules/hub75/hub75.c new file mode 100644 index 00000000..38e1ea7e --- /dev/null +++ b/micropython/modules/hub75/hub75.c @@ -0,0 +1,46 @@ +#include "hub75.h" + + +/***** Methods *****/ +MP_DEFINE_CONST_FUN_OBJ_1(Hub75___del___obj, Hub75___del__); +MP_DEFINE_CONST_FUN_OBJ_KW(Hub75_set_rgb_obj, 5, Hub75_set_rgb); +MP_DEFINE_CONST_FUN_OBJ_1(Hub75_clear_obj, Hub75_clear); +MP_DEFINE_CONST_FUN_OBJ_1(Hub75_start_obj, Hub75_start); +MP_DEFINE_CONST_FUN_OBJ_1(Hub75_stop_obj, Hub75_stop); +MP_DEFINE_CONST_FUN_OBJ_1(Hub75_flip_obj, Hub75_flip); + +/***** Binding of Methods *****/ +STATIC const mp_rom_map_elem_t Hub75_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Hub75___del___obj) }, + { MP_ROM_QSTR(MP_QSTR_set_rgb), MP_ROM_PTR(&Hub75_set_rgb_obj) }, + { MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&Hub75_clear_obj) }, + { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&Hub75_start_obj) }, + { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&Hub75_stop_obj) }, + { MP_ROM_QSTR(MP_QSTR_flip), MP_ROM_PTR(&Hub75_flip_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(Hub75_locals_dict, Hub75_locals_dict_table); + +/***** Class Definition *****/ +const mp_obj_type_t Hub75_type = { + { &mp_type_type }, + .name = MP_QSTR_hub75, + .print = Hub75_print, + .make_new = Hub75_make_new, + .locals_dict = (mp_obj_dict_t*)&Hub75_locals_dict, +}; + +/***** Globals Table *****/ + +STATIC const mp_map_elem_t hub75_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_hub75) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Hub75), (mp_obj_t)&Hub75_type }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_hub75_globals, hub75_globals_table); + +/***** Module Definition *****/ +const mp_obj_module_t hub75_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_hub75_globals, +}; +MP_REGISTER_MODULE(MP_QSTR_hub75, hub75_user_cmodule, MODULE_HUB75_ENABLED); \ No newline at end of file diff --git a/micropython/modules/hub75/hub75.cpp b/micropython/modules/hub75/hub75.cpp new file mode 100644 index 00000000..28d1ba04 --- /dev/null +++ b/micropython/modules/hub75/hub75.cpp @@ -0,0 +1,155 @@ +#include +#include "lib/hub75.hpp" +#include "pico/multicore.h" + +#define MP_OBJ_TO_PTR2(o, t) ((t *)(uintptr_t)(o)) + + +extern "C" { +#include "hub75.h" +#include "py/builtin.h" +#include "py/mpthread.h" + +typedef struct _mp_obj_float_t { + mp_obj_base_t base; + mp_float_t value; +} mp_obj_float_t; + +const mp_obj_float_t const_float_1 = {{&mp_type_float}, 1.0f}; + +/********** WS2812 **********/ + +/***** Variables Struct *****/ +typedef struct _Hub75_obj_t { + mp_obj_base_t base; + Hub75* hub75; + void *buf; +} _Hub75_obj_t; + +_Hub75_obj_t *hub75_obj; + +/***** Print *****/ +void Hub75_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + (void)kind; //Unused input parameter + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Hub75_obj_t); + mp_print_str(print, "Hub75("); + + mp_print_str(print, "dimensions = "); + mp_obj_print_helper(print, mp_obj_new_int(self->hub75->width), PRINT_REPR); + mp_print_str(print, " x "); + mp_obj_print_helper(print, mp_obj_new_int(self->hub75->height), PRINT_REPR); + + mp_print_str(print, ")"); +} + +/***** Destructor ******/ +mp_obj_t Hub75___del__(mp_obj_t self_in) { + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Hub75_obj_t); + delete self->hub75; + return mp_const_none; +} + +/***** Constructor *****/ +mp_obj_t Hub75_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + _Hub75_obj_t *self = nullptr; + + enum { + ARG_width, + ARG_height, + ARG_buffer, + }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_width, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_height, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_buffer, MP_ARG_OBJ, {.u_obj = nullptr} } + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int width = args[ARG_width].u_int; + int height = args[ARG_height].u_int; + + Pixel *buffer = nullptr; + + if (args[ARG_buffer].u_obj) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(args[ARG_buffer].u_obj, &bufinfo, MP_BUFFER_RW); + buffer = (Pixel *)bufinfo.buf; + if(bufinfo.len < (size_t)(width * height * 2 * sizeof(Pixel))) { + mp_raise_ValueError("Supplied buffer is too small!"); + } + } else { + buffer = m_new(Pixel, width * height * 2); + } + + self = m_new_obj_with_finaliser(_Hub75_obj_t); + self->base.type = &Hub75_type; + self->buf = buffer; + self->hub75 = new Hub75(width, height, buffer); + hub75_obj = self; + + return MP_OBJ_FROM_PTR(self); +} + +mp_obj_t Hub75_clear(mp_obj_t self_in) { + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Hub75_obj_t); + self->hub75->clear(); + return mp_const_none; +} + +mp_obj_t Hub75_flip(mp_obj_t self_in) { + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Hub75_obj_t); + self->hub75->flip(); + return mp_const_none; +} + +void Hub75_display_update() { + if(hub75_obj) { + hub75_obj->hub75->start(); + } +} + +mp_obj_t Hub75_start(mp_obj_t self_in) { + //size_t stack_size = 0; + //mp_thread_create(&Hub75_display_update, nullptr, &stack_size); + multicore_reset_core1(); + multicore_launch_core1(Hub75_display_update); + return mp_const_none; +} + +mp_obj_t Hub75_stop(mp_obj_t self_in) { + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(self_in, _Hub75_obj_t); + self->hub75->stop(); + return mp_const_none; +} + +mp_obj_t Hub75_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_self, ARG_x, ARG_y, ARG_r, ARG_g, ARG_b }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ }, + { MP_QSTR_x, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_r, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_g, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_b, MP_ARG_REQUIRED | MP_ARG_INT } + }; + + // Parse args. + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + int x = args[ARG_x].u_int; + int y = args[ARG_y].u_int; + int r = args[ARG_r].u_int; + int g = args[ARG_g].u_int; + int b = args[ARG_b].u_int; + + _Hub75_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _Hub75_obj_t); + self->hub75->set_rgb(x, y, r, g, b); + + return mp_const_none; +} + +} \ No newline at end of file diff --git a/micropython/modules/hub75/hub75.h b/micropython/modules/hub75/hub75.h new file mode 100644 index 00000000..8dc3cae5 --- /dev/null +++ b/micropython/modules/hub75/hub75.h @@ -0,0 +1,15 @@ +// Include MicroPython API. +#include "py/runtime.h" + +/***** Extern of Class Definition *****/ +extern const mp_obj_type_t Hub75_type; + +/***** Extern of Class Methods *****/ +extern void Hub75_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); +extern mp_obj_t Hub75_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t Hub75___del__(mp_obj_t self_in); +extern mp_obj_t Hub75_start(mp_obj_t self_in); +extern mp_obj_t Hub75_stop(mp_obj_t self_in); +extern mp_obj_t Hub75_set_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +extern mp_obj_t Hub75_clear(mp_obj_t self_in); +extern mp_obj_t Hub75_flip(mp_obj_t self_in); \ No newline at end of file diff --git a/micropython/modules/hub75/lib/hub75.cpp b/micropython/modules/hub75/lib/hub75.cpp new file mode 100644 index 00000000..abb6f382 --- /dev/null +++ b/micropython/modules/hub75/lib/hub75.cpp @@ -0,0 +1,128 @@ +#include +#include + +#include "hub75.hpp" +#include "pico/stdlib.h" +#include "pico/multicore.h" + + +Hub75::Hub75(uint8_t width, uint8_t height, Pixel *buffer) + : width(width), height(height), front_buffer(buffer), back_buffer(buffer + width * height) + { + // 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, true); + + // 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); +} + +void Hub75::set_rgb(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) { + front_buffer[y * width + x] = Pixel(r, g, b); +} + +void Hub75::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::start() { + running = true; + + // Ridiculous register write nonsense for the FM6126A-based 64x64 matrix + FM6126A_write_register(0b1111111111111110, 12); + FM6126A_write_register(0b0000001000000000, 13); + + while (running) { + display_update(); + } +} + +void Hub75::stop() { + running = false; +} + +void Hub75::clear() { + +} + +void Hub75::flip() { + do_flip = true; +} + +void Hub75::display_update() { + if (do_flip) { + //std::swap(front_buffer, back_buffer); + memcpy((uint8_t *)back_buffer, (uint8_t *)front_buffer, width * height * sizeof(Pixel)); + do_flip = false; + } + + for(auto bit = 1u; bit < 1 << 11; bit <<= 1) { + for(auto y = 0u; y < height / 2; y++) { + for(auto x = 0u; x < width; x++) { + Pixel pixel_top = back_buffer[y * width + x]; + Pixel pixel_bottom = back_buffer[(y + height / 2) * width + x]; + + gpio_put(pin_clk, !clk_polarity); + + 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)); + + 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)); + + gpio_put(pin_clk, clk_polarity); + } + + gpio_put_masked(0b11111 << pin_row_a, y << pin_row_a); + gpio_put(pin_stb, stb_polarity); + gpio_put(pin_oe, oe_polarity); + + for(auto s = 0u; s < bit; ++s) { + asm volatile("nop \nnop"); + } + + gpio_put(pin_stb, !stb_polarity); + gpio_put(pin_oe, !oe_polarity); + } + sleep_us(1); + } +} \ No newline at end of file diff --git a/micropython/modules/hub75/lib/hub75.hpp b/micropython/modules/hub75/lib/hub75.hpp new file mode 100644 index 00000000..20e79423 --- /dev/null +++ b/micropython/modules/hub75/lib/hub75.hpp @@ -0,0 +1,91 @@ +#include + +// 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. +constexpr uint16_t GAMMA_12BIT[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 { + uint16_t r; + uint16_t g; + uint16_t b; + uint16_t _; + constexpr Pixel() : r(0), g(0), b(0), _(0) {}; + constexpr Pixel(uint8_t r, uint8_t g, uint8_t b) : r(GAMMA_12BIT[r]), g(GAMMA_12BIT[g]), b(GAMMA_12BIT[b]), _(0) {}; +}; +#pragma pack(pop) + +class Hub75 { + public: + uint8_t width; + uint8_t height; + Pixel *front_buffer; + Pixel *back_buffer; + bool running = false; + + // Top half of display - 16 rows on a 32x32 panel + unsigned int pin_r0 = 0; + unsigned int pin_g0 = 1; + unsigned int pin_b0 = 2; + + // Bottom half of display - 16 rows on a 64x64 panel + unsigned int pin_r1 = 3; + unsigned int pin_g1 = 4; + unsigned int pin_b1 = 5; + + // Address pins, 5 lines = 2^5 = 32 values (max 64x64 display) + unsigned int pin_row_a = 6; + unsigned int pin_row_b = 7; + unsigned int pin_row_c = 8; + unsigned int pin_row_d = 9; + unsigned int pin_row_e = 10; + + // Sundry things + unsigned int pin_clk = 11; // Clock + unsigned int pin_stb = 12; // Strobe/Latch + unsigned int 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 + unsigned int pin_sw_a = 14; + unsigned int pin_sw_user = 23; + + unsigned int pin_led_r = 16; + unsigned int pin_led_g = 17; + unsigned int pin_led_b = 18; + + volatile bool do_flip = false; + + Hub75(uint8_t width, uint8_t height, Pixel *buffer); + ~Hub75() { + }; + + void FM6126A_write_register(uint16_t value, uint8_t position); + void set_rgb(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b); + void display_update(); + void clear(); + void start(); + void stop(); + void flip(); +}; \ No newline at end of file diff --git a/micropython/modules/hub75/micropython.cmake b/micropython/modules/hub75/micropython.cmake new file mode 100644 index 00000000..dea407c7 --- /dev/null +++ b/micropython/modules/hub75/micropython.cmake @@ -0,0 +1,23 @@ +add_library(usermod_hub75 INTERFACE) + +target_sources(usermod_pico_display INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/hub75.c + ${CMAKE_CURRENT_LIST_DIR}/hub75.cpp + ${CMAKE_CURRENT_LIST_DIR}/lib/hub75.cpp +) + +target_include_directories(usermod_hub75 INTERFACE + ${CMAKE_CURRENT_LIST_DIR} +) + +target_compile_definitions(usermod_hub75 INTERFACE + MODULE_HUB75_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_hub75) + +set_source_files_properties( + ${CMAKE_CURRENT_LIST_DIR}/hub75.c + PROPERTIES COMPILE_FLAGS + "-Wno-discarded-qualifiers -Wno-implicit-int" +) diff --git a/micropython/modules/micropython.cmake b/micropython/modules/micropython.cmake index 4318406d..ea44e0a1 100644 --- a/micropython/modules/micropython.cmake +++ b/micropython/modules/micropython.cmake @@ -37,4 +37,5 @@ include(pico_display_2/micropython) include(pico_explorer/micropython) include(pico_wireless/micropython) include(plasma/micropython) +include(hub75/micropython) include(ulab/code/micropython)