Unicorn Pack driver and demo
This commit is contained in:
parent
cf86122d56
commit
5da504c625
|
@ -0,0 +1,24 @@
|
|||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
# Pull in PICO SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(pico_examples C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
# Initialize the SDK
|
||||
pico_sdk_init()
|
||||
|
||||
add_executable(
|
||||
unicorn
|
||||
demo.cpp
|
||||
)
|
||||
|
||||
pico_generate_pio_header(unicorn ${CMAKE_CURRENT_LIST_DIR}/unicorn.pio)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(unicorn pico_stdlib hardware_pio hardware_dma)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(unicorn)
|
|
@ -0,0 +1,308 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "unicorn.pio.h"
|
||||
|
||||
#define BLINK_PIN 25
|
||||
|
||||
#define LED_DATA 8
|
||||
#define LED_CLOCK 9
|
||||
#define LED_LATCH 10
|
||||
#define LED_BLANK 11
|
||||
|
||||
#define ROW_0 22
|
||||
#define ROW_1 21
|
||||
#define ROW_2 20
|
||||
#define ROW_3 19
|
||||
#define ROW_4 18
|
||||
#define ROW_5 17
|
||||
#define ROW_6 16
|
||||
|
||||
#define ROW_COUNT 7
|
||||
#define ROW_BYTES 12
|
||||
#define BCD_FRAMES 15
|
||||
#define BITSTREAM_LENGTH (ROW_COUNT * ROW_BYTES * BCD_FRAMES)
|
||||
|
||||
uint32_t dma_channel;
|
||||
|
||||
static inline void unicorn_jetpack_program_init(PIO pio, uint sm, uint offset) {
|
||||
pio_gpio_select(pio, LED_DATA);
|
||||
pio_gpio_select(pio, LED_CLOCK);
|
||||
pio_gpio_select(pio, LED_LATCH);
|
||||
pio_gpio_select(pio, LED_BLANK);
|
||||
pio_gpio_select(pio, ROW_0);
|
||||
pio_gpio_select(pio, ROW_1);
|
||||
pio_gpio_select(pio, ROW_2);
|
||||
pio_gpio_select(pio, ROW_3);
|
||||
pio_gpio_select(pio, ROW_4);
|
||||
pio_gpio_select(pio, ROW_5);
|
||||
pio_gpio_select(pio, ROW_6);
|
||||
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, LED_DATA, 4, true);
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, ROW_6, 7, true);
|
||||
|
||||
pio_sm_config c = unicorn_program_get_default_config(offset);
|
||||
|
||||
// osr shifts right, autopull on, autopull threshold 8
|
||||
sm_config_set_out_shift(&c, true, true, 32);
|
||||
|
||||
// configure out, set, and sideset pins
|
||||
sm_config_set_out_pins(&c, ROW_6, 7);
|
||||
sm_config_set_sideset_pins(&c, LED_CLOCK);
|
||||
sm_config_set_set_pins(&c, LED_DATA, 4);
|
||||
|
||||
// join fifos as only tx needed (gives 8 deep fifo instead of 4)
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
// set clock divider
|
||||
//sm_config_set_clkdiv(&c, 4);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &c);
|
||||
pio_sm_enable(pio, sm, true);
|
||||
}
|
||||
|
||||
// pixel data is stored as a stream of bits delivered in the
|
||||
// order the PIO needs to manage the shift registers, row
|
||||
// selects, delays, and latching/blanking
|
||||
//
|
||||
// the data consists of 7 rows each of which has 14 frames of
|
||||
// bcd timing data
|
||||
//
|
||||
// each row looks like this:
|
||||
//
|
||||
// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data
|
||||
// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data)
|
||||
// 0b00001111, 0b11111111, # bcd tick count (0-65536)
|
||||
// .. next BCD frame for this row (repeat for 8 frames)
|
||||
//
|
||||
// .. next row (repeat for 7 rows)
|
||||
//
|
||||
// pixels are encoded as 4 bits: r, g, b, dummy to conveniently
|
||||
// pack them into nibbles
|
||||
|
||||
uint8_t bitstream[BITSTREAM_LENGTH] = {0};
|
||||
uint16_t r_gamma_lut[256] = {0};
|
||||
uint16_t g_gamma_lut[256] = {0};
|
||||
uint16_t b_gamma_lut[256] = {0};
|
||||
|
||||
void set_pixel(uint8_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b) {
|
||||
if(x < 0 || x > 15 || y < 0 || y > 6) return;
|
||||
|
||||
// make those coordinates sane
|
||||
x = 15 - x;
|
||||
|
||||
// work out the byte offset of this pixel
|
||||
uint8_t byte_offset = x / 2;
|
||||
|
||||
// check if it's the high or low nibble and create mask and shift value
|
||||
uint8_t shift = x % 2 == 0 ? 0 : 4;
|
||||
uint8_t nibble_mask = 0b00001111 << shift;
|
||||
|
||||
uint16_t gr = r_gamma_lut[r];
|
||||
uint16_t gg = g_gamma_lut[g];
|
||||
uint16_t gb = b_gamma_lut[b];
|
||||
|
||||
// set the appropriate bits in the separate bcd frames
|
||||
for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (y * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint8_t rgbd = ((gr & 0b1) << 1) | ((gg & 0b1) << 3) | ((gb & 0b1) << 2);
|
||||
|
||||
// shift to correct nibble
|
||||
rgbd <<= shift;
|
||||
|
||||
// clear existing data
|
||||
bitstream[offset + byte_offset] &= ~nibble_mask;
|
||||
|
||||
// set new data
|
||||
bitstream[offset + byte_offset] |= rgbd;
|
||||
|
||||
gr >>= 1;
|
||||
gg >>= 1;
|
||||
gb >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
|
||||
float i = floor(h * 6.0f);
|
||||
float f = h * 6.0f - i;
|
||||
v *= 255.0f;
|
||||
uint8_t p = v * (1.0f - s);
|
||||
uint8_t q = v * (1.0f - f * s);
|
||||
uint8_t t = v * (1.0f - (1.0f - f) * s);
|
||||
|
||||
switch (int(i) % 6) {
|
||||
case 0: r = v; g = t; b = p; break;
|
||||
case 1: r = q; g = v; b = p; break;
|
||||
case 2: r = p; g = v; b = t; break;
|
||||
case 3: r = p; g = q; b = v; break;
|
||||
case 4: r = t; g = p; b = v; break;
|
||||
case 5: r = v; g = p; b = q; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
printf("Unicorn PIO + DMA example\n");
|
||||
|
||||
gpio_init(BLINK_PIN); gpio_set_dir(BLINK_PIN, GPIO_OUT);
|
||||
|
||||
// setup pins
|
||||
gpio_init(LED_DATA); gpio_set_dir(LED_DATA, GPIO_OUT);
|
||||
gpio_init(LED_CLOCK); gpio_set_dir(LED_CLOCK, GPIO_OUT);
|
||||
gpio_init(LED_LATCH); gpio_set_dir(LED_LATCH, GPIO_OUT);
|
||||
gpio_init(LED_BLANK); gpio_set_dir(LED_BLANK, GPIO_OUT);
|
||||
|
||||
gpio_init(ROW_0); gpio_set_dir(ROW_0, GPIO_OUT);
|
||||
gpio_init(ROW_1); gpio_set_dir(ROW_1, GPIO_OUT);
|
||||
gpio_init(ROW_2); gpio_set_dir(ROW_2, GPIO_OUT);
|
||||
gpio_init(ROW_3); gpio_set_dir(ROW_3, GPIO_OUT);
|
||||
gpio_init(ROW_4); gpio_set_dir(ROW_4, GPIO_OUT);
|
||||
gpio_init(ROW_5); gpio_set_dir(ROW_5, GPIO_OUT);
|
||||
gpio_init(ROW_6); gpio_set_dir(ROW_6, GPIO_OUT);
|
||||
|
||||
|
||||
// each row looks like this:
|
||||
//
|
||||
// 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # pixel data
|
||||
// 0b01111111, # row 0 select (7-bit row address, 1-bit dummy data)
|
||||
// 0b00001111, 0b00001111, # bcd tick count (0-65536)
|
||||
// .. next BCD frame for this row (repeat for 8 frames)
|
||||
//
|
||||
// .. next row (repeat for 7 rows)
|
||||
|
||||
for(uint16_t v = 0; v < 256; v++) {
|
||||
// gamma correct the provided 0-255 brightness value onto a
|
||||
// 0-65535 range for the pwm counter
|
||||
float r_gamma = 2.8f;
|
||||
r_gamma_lut[v] = (uint16_t)(pow((float)(v) / 255.0f, r_gamma) * 16383.0f + 0.5f);
|
||||
|
||||
float g_gamma = 3.5f;
|
||||
g_gamma_lut[v] = (uint16_t)(pow((float)(v) / 255.0f, g_gamma) * 16383.0f + 0.5f);
|
||||
|
||||
float b_gamma = 2.6f;
|
||||
b_gamma_lut[v] = (uint16_t)(pow((float)(v) / 255.0f, b_gamma) * 16383.0f + 0.5f);
|
||||
}
|
||||
|
||||
// initialise the BCD timing values and row selects
|
||||
for(uint8_t row = 0; row < 7; row++) {
|
||||
for(uint8_t frame = 0; frame < BCD_FRAMES; frame++) {
|
||||
// determine offset in the buffer for this row/frame
|
||||
uint16_t offset = (row * ROW_BYTES * BCD_FRAMES) + (ROW_BYTES * frame);
|
||||
|
||||
uint16_t row_select_offset = offset + 8;
|
||||
|
||||
// the last BCD frame is used to allow the fets to discharge to avoid ghosting
|
||||
if(frame == BCD_FRAMES - 1) {
|
||||
bitstream[row_select_offset] = 0b01111111;
|
||||
|
||||
uint16_t bcd_offset = offset + 9;
|
||||
uint16_t bcd_ticks = 65535;
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
}else{
|
||||
uint8_t row_select_mask = ~(1 << (7 - row));
|
||||
bitstream[row_select_offset] = row_select_mask;
|
||||
|
||||
uint16_t bcd_offset = offset + 9;
|
||||
uint16_t bcd_ticks = pow(2, frame);
|
||||
bitstream[bcd_offset + 1] = (bcd_ticks & 0xff00) >> 8;
|
||||
bitstream[bcd_offset] = (bcd_ticks & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//set_pixel(4, 1, 20, 20, 20);
|
||||
//set_pixel(6, 2, 20, 20, 20);
|
||||
//set_pixel(8, 3, 20, 20, 20);
|
||||
//set_pixel(10, 4, 20, 20, 20);
|
||||
//set_pixel(12, 5, 20, 20, 20);
|
||||
//set_pixel(14, 6, 20, 20, 20);
|
||||
|
||||
// setup the pio
|
||||
PIO bitstream_pio = pio0;
|
||||
uint sm = 0;
|
||||
uint offset = pio_add_program(bitstream_pio, &unicorn_program);
|
||||
unicorn_jetpack_program_init(bitstream_pio, sm, offset);
|
||||
|
||||
|
||||
// initialise dma channel for transmitting pixel data to screen
|
||||
// via the pixel doubling pio - initially we configure it with no
|
||||
// source or transfer length since these need to be assigned for
|
||||
// each scaline
|
||||
|
||||
// dma_channel = dma_claim_unused_channel(true);
|
||||
// dma_channel_config config = dma_channel_get_default_config(dma_channel);
|
||||
// channel_config_set_bswap(&config, true); // byte swap to reverse little endian
|
||||
// channel_config_set_dreq(&config, pio_get_dreq(bitstream_pio, sm, true));
|
||||
// dma_channel_configure(dma_channel, &config, &bitstream_pio->txf[sm], bitstream, BITSTREAM_LENGTH, false);
|
||||
// dma_channel_set_irq0_enabled(dma_channel, true);
|
||||
// irq_enable(pio_get_dreq(bitstream_pio, sm, true), true);
|
||||
|
||||
//irq_set_exclusive_handler(DMA_IRQ_0, dma_complete);
|
||||
//irq_enable(DMA_IRQ_0, true);
|
||||
|
||||
|
||||
// printf("configure bitstream DMA channel\n");
|
||||
// grab some unused dma channels
|
||||
// const uint32_t bitstream_dma = dma_claim_unused_channel(true);
|
||||
// dma_channel_config c = dma_channel_get_default_config(bitstream_dma);
|
||||
// channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
|
||||
// channel_config_set_dreq(&c, pio_get_dreq(bitstream_pio, sm, true));
|
||||
// dma_channel_configure(bitstream_dma, &c,
|
||||
// &bitstream_pio->txf[sm], // write address
|
||||
// bitstream, // buffer
|
||||
// BITSTREAM_LENGTH, // size of buffer
|
||||
// false); // don't auto start
|
||||
// dma_channel_set_irq0_enabled(bitstream_dma, true);
|
||||
// irq_enable(pio_get_dreq(bitstream_pio, sm, true), true);
|
||||
|
||||
uint32_t i = 0;
|
||||
while(true) {
|
||||
i = i + 1;
|
||||
uint8_t j = 0;
|
||||
for(uint8_t y = 0; y < 7; y++) {
|
||||
for(uint8_t x = 0; x < 16; x++) {
|
||||
uint8_t r, g, b;
|
||||
float h = float(x) / 63.0f + float(i) / 500.0f;
|
||||
h = h - float(int(h));
|
||||
float s = 1.0f;//(sin(float(i) / 200.0f) * 0.5f) + 0.5f;
|
||||
float v = (float(y) / 8.0f) + 0.05f;
|
||||
from_hsv(h, s, v, r, g, b);
|
||||
|
||||
set_pixel(x, y, r, g, b);
|
||||
j = j + 1;
|
||||
}
|
||||
}
|
||||
/*
|
||||
set_pixel(0, 0, 100, 0, 0);
|
||||
set_pixel(1, 1, 0, 100, 0);*/
|
||||
//set_pixel(2, 2, 0, 0, 100);
|
||||
/*
|
||||
set_pixel(3, 3, 100, 0, 0);
|
||||
set_pixel(4, 4, 0, 100, 0);
|
||||
set_pixel(5, 5, 0, 0, 100);
|
||||
|
||||
set_pixel(6, 6, 100, 0, 0);
|
||||
*/
|
||||
|
||||
// dma_channel_start(dma_channel);
|
||||
// dma_channel_wait_for_finish_blocking(dma_channel);
|
||||
|
||||
// copy data to pio tx fifo 4 bytes at a time
|
||||
uint32_t *p = (uint32_t *)bitstream;
|
||||
for(uint16_t i = 0; i < BITSTREAM_LENGTH; i+=4) {
|
||||
pio_sm_put_blocking(bitstream_pio, sm, *p++);
|
||||
}
|
||||
}
|
||||
|
||||
printf("done\n");
|
||||
//dma_channel_unclaim(bitstream_dma);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# todo document
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
)
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading PICO SDK")
|
||||
FetchContent_Populate(pico_sdk)
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
|
@ -0,0 +1,83 @@
|
|||
.program unicorn
|
||||
.side_set 1 opt
|
||||
|
||||
; set pins:
|
||||
; 0: data (base)
|
||||
; 1: clock
|
||||
; 2: latch
|
||||
; 3: blank
|
||||
|
||||
; sideset pin: clock
|
||||
|
||||
; out pins:
|
||||
; 0: row 6 select
|
||||
; 1: row 5 select
|
||||
; 2: row 4 select
|
||||
; 3: row 3 select
|
||||
; 4: row 2 select
|
||||
; 5: row 1 select
|
||||
; 6: row 0 select
|
||||
|
||||
.wrap_target
|
||||
|
||||
; clock out 16 pixels worth of data
|
||||
set y, 15 ; 15 because `jmp` test is pre decrement
|
||||
|
||||
pixels:
|
||||
|
||||
; dummy bit used to align pixel data to nibbles
|
||||
out null, 1 ; discard
|
||||
|
||||
; red bit
|
||||
out x, 1 side 0 ; pull in first bit from OSR into register X, clear clock
|
||||
set pins, 8 ; clear data bit (maintain blank)
|
||||
jmp !x endr ; if bit was zero jump to endr
|
||||
set pins, 9 ; set data bit (maintain blank)
|
||||
endr: ;
|
||||
nop side 1 ; clock in bit
|
||||
|
||||
; green bit
|
||||
out x, 1 side 0 ; pull in first bit from OSR into register X, clear clock
|
||||
set pins, 8 ; clear data bit (maintain blank)
|
||||
jmp !x endg ; if bit was zero jump to endg
|
||||
set pins, 9 ; set data bit (maintain blank)
|
||||
endg: ;
|
||||
nop side 1 ; clock in bit
|
||||
|
||||
; blue bit
|
||||
out x, 1 side 0 ; pull in first bit from OSR into register X, clear clock
|
||||
set pins, 8 ; clear data bit (maintain blank)
|
||||
jmp !x endb ; if bit was zero jump to endb
|
||||
set pins, 9 ; set data bit (maintain blank)
|
||||
endb: ;
|
||||
nop side 1 ; clock in bit
|
||||
|
||||
jmp y-- pixels ; jump back to start of pixel loop
|
||||
|
||||
; select active row
|
||||
out null, 1 ; discard dummy bit
|
||||
out pins, 7 ; output row selection mask
|
||||
|
||||
; pull bcd tick count into x register
|
||||
out x, 16
|
||||
|
||||
; set latch pin to output column data on shift registers
|
||||
set pins, 12 ; set latch pin (while keeping blank high)
|
||||
|
||||
; set blank pin to enable column drivers
|
||||
set pins, 4
|
||||
|
||||
bcd_count:
|
||||
jmp x-- bcd_count ; loop until bcd delay complete
|
||||
|
||||
; disable led output (blank) and clear latch pin
|
||||
set pins, 8
|
||||
|
||||
; disable all row outputs
|
||||
set x, 0 ; load x register with 0 (we can't set more than 5 bits at a time)
|
||||
mov pins, !x ; write inverted x (0xff) to row pins latching them all high
|
||||
|
||||
; dummy byte to 32 bit align row data
|
||||
out null, 8
|
||||
|
||||
.wrap
|
Loading…
Reference in New Issue