Initial Release

This commit is contained in:
graham sanderson
2021-01-20 10:49:34 -06:00
commit 46078742c7
245 changed files with 21157 additions and 0 deletions

16
pio/hub75/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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;
}
%}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because it is too large Load Diff