diff --git a/drivers/hub75/hub75.cpp b/drivers/hub75/hub75.cpp index b3b2e3767..ff85563d2 100644 --- a/drivers/hub75/hub75.cpp +++ b/drivers/hub75/hub75.cpp @@ -1,3 +1,4 @@ + #include #include #include @@ -6,50 +7,92 @@ #include "hub75.hpp" -namespace pimoroni { - -Hub75::Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool inverted_stb, COLOR_ORDER color_order) - : width(width), height(height), panel_type(panel_type), inverted_stb(inverted_stb), color_order(color_order) - { - // Set up allllll the GPIO - gpio_init(pin_r0); gpio_set_function(pin_r0, GPIO_FUNC_SIO); gpio_set_dir(pin_r0, true); gpio_put(pin_r0, 0); - gpio_init(pin_g0); gpio_set_function(pin_g0, GPIO_FUNC_SIO); gpio_set_dir(pin_g0, true); gpio_put(pin_g0, 0); - gpio_init(pin_b0); gpio_set_function(pin_b0, GPIO_FUNC_SIO); gpio_set_dir(pin_b0, true); gpio_put(pin_b0, 0); - - gpio_init(pin_r1); gpio_set_function(pin_r1, GPIO_FUNC_SIO); gpio_set_dir(pin_r1, true); gpio_put(pin_r1, 0); - gpio_init(pin_g1); gpio_set_function(pin_g1, GPIO_FUNC_SIO); gpio_set_dir(pin_g1, true); gpio_put(pin_g1, 0); - gpio_init(pin_b1); gpio_set_function(pin_b1, GPIO_FUNC_SIO); gpio_set_dir(pin_b1, true); gpio_put(pin_b1, 0); - - gpio_init(pin_row_a); gpio_set_function(pin_row_a, GPIO_FUNC_SIO); gpio_set_dir(pin_row_a, true); gpio_put(pin_row_a, 0); - gpio_init(pin_row_b); gpio_set_function(pin_row_b, GPIO_FUNC_SIO); gpio_set_dir(pin_row_b, true); gpio_put(pin_row_b, 0); - gpio_init(pin_row_c); gpio_set_function(pin_row_c, GPIO_FUNC_SIO); gpio_set_dir(pin_row_c, true); gpio_put(pin_row_c, 0); - gpio_init(pin_row_d); gpio_set_function(pin_row_d, GPIO_FUNC_SIO); gpio_set_dir(pin_row_d, true); gpio_put(pin_row_d, 0); - gpio_init(pin_row_e); gpio_set_function(pin_row_e, GPIO_FUNC_SIO); gpio_set_dir(pin_row_e, true); gpio_put(pin_row_e, 0); - - gpio_init(pin_clk); gpio_set_function(pin_clk, GPIO_FUNC_SIO); gpio_set_dir(pin_clk, true); gpio_put(pin_clk, !clk_polarity); - gpio_init(pin_stb); gpio_set_function(pin_stb, GPIO_FUNC_SIO); gpio_set_dir(pin_stb, true); gpio_put(pin_clk, !stb_polarity); - gpio_init(pin_oe); gpio_set_function(pin_oe, GPIO_FUNC_SIO); gpio_set_dir(pin_oe, true); gpio_put(pin_clk, !oe_polarity); - - if (buffer == nullptr) { - back_buffer = new Pixel[width * height]; - managed_buffer = true; - } else { - back_buffer = buffer; - managed_buffer = false; - } - - if (brightness == 0) { +namespace pimoroni +{ + Hub75::Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool inverted_stb, COLOR_ORDER color_order) + : width(width), height(height), panel_type(panel_type), inverted_stb(inverted_stb), color_order(color_order), buffer(buffer) + { + self = this; + + // Set up allllll the GPIO + gpio_init(pin_r0); + gpio_set_function(pin_r0, GPIO_FUNC_SIO); + gpio_set_dir(pin_r0, true); + gpio_put(pin_r0, 0); + gpio_init(pin_g0); + gpio_set_function(pin_g0, GPIO_FUNC_SIO); + gpio_set_dir(pin_g0, true); + gpio_put(pin_g0, 0); + gpio_init(pin_b0); + gpio_set_function(pin_b0, GPIO_FUNC_SIO); + gpio_set_dir(pin_b0, true); + gpio_put(pin_b0, 0); + + gpio_init(pin_r1); + gpio_set_function(pin_r1, GPIO_FUNC_SIO); + gpio_set_dir(pin_r1, true); + gpio_put(pin_r1, 0); + gpio_init(pin_g1); + gpio_set_function(pin_g1, GPIO_FUNC_SIO); + gpio_set_dir(pin_g1, true); + gpio_put(pin_g1, 0); + gpio_init(pin_b1); + gpio_set_function(pin_b1, GPIO_FUNC_SIO); + gpio_set_dir(pin_b1, true); + gpio_put(pin_b1, 0); + + gpio_init(pin_row_a); + gpio_set_function(pin_row_a, GPIO_FUNC_SIO); + gpio_set_dir(pin_row_a, true); + gpio_put(pin_row_a, 0); + gpio_init(pin_row_b); + gpio_set_function(pin_row_b, GPIO_FUNC_SIO); + gpio_set_dir(pin_row_b, true); + gpio_put(pin_row_b, 0); + gpio_init(pin_row_c); + gpio_set_function(pin_row_c, GPIO_FUNC_SIO); + gpio_set_dir(pin_row_c, true); + gpio_put(pin_row_c, 0); + gpio_init(pin_row_d); + gpio_set_function(pin_row_d, GPIO_FUNC_SIO); + gpio_set_dir(pin_row_d, true); + gpio_put(pin_row_d, 0); + gpio_init(pin_row_e); + gpio_set_function(pin_row_e, GPIO_FUNC_SIO); + gpio_set_dir(pin_row_e, true); + gpio_put(pin_row_e, 0); + + gpio_init(pin_clk); + gpio_set_function(pin_clk, GPIO_FUNC_SIO); + gpio_set_dir(pin_clk, true); + gpio_put(pin_clk, !clk_polarity); + gpio_init(pin_stb); + gpio_set_function(pin_stb, GPIO_FUNC_SIO); + gpio_set_dir(pin_stb, true); + gpio_put(pin_clk, !stb_polarity); + gpio_init(pin_oe); + gpio_set_function(pin_oe, GPIO_FUNC_SIO); + gpio_set_dir(pin_oe, true); + gpio_put(pin_clk, !oe_polarity); + + if (brightness == 0) + { #if PICO_RP2350 - brightness = 6; + brightness = 6; #else - if (width >= 64) brightness = 6; - if (width >= 96) brightness = 3; - if (width >= 128) brightness = 2; - if (width >= 160) brightness = 1; + if (width >= 64) + brightness = 6; + if (width >= 96) + brightness = 3; + if (width >= 128) + brightness = 2; + if (width >= 160) + brightness = 1; #endif - } + } - switch (color_order) { + switch (color_order) + { case COLOR_ORDER::RGB: r_shift = 0; g_shift = 10; @@ -80,312 +123,528 @@ Hub75::Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool g_shift = 10; b_shift = 0; break; - + } } -} -void Hub75::set_color(uint x, uint y, Pixel c) { - int offset = 0; - if(x >= width || y >= height) return; - if(y >= height / 2) { - y -= height / 2; - offset = (y * width + x) * 2; - offset += 1; - } else { - offset = (y * width + x) * 2; + void Hub75::set_color(uint x, uint y, Pixel c) + { + int offset = 0; + if (x >= width || y >= height) + return; + if (y >= height / 2) + { + y -= height / 2; + offset = (y * width + x) * 2; + offset += 1; + } + else + { + offset = (y * width + x) * 2; + } + + frame_buffer[offset] = c.color; } - back_buffer[offset] = c; -} -void Hub75::set_pixel(uint x, uint y, uint8_t r, uint8_t g, uint8_t b) { - int offset = 0; - if(x >= width || y >= height) return; - if(y >= height / 2) { - y -= height / 2; - offset = (y * width + x) * 2; - offset += 1; - } else { - offset = (y * width + x) * 2; + void Hub75::set_pixel(uint x, uint y, uint8_t r, uint8_t g, uint8_t b) + { + int offset = 0; + if (x >= width || y >= height) + return; + if (y >= height / 2) + { + y -= height / 2; + offset = (y * width + x) * 2; + offset += 1; + } + else + { + offset = (y * width + x) * 2; + } + frame_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); } - back_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); -} -void Hub75::FM6126A_write_register(uint16_t value, uint8_t position) { - gpio_put(pin_clk, !clk_polarity); - gpio_put(pin_stb, !stb_polarity); - - 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); + void Hub75::FM6126A_write_register(uint16_t value, uint8_t position) + { gpio_put(pin_clk, !clk_polarity); + gpio_put(pin_stb, !stb_polarity); + + 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::FM6126A_setup() { - // Ridiculous register write nonsense for the FM6126A-based 64x64 matrix - FM6126A_write_register(0b1111111111111110, 12); - FM6126A_write_register(0b0000001000000000, 13); -} + void Hub75::FM6126A_setup() + { + // Ridiculous register write nonsense for the FM6126A-based 64x64 matrix + FM6126A_write_register(0b1111111111111110, 12); + FM6126A_write_register(0b0000001000000000, 13); + } -void Hub75::start(irq_handler_t handler) { - if(handler) { - if (panel_type == PANEL_FM6126A) { - FM6126A_setup(); + /** + * @brief Configures the PIO state machines for HUB75 matrix control. + * + * This function sets up the PIO state machines responsible for shifting + * pixel data and controlling row addressing. If a PIO state machine cannot + * be claimed, it prints an error message. + */ + void Hub75::configure_pio() + { + if (!pio_claim_free_sm_and_add_program(&hub75_data_rgb888_program, &pio_config.data_pio, &pio_config.sm_data, &pio_config.data_prog_offs)) + { + fprintf(stderr, "Failed to claim PIO state machine for hub75_data_rgb888_program\n"); } - uint latch_cycles = clock_get_hz(clk_sys) / 4000000; - - // Claim the PIO so we can clean it upon soft restart - pio_sm_claim(pio, sm_data); - pio_sm_claim(pio, sm_row); - - data_prog_offs = pio_add_program(pio, &hub75_data_rgb888_program); - if (inverted_stb) { - row_prog_offs = pio_add_program(pio, &hub75_row_inverted_program); - } else { - row_prog_offs = pio_add_program(pio, &hub75_row_program); + if (inverted_stb) + { + if (!pio_claim_free_sm_and_add_program(&hub75_row_inverted_program, &pio_config.row_pio, &pio_config.sm_row, &pio_config.row_prog_offs)) + { + fprintf(stderr, "Failed to claim PIO state machine for hub75_row_inverted_program\n"); + } + } + else + { + if (!pio_claim_free_sm_and_add_program(&hub75_row_program, &pio_config.row_pio, &pio_config.sm_row, &pio_config.row_prog_offs)) + { + fprintf(stderr, "Failed to claim PIO state machine for hub75_row_program\n"); + } } - hub75_data_rgb888_program_init(pio, sm_data, data_prog_offs, DATA_BASE_PIN, pin_clk); - hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb, latch_cycles); - - // 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); - - dma_channel = dma_claim_unused_channel(true); - dma_channel_config config = dma_channel_get_default_config(dma_channel); - channel_config_set_transfer_data_size(&config, DMA_SIZE_32); - channel_config_set_bswap(&config, false); - channel_config_set_dreq(&config, pio_get_dreq(pio, sm_data, true)); - dma_channel_configure(dma_channel, &config, &pio->txf[sm_data], NULL, 0, false); - - - // Same handler for both DMA channels - irq_add_shared_handler(DMA_IRQ_0, handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); - dma_channel_set_irq0_enabled(dma_channel, true); + hub75_data_rgb888_program_init(pio_config.data_pio, pio_config.sm_data, pio_config.data_prog_offs, DATA_BASE_PIN, pin_clk); + hub75_row_program_init(pio_config.row_pio, pio_config.sm_row, pio_config.row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, pin_stb); + } - irq_set_enabled(DMA_IRQ_0, true); + /** + * @brief Claims an available DMA channel. + * + * Attempts to claim an unused DMA channel. If no channels are available, + * prints an error message and exits the program. + * + * @param channel_name A descriptive name for the channel, used in error messages. + * @return The claimed DMA channel number. + */ + inline int Hub75::claim_dma_channel(const char *channel_name) + { + int dma_channel = dma_claim_unused_channel(true); + if (dma_channel < 0) + { + fprintf(stderr, "Failed to claim DMA channel for %s\n", channel_name); + exit(EXIT_FAILURE); // Stop execution + } + return dma_channel; + } - row = 0; - bit = 0; + /** + * @brief Configures and claims DMA channels for HUB75 control. + * + * This function assigns DMA channels to handle pixel data transfer, + * dummy pixel data, output enable signal, and output enable completion. + * If a DMA channel cannot be claimed, the function prints an error message and exits. + */ + void Hub75::configure_dma_channels() + { + pixel_chan = claim_dma_channel("pixel channel"); + dummy_pixel_chan = claim_dma_channel("dummy pixel channel"); + oen_chan = claim_dma_channel("output enable channel"); + oen_finished_chan = claim_dma_channel("output enable has finished channel"); + } - hub75_data_rgb888_set_shift(pio, sm_data, data_prog_offs, bit); - dma_channel_set_trans_count(dma_channel, width * 2, false); - dma_channel_set_read_addr(dma_channel, &back_buffer, true); + /** + * @brief Configures a DMA input channel for transferring data to a PIO state machine. + * + * This function sets up a DMA channel to transfer data from memory to a PIO + * state machine. It configures transfer size, address incrementing, and DMA + * chaining to ensure seamless operation. + * + * @param channel DMA channel number to configure. + * @param transfer_count Number of data transfers per DMA transaction. + * @param dma_size Data transfer size (8, 16, or 32-bit). + * @param read_incr Whether the read address should increment after each transfer. + * @param chain_to DMA channel to chain the transfer to, enabling automatic triggering. + * @param pio PIO instance that will receive the transferred data. + * @param sm State machine within the PIO instance that will process the data. + */ + void Hub75::dma_input_channel_setup(uint channel, + uint transfer_count, + enum dma_channel_transfer_size dma_size, + bool read_incr, + uint chain_to, + PIO pio, + uint sm) + { + dma_channel_config conf = dma_channel_get_default_config(channel); + channel_config_set_transfer_data_size(&conf, dma_size); + channel_config_set_read_increment(&conf, read_incr); + channel_config_set_write_increment(&conf, false); + channel_config_set_dreq(&conf, pio_get_dreq(pio, sm, true)); + + channel_config_set_chain_to(&conf, chain_to); + + dma_channel_configure( + channel, // Channel to be configured + &conf, // DMA configuration + &pio->txf[sm], // Write address: PIO TX FIFO + NULL, // Read address: set later + transfer_count, // Number of transfers per transaction + false // Do not start transfer immediately + ); } -} -void Hub75::stop(irq_handler_t handler) { + /** + * @brief Sets up DMA transfers for the HUB75 matrix. + * + * Configures multiple DMA channels to transfer pixel data, dummy pixel data, + * and output enable signal, to the PIO state machines controlling the HUB75 matrix. + * Also configures the DMA channel which gets active when an output enable signal has finished + */ + void Hub75::setup_dma_transfers() + { + dma_input_channel_setup(pixel_chan, row_width, DMA_SIZE_32, true, dummy_pixel_chan, pio_config.data_pio, pio_config.sm_data); + dma_input_channel_setup(dummy_pixel_chan, 8, DMA_SIZE_32, false, oen_chan, pio_config.data_pio, pio_config.sm_data); + dma_input_channel_setup(oen_chan, 1, DMA_SIZE_32, true, oen_chan, pio_config.row_pio, pio_config.sm_row); + + dma_channel_set_read_addr(dummy_pixel_chan, dummy_pixel_data, false); + + row_in_bit_plane = row_address | ((brightness << bit_plane) << 5); + dma_channel_set_read_addr(oen_chan, &row_in_bit_plane, false); + + dma_channel_config oen_finished_config = dma_channel_get_default_config(oen_finished_chan); + channel_config_set_transfer_data_size(&oen_finished_config, DMA_SIZE_32); + channel_config_set_read_increment(&oen_finished_config, false); + channel_config_set_write_increment(&oen_finished_config, false); + channel_config_set_dreq(&oen_finished_config, pio_get_dreq(pio_config.row_pio, pio_config.sm_row, false)); + dma_channel_configure(oen_finished_chan, &oen_finished_config, &oen_finished_data, &pio_config.row_pio->rxf[pio_config.sm_row], 1, false); + } - irq_set_enabled(DMA_IRQ_0, false); + /** + * @brief Sets up and enables the DMA interrupt handler. + * + * Registers the interrupt service routine (ISR) for the output enable finished DMA channel. + * This is the channel that triggers the end of the output enable signal, which in turn + * triggers the start of the next row's pixel data transfer. + */ + void Hub75::setup_dma_irq() + { + irq_set_exclusive_handler(DMA_IRQ_0, oen_finished_handler_wrapper); + dma_channel_set_irq0_enabled(oen_finished_chan, true); + irq_set_enabled(DMA_IRQ_0, true); - if(dma_channel != -1 && dma_channel_is_claimed(dma_channel)) { - dma_channel_set_irq0_enabled(dma_channel, false); - irq_remove_handler(DMA_IRQ_0, handler); - //dma_channel_wait_for_finish_blocking(dma_channel); - dma_channel_abort(dma_channel); - dma_channel_acknowledge_irq0(dma_channel); - dma_channel_unclaim(dma_channel); + irq_handler_t t = irq_get_exclusive_handler(DMA_IRQ_1); } - if(pio_sm_is_claimed(pio, sm_data)) { - pio_sm_set_enabled(pio, sm_data, false); - pio_sm_drain_tx_fifo(pio, sm_data); - pio_remove_program(pio, &hub75_data_rgb888_program, data_prog_offs); - pio_sm_unclaim(pio, sm_data); + /** + * @brief Starts the DMA transfers for the HUB75 display driver. + * + * This function initializes the DMA transfers by setting up the write address + * for the Output Enable finished DMA channel and the read address for pixel data. + * It ensures that the display begins processing frames. + */ + void Hub75::start_hub75_driver() + { + dma_channel_set_write_addr(oen_finished_chan, &oen_finished_data, true); + dma_channel_set_read_addr(pixel_chan, &frame_buffer[row_address * row_width], true); } - if(pio_sm_is_claimed(pio, sm_row)) { - pio_sm_set_enabled(pio, sm_row, false); - pio_sm_drain_tx_fifo(pio, sm_row); - if (inverted_stb) { - pio_remove_program(pio, &hub75_row_inverted_program, row_prog_offs); - } else { - pio_remove_program(pio, &hub75_row_program, row_prog_offs); + /** + * @brief Initializes the HUB75 display by setting up DMA and PIO subsystems. + * + * This function configures the necessary hardware components to drive a HUB75 + * LED matrix display. It initializes DMA channels, PIO state machines, and + * interrupt handlers. + * + * @param w Width of the HUB75 display in pixels. + * @param h Height of the HUB75 display in pixels. + */ + void Hub75::create_hub75_driver(uint w, uint h) + { + row_width = w << 1; + row_threshold = h >> 1; + + row_offset = w * row_threshold; + + frame_buffer = new uint32_t[w * h](); // Allocate memory for frame buffer + + if (buffer != nullptr) + { + uint j = 0; + for (int i = 0; i < w * h; i += 2) + { + frame_buffer[i] = GAMMA_10BIT[(buffer[j].color & 0x0000ff) >> 0] << b_shift | GAMMA_10BIT[(buffer[j].color & 0x00ff00) >> 8] << g_shift | GAMMA_10BIT[(buffer[j].color & 0xff0000) >> 16] << r_shift; + frame_buffer[i + 1] = GAMMA_10BIT[(buffer[j + row_offset].color & 0x0000ff) >> 0] << b_shift | GAMMA_10BIT[(buffer[j + row_offset].color & 0x00ff00) >> 8] << g_shift | GAMMA_10BIT[(buffer[j + row_offset].color & 0xff0000) >> 16] << r_shift;; + j++; + } } - pio_sm_unclaim(pio, sm_row); - } - - // Make sure the GPIO is in a known good state - // since we don't know what the PIO might have done with it - gpio_put_masked(0b111111 << pin_r0, 0); - gpio_put_masked(0b11111 << pin_row_a, 0); - gpio_put(pin_clk, !clk_polarity); - gpio_put(pin_clk, !oe_polarity); -} -Hub75::~Hub75() { - if (managed_buffer) { - delete[] back_buffer; + configure_pio(); + configure_dma_channels(); + setup_dma_transfers(); + setup_dma_irq(); } -} -void Hub75::clear() { - for(auto x = 0u; x < width; x++) { - for(auto y = 0u; y < height; y++) { - set_pixel(x, y, 0, 0, 0); - } + void Hub75::start(irq_handler_t handler) + { + handler = handler; + start(); } -} - -void Hub75::dma_complete() { + void Hub75::start() + { + if (panel_type == PANEL_FM6126A) + { + FM6126A_setup(); + } - if(dma_channel_get_irq0_status(dma_channel)) { - dma_channel_acknowledge_irq0(dma_channel); + create_hub75_driver(width, height); + start_hub75_driver(); + } - // Push out a dummy pixel for each row - pio_sm_put_blocking(pio, sm_data, 0); - pio_sm_put_blocking(pio, sm_data, 0); + void Hub75::stop() + { + irq_set_enabled(DMA_IRQ_0, false); - // SM is finished when it stalls on empty TX FIFO - hub75_wait_tx_stall(pio, sm_data); + if (oen_finished_chan != -1 && dma_channel_is_claimed(oen_finished_chan)) + { + dma_channel_set_irq0_enabled(oen_finished_chan, false); + irq_remove_handler(DMA_IRQ_0, oen_finished_handler_wrapper); - // Check that previous OEn pulse is finished, else things WILL get out of sequence - hub75_wait_tx_stall(pio, sm_row); + dma_channel_abort(oen_finished_chan); + dma_channel_acknowledge_irq0(oen_finished_chan); + dma_channel_unclaim(oen_finished_chan); + } - // Latch row data, pulse output enable for new row. - pio_sm_put_blocking(pio, sm_row, row | (brightness << 5 << bit)); + if (oen_chan != -1 && dma_channel_is_claimed(oen_chan)) + { + dma_channel_abort(oen_chan); + dma_channel_acknowledge_irq0(oen_chan); + dma_channel_unclaim(oen_chan); + } - row++; + if (dummy_pixel_chan != -1 && dma_channel_is_claimed(dummy_pixel_chan)) + { + dma_channel_abort(dummy_pixel_chan); + dma_channel_acknowledge_irq0(dummy_pixel_chan); + dma_channel_unclaim(dummy_pixel_chan); + } - if(row == height / 2) { - row = 0; - bit++; - if (bit == BIT_DEPTH) { - bit = 0; - } - hub75_data_rgb888_set_shift(pio, sm_data, data_prog_offs, bit); + if (pixel_chan != -1 && dma_channel_is_claimed(pixel_chan)) + { + dma_channel_abort(pixel_chan); + dma_channel_acknowledge_irq0(pixel_chan); + dma_channel_unclaim(pixel_chan); } - dma_channel_set_trans_count(dma_channel, width * 2, false); - dma_channel_set_read_addr(dma_channel, &back_buffer[row * width * 2], true); - } -} + if (pio_sm_is_claimed(pio, sm_data)) + { + pio_sm_set_enabled(pio, sm_data, false); + pio_sm_drain_tx_fifo(pio, sm_data); + pio_remove_program(pio, &hub75_data_rgb888_program, data_prog_offs); + pio_sm_unclaim(pio, sm_data); + } -void Hub75::copy_to_back_buffer(void *data, size_t len, int start_x, int start_y, int g_width, int g_height) { - uint8_t *p = (uint8_t *)data; - - if(g_width == int32_t(width / 2) && g_height == int32_t(height * 2)) { - for(int y = start_y; y < g_height; y++) { - int offsety = 0; - int sy = y; - int basex = 0; - - // Assuming our canvas is 128x128 and our display is 256x64, - // consisting of 2x128x64 panels, remap the bottom half - // of the canvas to the right-half of the display, - // This gives us an optional square arrangement. - if (sy >= int(height)) { - sy -= height; - basex = width / 2; + if (pio_sm_is_claimed(pio, sm_row)) + { + pio_sm_set_enabled(pio, sm_row, false); + pio_sm_drain_tx_fifo(pio, sm_row); + if (inverted_stb) + { + pio_remove_program(pio, &hub75_row_inverted_program, row_prog_offs); } - - // Interlace the top and bottom halves of the panel. - // Since these are scanned out simultaneously to two chains - // of shift registers we need each pair of rows - // (N and N + height / 2) to be adjacent in the buffer. - offsety = width * 2; - if(sy >= int(height / 2)) { - sy -= height / 2; - offsety *= sy; - offsety += 1; - } else { - offsety *= sy; + else + { + pio_remove_program(pio, &hub75_row_program, row_prog_offs); } + pio_sm_unclaim(pio, sm_row); + } - for(int x = start_x; x < g_width; x++) { - int sx = x; - uint8_t b = *p++; - uint8_t g = *p++; - uint8_t r = *p++; - - // Assumes width / 2 is even. - if (basex & 1) { - sx = basex - sx; - } else { - sx += basex; - } - int offset = offsety + sx * 2; + // Make sure the GPIO is in a known good state + // since we don't know what the PIO might have done with it + gpio_put_masked(0b111111 << pin_r0, 0); + gpio_put_masked(0b11111 << pin_row_a, 0); + gpio_put(pin_clk, !clk_polarity); + gpio_put(pin_clk, !oe_polarity); + } - back_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); + Hub75::~Hub75() + { + delete[] frame_buffer; + self = nullptr; + } - // Skip the empty byte in out 32-bit aligned 24-bit colour. - p++; + void Hub75::clear() + { + for (int i = 0; i < width * height; i += 2) + { + frame_buffer[i] = 0; + frame_buffer[i + 1] = 0; + } + } - len -= 4; + void Hub75::copy_to_back_buffer(void *data, size_t len, int start_x, int start_y, int g_width, int g_height) + { + uint8_t *p = (uint8_t *)data; - if(len == 0) { - return; - } - } - } - } else { - for(uint y = start_y; y < height; y++) { - for(uint x = start_x; x < width; x++) { - int offset = 0; + if (g_width == int32_t(width / 2) && g_height == int32_t(height * 2)) + { + for (int y = start_y; y < g_height; y++) + { + int offsety = 0; int sy = y; - int sx = x; - uint8_t b = *p++; - uint8_t g = *p++; - uint8_t r = *p++; + int basex = 0; + + // Assuming our canvas is 128x128 and our display is 256x64, + // consisting of 2x128x64 panels, remap the bottom half + // of the canvas to the right-half of the display, + // This gives us an optional square arrangement. + if (sy >= int(height)) + { + sy -= height; + basex = width / 2; + } // Interlace the top and bottom halves of the panel. // Since these are scanned out simultaneously to two chains // of shift registers we need each pair of rows // (N and N + height / 2) to be adjacent in the buffer. - offset = width * 2; - if(sy >= int(height / 2)) { + offsety = width * 2; + if (sy >= int(height / 2)) + { sy -= height / 2; - offset *= sy; - offset += 1; - } else { - offset *= sy; + offsety *= sy; + offsety += 1; + } + else + { + offsety *= sy; } - offset += sx * 2; - - back_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); - - // Skip the empty byte in out 32-bit aligned 24-bit colour. - p++; - - len -= 4; - if(len == 0) { - return; + for (int x = start_x; x < g_width; x++) + { + int sx = x; + uint8_t b = *p++; + uint8_t g = *p++; + uint8_t r = *p++; + + // Assumes width / 2 is even. + if (basex & 1) + { + sx = basex - sx; + } + else + { + sx += basex; + } + int offset = offsety + sx * 2; + + frame_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); + + // Skip the empty byte in out 32-bit aligned 24-bit colour. + p++; + + len -= 4; + + if (len == 0) + { + return; + } + } + } + } + else + { + for (uint y = start_y; y < height; y++) + { + for (uint x = start_x; x < width; x++) + { + int offset = 0; + int sy = y; + int sx = x; + uint8_t b = *p++; + uint8_t g = *p++; + uint8_t r = *p++; + + // Interlace the top and bottom halves of the panel. + // Since these are scanned out simultaneously to two chains + // of shift registers we need each pair of rows + // (N and N + height / 2) to be adjacent in the buffer. + offset = width * 2; + if (sy >= int(height / 2)) + { + sy -= height / 2; + offset *= sy; + offset += 1; + } + else + { + offset *= sy; + } + offset += sx * 2; + + frame_buffer[offset] = (GAMMA_10BIT[b] << b_shift) | (GAMMA_10BIT[g] << g_shift) | (GAMMA_10BIT[r] << r_shift); + + // Skip the empty byte in out 32-bit aligned 24-bit colour. + p++; + + len -= 4; + + if (len == 0) + { + return; + } } } } } -} - -void Hub75::update(PicoGraphics *graphics) { - if(graphics->pen_type == PicoGraphics::PEN_RGB888) { - copy_to_back_buffer(graphics->frame_buffer, width * height * sizeof(RGB888), 0, 0, graphics->bounds.w, graphics->bounds.h); - } else { - unsigned int offset = 0; - graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, &offset, &graphics](void *data, size_t length) { + /** + * @brief Updates the frame buffer with pixel data from the source array. + * + * This function takes a source array of pixel data and updates the frame buffer + * with interleaved pixel values. The pixel values are gamma-corrected to 10 bits using a lookup table. + * + * @param src Pointer to the source pixel data array (RGB888 format). + */ + void Hub75::update(PicoGraphics *graphics) + { + if (graphics->pen_type == PicoGraphics::PEN_RGB888) + { + uint32_t const *src = static_cast(graphics->frame_buffer); + + // Ramp up color resolution from 8 to 10 bits via gamma table look-up + // Interweave pixels from intermediate buffer into target image to fit the format expected by Hub75 LED panel. + uint j = 0; + for (int i = 0; i < width * height; i += 2) + { + frame_buffer[i] = GAMMA_10BIT[(src[j] & 0x0000ff) >> 0] << b_shift | GAMMA_10BIT[(src[j] & 0x00ff00) >> 8] << g_shift | GAMMA_10BIT[(src[j] & 0xff0000) >> 16] << r_shift; + frame_buffer[i + 1] = GAMMA_10BIT[(src[j + row_offset] & 0x0000ff) >> 0] << b_shift | GAMMA_10BIT[(src[j + row_offset] & 0x00ff00) >> 8] << g_shift | GAMMA_10BIT[(src[j + row_offset] & 0xff0000) >> 16] << r_shift; + j++; + } + } + else + { + unsigned int offset = 0; + graphics->frame_convert(PicoGraphics::PEN_RGB888, [this, &offset, &graphics](void *data, size_t length) + { if (length > 0) { int offset_y = offset / graphics->bounds.w; int offset_x = offset - (offset_y * graphics->bounds.w); copy_to_back_buffer(data, length, offset_x, offset_y, graphics->bounds.w, graphics->bounds.h); offset += length / sizeof(RGB888); - } - }); + } }); + } } } -} diff --git a/drivers/hub75/hub75.hpp b/drivers/hub75/hub75.hpp index 1354406b4..718b545d1 100644 --- a/drivers/hub75/hub75.hpp +++ b/drivers/hub75/hub75.hpp @@ -10,150 +10,255 @@ #include "hub75.pio.h" #endif -namespace pimoroni { -const uint DATA_BASE_PIN = 0; -const uint DATA_N_PINS = 6; -const uint ROWSEL_BASE_PIN = 6; -const uint ROWSEL_N_PINS = 5; -const uint BIT_DEPTH = 10; - -/* -10-bit gamma table, allowing us to gamma correct our 8-bit colour values up -to 10-bit without losing dynamic range. - -Calculated with the following Python code: - -gamma_lut = [int(round(1024 * (x / (256 - 1)) ** 2.2)) for x in range(256)] - -Post-processed to enforce a minimum difference of 1 between adjacent values, -and no leading zeros: - -for i in range(1, len(gamma_lut)): - if gamma_lut[i] <= gamma_lut[i - 1]: - gamma_lut[i] = gamma_lut[i - 1] + 1 -*/ -constexpr uint16_t GAMMA_10BIT[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, 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, 75, 76, 77, 78, 79, - 80, 82, 84, 87, 89, 91, 94, 96, 98, 101, 103, 106, 109, 111, 114, 117, - 119, 122, 125, 128, 130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 164, - 167, 171, 174, 177, 181, 184, 188, 191, 195, 198, 202, 206, 209, 213, 217, 221, - 225, 228, 232, 236, 240, 244, 248, 252, 257, 261, 265, 269, 274, 278, 282, 287, - 291, 295, 300, 304, 309, 314, 318, 323, 328, 333, 337, 342, 347, 352, 357, 362, - 367, 372, 377, 382, 387, 393, 398, 403, 408, 414, 419, 425, 430, 436, 441, 447, - 452, 458, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 529, 535, 542, - 548, 554, 561, 567, 573, 580, 586, 593, 599, 606, 613, 619, 626, 633, 640, 647, - 653, 660, 667, 674, 681, 689, 696, 703, 710, 717, 725, 732, 739, 747, 754, 762, - 769, 777, 784, 792, 800, 807, 815, 823, 831, 839, 847, 855, 863, 871, 879, 887, - 895, 903, 912, 920, 928, 937, 945, 954, 962, 971, 979, 988, 997, 1005, 1014, 1023 -}; - - -struct Pixel { - uint32_t color; - constexpr Pixel() : color(0) {}; - constexpr Pixel(uint32_t color) : color(color) {}; - constexpr Pixel(uint8_t r, uint8_t g, uint8_t b) : color((GAMMA_10BIT[b] << 20) | (GAMMA_10BIT[g] << 10) | GAMMA_10BIT[r]) {}; -}; - -enum PanelType { - PANEL_GENERIC = 0, - PANEL_FM6126A, -}; - -Pixel hsv_to_rgb(float h, float s, float v); - -class Hub75 { - public: - enum class COLOR_ORDER { - RGB, - RBG, - GRB, - GBR, - BRG, - BGR +namespace pimoroni +{ + const uint DATA_BASE_PIN = 0; + const uint DATA_N_PINS = 6; + const uint ROWSEL_BASE_PIN = 6; + const uint ROWSEL_N_PINS = 5; + const uint BIT_DEPTH = 10; + + /* + 10-bit gamma table, allowing us to gamma correct our 8-bit colour values up + to 10-bit without losing dynamic range. + + Calculated with the following Python code: + + gamma_lut = [int(round(1024 * (x / (256 - 1)) ** 2.2)) for x in range(256)] + + Post-processed to enforce a minimum difference of 1 between adjacent values, + and no leading zeros: + + for i in range(1, len(gamma_lut)): + if gamma_lut[i] <= gamma_lut[i - 1]: + gamma_lut[i] = gamma_lut[i - 1] + 1 + */ + constexpr uint16_t GAMMA_10BIT[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, 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, 75, 76, 77, 78, 79, + 80, 82, 84, 87, 89, 91, 94, 96, 98, 101, 103, 106, 109, 111, 114, 117, + 119, 122, 125, 128, 130, 133, 136, 139, 142, 145, 148, 151, 155, 158, 161, 164, + 167, 171, 174, 177, 181, 184, 188, 191, 195, 198, 202, 206, 209, 213, 217, 221, + 225, 228, 232, 236, 240, 244, 248, 252, 257, 261, 265, 269, 274, 278, 282, 287, + 291, 295, 300, 304, 309, 314, 318, 323, 328, 333, 337, 342, 347, 352, 357, 362, + 367, 372, 377, 382, 387, 393, 398, 403, 408, 414, 419, 425, 430, 436, 441, 447, + 452, 458, 464, 470, 475, 481, 487, 493, 499, 505, 511, 517, 523, 529, 535, 542, + 548, 554, 561, 567, 573, 580, 586, 593, 599, 606, 613, 619, 626, 633, 640, 647, + 653, 660, 667, 674, 681, 689, 696, 703, 710, 717, 725, 732, 739, 747, 754, 762, + 769, 777, 784, 792, 800, 807, 815, 823, 831, 839, 847, 855, 863, 871, 879, 887, + 895, 903, 912, 920, 928, 937, 945, 954, 962, 971, 979, 988, 997, 1005, 1014, 1023}; + + struct Pixel + { + uint32_t color; + constexpr Pixel() : color(0) {}; + constexpr Pixel(uint32_t color) : color(color) {}; + constexpr Pixel(uint8_t r, uint8_t g, uint8_t b) : color((GAMMA_10BIT[b] << 20) | (GAMMA_10BIT[g] << 10) | GAMMA_10BIT[r]) {}; + }; + + enum PanelType + { + PANEL_GENERIC = 0, + PANEL_FM6126A, }; - uint width; - uint height; - uint r_shift = 0; - uint g_shift = 10; - uint b_shift = 20; - Pixel *back_buffer; - bool managed_buffer = false; - PanelType panel_type; - bool inverted_stb = false; - COLOR_ORDER color_order; - Pixel background = 0; - - // DMA & PIO - int dma_channel = -1; - uint bit = 0; - uint row = 0; - - PIO pio = pio0; - uint sm_data = 0; - uint sm_row = 1; - - uint data_prog_offs = 0; - uint row_prog_offs = 0; - - uint brightness = 6; - - - // 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; - - Hub75(uint width, uint height) : Hub75(width, height, nullptr) {}; - Hub75(uint width, uint height, Pixel *buffer) : Hub75(width, height, buffer, PANEL_GENERIC) {}; - Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type) : Hub75(width, height, buffer, panel_type, false) {}; - Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool inverted_stb, COLOR_ORDER color_order=COLOR_ORDER::RGB); - ~Hub75(); - - void FM6126A_write_register(uint16_t value, uint8_t position); - void FM6126A_setup(); - void set_color(uint x, uint y, Pixel c); - - void set_pixel(uint x, uint y, uint8_t r, uint8_t g, uint8_t b); - void copy_to_back_buffer(void *data, size_t len, int start_x, int start_y, int g_width, int g_height); - void display_update(); - void clear(); - void start(irq_handler_t handler); - void stop(irq_handler_t handler); - void dma_complete(); - void update(PicoGraphics *graphics); + + Pixel hsv_to_rgb(float h, float s, float v); + + class Hub75 + { + public: + enum class COLOR_ORDER + { + RGB, + RBG, + GRB, + GBR, + BRG, + BGR + }; + uint width; + uint height; + + uint r_shift = 0; + uint g_shift = 10; + uint b_shift = 20; + Pixel *buffer; + bool managed_buffer = false; + PanelType panel_type; + bool inverted_stb = false; + COLOR_ORDER color_order; + Pixel background = 0; + + // DMA & PIO + int dma_channel = -1; + uint bit = 0; + uint row = 0; + + PIO pio = pio0; + uint sm_data = 0; + uint sm_row = 1; + + uint data_prog_offs = 0; + uint row_prog_offs = 0; + + uint brightness = 6; + + // 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; + + Hub75(uint width, uint height) : Hub75(width, height, nullptr) {}; + Hub75(uint width, uint height, Pixel *buffer) : Hub75(width, height, buffer, PANEL_GENERIC) {}; + Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type) : Hub75(width, height, buffer, panel_type, false) {}; + Hub75(uint width, uint height, Pixel *buffer, PanelType panel_type, bool inverted_stb, COLOR_ORDER color_order = COLOR_ORDER::RGB); + ~Hub75(); + + void FM6126A_write_register(uint16_t value, uint8_t position); + void FM6126A_setup(); + void set_color(uint x, uint y, Pixel c); + + void set_pixel(uint x, uint y, uint8_t r, uint8_t g, uint8_t b); + void copy_to_back_buffer(void *data, size_t len, int start_x, int start_y, int g_width, int g_height); + + void clear(); + void start(); + [[deprecated("Use hub75.start() instead. Remove interrupt handler.")]] + void start(irq_handler_t handler); + void stop(); + [[deprecated("Use hub75.stop() instead. Remove interrupt handler.")]] + void stop(irq_handler_t handler); + [[deprecated("Use hub75.start() without parameters. Remove interrupt handler which calls dma_complete() .")]] + void dma_complete() {}; + void update(PicoGraphics *graphics); + + // Frame buffer for the HUB75 matrix - memory area where pixel data is stored + volatile uint32_t *frame_buffer; ///< Interwoven image data for examples; + + // Variables for row addressing and bit plane selection + volatile uint32_t row_address = 0; + volatile uint32_t bit_plane = 0; + volatile uint32_t row_in_bit_plane = 0; + + // DMA channel numbers + uint pixel_chan = -1; + uint dummy_pixel_chan = -1; + uint oen_chan = -1; + + // some helper vars + uint32_t row_threshold; + uint row_width; + uint row_offset; + + // DMA channel that becomes active when output enable (OEn) has finished. + // This channel's interrupt handler restarts the pixel data DMA channel. + int oen_finished_chan = -1; + + // Dummy pixel data emitted at the end of each row to ensure the last genuine pixels of a row are displayed - keep volatile! + volatile uint32_t dummy_pixel_data[8] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + // Control data for the output enable signal - keep volatile! + volatile uint32_t oen_finished_data = 0; + + // PIO configuration structure for state machine numbers and corresponding program offsets + typedef struct + { + uint sm_data; + PIO data_pio; + uint data_prog_offs; + uint sm_row; + PIO row_pio; + uint row_prog_offs; + } PioConfig; + + PioConfig pio_config; + + void configure_pio(); + int claim_dma_channel(const char *channel_name); + void configure_dma_channels(); + void setup_dma_transfers(); + void setup_dma_irq(); + void dma_input_channel_setup(uint channel, + uint transfer_count, + enum dma_channel_transfer_size dma_size, + bool read_incr, + uint chain_to, + PIO pio, + uint sm); + void create_hub75_driver(uint w, uint h); + void start_hub75_driver(); + + /** + * @brief Interrupt handler for the Output Enable (OEn) finished event. + * + * This interrupt is triggered when the output enable DMA transaction is completed. + * It updates row addressing and bit-plane selection for the next frame, + * modifies the PIO state machine instruction, and restarts DMA transfers + * for pixel data to ensure continuous frame updates. + */ + void oen_finished_handler() + { + // Clear the interrupt request for the finished DMA channel + dma_hw->ints0 = 1u << oen_finished_chan; + + // Advance row addressing; reset and increment bit-plane if needed + if (++row_address >= row_threshold) + { + row_address = 0; + + if (++bit_plane >= BIT_DEPTH) + { + bit_plane = 0; + } + // Patch the PIO program to make it shift to the next bit plane + hub75_data_rgb888_set_shift(pio_config.data_pio, pio_config.sm_data, pio_config.data_prog_offs, bit_plane); + } + + // Compute address and length of OEn pulse for next row + row_in_bit_plane = row_address | ((brightness << bit_plane) << 5); + dma_channel_set_read_addr(oen_chan, &row_in_bit_plane, false); + + // Restart DMA channels for the next row's data transfer + dma_channel_set_write_addr(oen_finished_chan, &oen_finished_data, true); + dma_channel_set_read_addr(pixel_chan, &frame_buffer[row_address * row_width], true); + } + + static inline Hub75 *self; + + static void oen_finished_handler_wrapper() + { + Hub75::self->oen_finished_handler(); + } }; } \ No newline at end of file diff --git a/drivers/hub75/hub75.pio b/drivers/hub75/hub75.pio index 1b7ee29e6..011bf91e2 100644 --- a/drivers/hub75/hub75.pio +++ b/drivers/hub75/hub75.pio @@ -20,13 +20,11 @@ .side_set 2 .wrap_target - out pins, 5 [1] side 0x2 ; Deassert OEn, output row select - mov x, y [3] side 0x3 ; Pulse LATCH -wait_loop: - jmp x-- wait_loop side 0x2 ; Wait for row to latch - out x, 27 side 0x2 ; Get OEn pulse width + out pins, 5 [7] side 0x2 ; Deassert OEn, output row select + out x, 27 [7] side 0x3 ; Pulse LATCH, get OEn pulse width pulse_loop: jmp x-- pulse_loop side 0x0 ; Assert OEn for x+1 cycles + in x, 32 side 0x0 ; Output data sent to DMA channel after OEn has finished .wrap .program hub75_row_inverted @@ -45,17 +43,15 @@ pulse_loop: .side_set 2 .wrap_target - out pins, 5 [1] side 0x3 ; Deassert OEn, output row select - mov x, y [3] side 0x2 ; Pulse LATCH -wait_loop: - jmp x-- wait_loop side 0x3 ; Wait for row to latch - out x, 27 side 0x3 ; Get OEn pulse width + out pins, 5 [7] side 0x3 ; Deassert OEn, output row select + mov x, y [7] side 0x2 ; Pulse LATCH pulse_loop: jmp x-- pulse_loop side 0x1 ; Assert OEn for x+1 cycles + in x, 32 side 0x0 ; Output data sent to DMA channel after OEn has finished .wrap % c-sdk { -static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint row_base_pin, uint n_row_pins, uint latch_base_pin, uint latch_cycles) { +static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint row_base_pin, uint n_row_pins, uint latch_base_pin) { pio_sm_set_consecutive_pindirs(pio, sm, row_base_pin, n_row_pins, true); pio_sm_set_consecutive_pindirs(pio, sm, latch_base_pin, 2, true); for (uint i = row_base_pin; i < row_base_pin + n_row_pins; ++i) @@ -66,22 +62,15 @@ static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint ro pio_sm_config c = hub75_row_program_get_default_config(offset); sm_config_set_out_pins(&c, row_base_pin, n_row_pins); sm_config_set_sideset_pins(&c, latch_base_pin); + sm_config_set_in_shift(&c, true, true, 32); sm_config_set_out_shift(&c, true, true, 32); pio_sm_init(pio, sm, offset, &c); - pio_sm_exec(pio, sm, pio_encode_out(pio_y, 32)); - pio_sm_put(pio, sm, latch_cycles); pio_sm_set_enabled(pio, sm, true); } - -static inline void hub75_wait_tx_stall(PIO pio, uint sm) { - uint32_t txstall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm); - pio->fdebug = txstall_mask; - while (!(pio->fdebug & txstall_mask)) - tight_loop_contents(); -} %} .program hub75_data_rgb888 +.define BIT_PLANES 10 .side_set 1 ; Each FIFO record consists of a RGB888 pixel. (This is ok for e.g. an RGB565 @@ -102,30 +91,30 @@ static inline void hub75_wait_tx_stall(PIO pio, uint sm) { public entry_point: .wrap_target -public shift0: ; R0 G0 B0 (Top half of 64x64 displays) - pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing) - in osr, 1 side 0 ; Red0 N - out null, 10 side 0 ; Red0 discard +public shift0: ; R0 G0 B0 (Top half of 64x64 displays) + pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing) + in osr, 1 side 0 ; Red0 N + out null, BIT_PLANES side 0 ; Red0 discard - in osr, 1 side 0 ; Green0 N - out null, 10 side 0 ; Green0 discard + in osr, 1 side 0 ; Green0 N + out null, BIT_PLANES side 0 ; Green0 discard - in osr, 1 side 0 ; Blue0 N - out null, 32 side 0 ; Remainder discard + in osr, 1 side 0 ; Blue0 N + out null, 32 side 0 ; Remainder discard -public shift1: ; R1 G1 B1 (Bottom half of 64x64 displays) - pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing) - in osr, 1 side 1 ; Red1 N - out null, 10 side 1 ; Red1 discard +public shift1: ; R1 G1 B1 (Bottom half of 64x64 displays) + pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing) + in osr, 1 side 1 ; Red1 N + out null, BIT_PLANES side 1 ; Red1 discard - in osr, 1 side 1 ; Green1 N - out null, 10 side 1 ; Green1 discard + in osr, 1 side 1 ; Green1 N + out null, BIT_PLANES side 1 ; Green1 discard - in osr, 1 side 1 ; Blue1 N - out null, 32 side 1 ; Remainder discard + in osr, 1 side 1 ; Blue1 N + out null, 32 side 1 ; Remainder discard - in null, 26 side 1 ; Note we are just doing this little manoeuvre here to get GPIOs in the order - mov pins, ::isr side 1 ; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed + in null, 26 side 1 ; Note we are just doing this little manoeuvre here to get GPIOs in the order + mov pins, ::isr side 1 ; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed .wrap ; Note that because the clock edge for pixel n is in the middle of pixel n +