Initial Release
This commit is contained in:
16
pio/hub75/CMakeLists.txt
Normal file
16
pio/hub75/CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
add_executable(pio_hub75)
|
||||
|
||||
pico_generate_pio_header(pio_hub75 ${CMAKE_CURRENT_LIST_DIR}/hub75.pio)
|
||||
|
||||
target_sources(pio_hub75 PRIVATE hub75.c)
|
||||
|
||||
target_compile_definitions(pio_hub75 PRIVATE
|
||||
PICO_DEFAULT_UART_TX_PIN=28
|
||||
PICO_DEFAULT_UART_RX_PIN=29
|
||||
)
|
||||
|
||||
target_link_libraries(pio_hub75 PRIVATE pico_stdlib hardware_pio)
|
||||
pico_add_extra_outputs(pio_hub75)
|
||||
|
||||
# add url via pico_set_program_url
|
||||
example_auto_set_url(pio_hub75)
|
||||
45
pio/hub75/Readme.md
Normal file
45
pio/hub75/Readme.md
Normal file
@@ -0,0 +1,45 @@
|
||||
HUB75E Pinout:
|
||||
|
||||
```
|
||||
/-----\
|
||||
R0 | o o | G0
|
||||
B0 | o o | GND
|
||||
R1 | o o | G1
|
||||
B1 \ o o | E
|
||||
A / o o | B
|
||||
C | o o | D
|
||||
CLK | o o | STB
|
||||
OEn | o o | GND
|
||||
\-----/
|
||||
```
|
||||
|
||||
Wiring:
|
||||
|
||||
```
|
||||
Must be contiguous, in order:
|
||||
R0 - GPIO0
|
||||
G0 - GPIO1
|
||||
B0 - GPIO2
|
||||
R1 - GPIO3
|
||||
G1 - GPIO4
|
||||
B1 - GPIO5
|
||||
|
||||
Must be contiguous, somewhat ok to change order:
|
||||
A - GPIO6
|
||||
B - GPIO7
|
||||
C - GPIO8
|
||||
D - GPIO9
|
||||
E - GPIO10
|
||||
|
||||
Can be anywhere:
|
||||
CLK - GPIO11
|
||||
|
||||
Must be contiguous, in order:
|
||||
STB - GPIO12
|
||||
OEn - GPIO13
|
||||
```
|
||||
|
||||
This is a 1/32nd scan panel. The inputs A, B, C, D, E select one of 32 rows, starting at the top and working down (assuming the first pixel to be shifted is the one on the left of the screen, even though this is the "far end" of the shift register). R0, B0, G0 contain pixel data for the upper half of the screen. R1, G1, B1 contain pixel data for the lower half of the screen, which is scanned simultaneously with the upper half.
|
||||
|
||||
Image credit for mountains_128x64.png: Paul Gilmore, found on [this wikimedia page](https://commons.wikimedia.org/wiki/File:Mountain_lake_dam.jpg)
|
||||
|
||||
78
pio/hub75/hub75.c
Normal file
78
pio/hub75/hub75.c
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/gpio.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hub75.pio.h"
|
||||
|
||||
#include "mountains_128x64_rgb565.h"
|
||||
|
||||
#define DATA_BASE_PIN 0
|
||||
#define DATA_N_PINS 6
|
||||
#define ROWSEL_BASE_PIN 6
|
||||
#define ROWSEL_N_PINS 5
|
||||
#define CLK_PIN 11
|
||||
#define STROBE_PIN 12
|
||||
#define OEN_PIN 13
|
||||
|
||||
#define WIDTH 128
|
||||
#define HEIGHT 64
|
||||
|
||||
static inline uint32_t gamma_correct_565_888(uint16_t pix) {
|
||||
uint32_t r_gamma = pix & 0xf800u;
|
||||
r_gamma *= r_gamma;
|
||||
uint32_t g_gamma = pix & 0x07e0u;
|
||||
g_gamma *= g_gamma;
|
||||
uint32_t b_gamma = pix & 0x001fu;
|
||||
b_gamma *= b_gamma;
|
||||
return (b_gamma >> 2 << 16) | (g_gamma >> 14 << 8) | (r_gamma >> 24 << 0);
|
||||
}
|
||||
|
||||
int main() {
|
||||
stdio_init_all();
|
||||
|
||||
PIO pio = pio0;
|
||||
uint sm_data = 0;
|
||||
uint sm_row = 1;
|
||||
|
||||
uint data_prog_offs = pio_add_program(pio, &hub75_data_rgb888_program);
|
||||
uint row_prog_offs = pio_add_program(pio, &hub75_row_program);
|
||||
hub75_data_rgb888_program_init(pio, sm_data, data_prog_offs, DATA_BASE_PIN, CLK_PIN);
|
||||
hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, STROBE_PIN);
|
||||
|
||||
static uint32_t gc_row[2][WIDTH];
|
||||
const uint16_t *img = (const uint16_t*)mountains_128x64;
|
||||
|
||||
while (1) {
|
||||
for (int rowsel = 0; rowsel < (1 << ROWSEL_N_PINS); ++rowsel) {
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
gc_row[0][x] = gamma_correct_565_888(img[rowsel * WIDTH + x]);
|
||||
gc_row[1][x] = gamma_correct_565_888(img[((1u << ROWSEL_N_PINS) + rowsel) * WIDTH + x]);
|
||||
}
|
||||
for (int bit = 0; bit < 8; ++bit) {
|
||||
hub75_data_rgb888_set_shift(pio, sm_data, data_prog_offs, bit);
|
||||
for (int x = 0; x < WIDTH; ++x) {
|
||||
pio_sm_put_blocking(pio, sm_data, gc_row[0][x]);
|
||||
pio_sm_put_blocking(pio, sm_data, gc_row[1][x]);
|
||||
}
|
||||
// Dummy pixel per lane
|
||||
pio_sm_put_blocking(pio, sm_data, 0);
|
||||
pio_sm_put_blocking(pio, sm_data, 0);
|
||||
// SM is finished when it stalls on empty TX FIFO
|
||||
hub75_wait_tx_stall(pio, sm_data);
|
||||
// Also check that previous OEn pulse is finished, else things can get out of sequence
|
||||
hub75_wait_tx_stall(pio, sm_row);
|
||||
|
||||
// Latch row data, pulse output enable for new row.
|
||||
pio_sm_put_blocking(pio, sm_row, rowsel | (100u * (1u << bit) << 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
122
pio/hub75/hub75.pio
Normal file
122
pio/hub75/hub75.pio
Normal file
@@ -0,0 +1,122 @@
|
||||
.program hub75_row
|
||||
|
||||
; side-set pin 0 is LATCH
|
||||
; side-set pin 1 is OEn
|
||||
; OUT pins are row select A-E
|
||||
;
|
||||
; Each FIFO record consists of:
|
||||
; - 5-bit row select (LSBs)
|
||||
; - Pulse width - 1 (27 MSBs)
|
||||
;
|
||||
; Repeatedly select a row, pulse LATCH, and generate a pulse of a certain
|
||||
; width on OEn.
|
||||
|
||||
.side_set 2
|
||||
|
||||
.wrap_target
|
||||
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
|
||||
.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) {
|
||||
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)
|
||||
pio_gpio_init(pio, i);
|
||||
pio_gpio_init(pio, latch_base_pin);
|
||||
pio_gpio_init(pio, latch_base_pin + 1);
|
||||
|
||||
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_out_shift(&c, true, true, 32);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
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
|
||||
.side_set 1
|
||||
|
||||
; Each FIFO record consists of a RGB888 pixel. (This is ok for e.g. an RGB565
|
||||
; source which has been gamma-corrected)
|
||||
;
|
||||
; Even pixels are sent on R0, G0, B0 and odd pixels on R1, G1, B1 (typically
|
||||
; these are for different parts of the screen, NOT for adjacent pixels, so the
|
||||
; frame buffer must be interleaved before passing to PIO.)
|
||||
;
|
||||
; Each pass through, we take bit n, n + 8 and n + 16 from each pixel, for n in
|
||||
; {0...7}. Therefore the pixels need to be transmitted 8 times (ouch) to build
|
||||
; up the full 8 bit value for each channel, and perform bit-planed PWM by
|
||||
; varying pulse widths on the other state machine, in ascending powers of 2.
|
||||
; This avoids a lot of bit shuffling on the processors, at the cost of DMA
|
||||
; bandwidth (which we have loads of).
|
||||
|
||||
; Might want to close your eyes before you read this
|
||||
public entry_point:
|
||||
.wrap_target
|
||||
public shift0:
|
||||
pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing)
|
||||
in osr, 1 side 0 ; shuffle shuffle shuffle
|
||||
out null, 8 side 0
|
||||
in osr, 1 side 0
|
||||
out null, 8 side 0
|
||||
in osr, 1 side 0
|
||||
out null, 32 side 0 ; Discard remainder of OSR contents
|
||||
public shift1:
|
||||
pull side 0 ; gets patched to out null, n if n is nonzero (otherwise PULL required)
|
||||
in osr, 1 side 1 ; Note this posedge clocks in the data from the previous iteration
|
||||
out null, 8 side 1
|
||||
in osr, 1 side 1
|
||||
out null, 8 side 1
|
||||
in osr, 1 side 1
|
||||
out null, 32 side 1
|
||||
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 +
|
||||
; 1, a dummy pixel at the end is required to clock the last piece of genuine
|
||||
; data. (Also 1 pixel of garbage is clocked out at the start, but this is
|
||||
; harmless)
|
||||
|
||||
% c-sdk {
|
||||
static inline void hub75_data_rgb888_program_init(PIO pio, uint sm, uint offset, uint rgb_base_pin, uint clock_pin) {
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, rgb_base_pin, 6, true);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin, 1, true);
|
||||
for (uint i = rgb_base_pin; i < rgb_base_pin + 6; ++i)
|
||||
pio_gpio_init(pio, i);
|
||||
pio_gpio_init(pio, clock_pin);
|
||||
|
||||
pio_sm_config c = hub75_data_rgb888_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&c, rgb_base_pin, 6);
|
||||
sm_config_set_sideset_pins(&c, clock_pin);
|
||||
sm_config_set_out_shift(&c, true, true, 24);
|
||||
// ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register.
|
||||
sm_config_set_in_shift(&c, false, false, 32);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_exec(pio, sm, offset + hub75_data_rgb888_offset_entry_point);
|
||||
pio_sm_set_enabled(pio, sm, true);
|
||||
}
|
||||
|
||||
// Patch a data program at `offset` to preshift pixels by `shamt`
|
||||
static inline void hub75_data_rgb888_set_shift(PIO pio, uint sm, uint offset, uint shamt) {
|
||||
uint16_t instr;
|
||||
if (shamt == 0)
|
||||
instr = pio_encode_pull(false, true); // blocking PULL
|
||||
else
|
||||
instr = pio_encode_out(pio_null, shamt);
|
||||
pio->instr_mem[offset + hub75_data_rgb888_offset_shift0] = instr;
|
||||
pio->instr_mem[offset + hub75_data_rgb888_offset_shift1] = instr;
|
||||
}
|
||||
%}
|
||||
BIN
pio/hub75/mountains_128x64.png
Normal file
BIN
pio/hub75/mountains_128x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
1027
pio/hub75/mountains_128x64_rgb565.h
Normal file
1027
pio/hub75/mountains_128x64_rgb565.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user