Added DVI hstx examples, started work on mandelbrot

This commit is contained in:
shylie 2025-04-12 09:20:40 -04:00
parent df6d93ac32
commit 9130db7d4f
15 changed files with 19930 additions and 3 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Compiler: arm-none-eabi-g++

View File

@ -5,17 +5,22 @@ list(APPEND PICO_BOARD_HEADER_DIRS lib/pico-ice-sdk/include/boards)
file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "*") file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "*")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)
include(lib/pico-ice-sdk/cmake/preinit_pico_ice_sdk.cmake) include(lib/pico-ice-sdk/cmake/preinit_pico_ice_sdk.cmake)
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/lib/pico-ice-sdk/lib/pico-sdk) set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/lib/pico-ice-sdk/lib/pico-sdk)
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake) include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(pico2-ice-projects C CXX ASM) project(pico2-ice-projects C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init() pico_sdk_init()
include(cmake/FileEmbed.cmake)
FileEmbedSetup()
add_subdirectory(lib/pico-ice-sdk) add_subdirectory(lib/pico-ice-sdk)
add_subdirectory(blinky) add_subdirectory(blinky)
add_subdirectory(dvi-example)
add_subdirectory(dvi-dynamic)

88
cmake/FileEmbed.cmake Normal file
View File

@ -0,0 +1,88 @@
function(FileEmbedSetup)
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}file_embed)
endif ()
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c)
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c "")
endif ()
add_library(file_embed ${CMAKE_BINARY_DIR}/file_embed/file_embed_empty.c)
target_include_directories(file_embed PUBLIC ${CMAKE_BINARY_DIR}/file_embed)
endfunction()
function(FileEmbedAdd file)
FileEmbedGenerate(${file} var)
target_sources(file_embed PUBLIC ${var})
add_custom_command(
OUTPUT ${var}
COMMAND ${CMAKE_COMMAND}
-DRUN_FILE_EMBED_GENERATE=1
-DFILE_EMBED_GENERATE_PATH=${file}
-P ${CMAKE_SOURCE_DIR}/cmake/FileEmbed.cmake
MAIN_DEPENDENCY ${file}
)
endfunction()
function(FileEmbedGenerate file generated_c)
get_filename_component(base_filename ${file} NAME)
set(output_filename "${base_filename}.c")
string(MAKE_C_IDENTIFIER ${base_filename} c_name)
file(READ ${file} content HEX)
message(${content})
# Separate into individual bytes.
string(REGEX MATCHALL "([A-Fa-f0-9][A-Fa-f0-9])" SEPARATED_HEX ${content})
set(output_c "")
set(counter 0)
foreach (hex IN LISTS SEPARATED_HEX)
string(APPEND output_c "0x${hex},")
MATH(EXPR counter "${counter}+1")
if (counter GREATER 16)
string(APPEND output_c "\n ")
set(counter 0)
endif ()
endforeach ()
set(output_c "
#include \"${c_name}.h\"
uint8_t ${c_name}_data[] = {
${output_c}
}\;
unsigned ${c_name}_size = sizeof(${c_name}_data)\;
")
set(output_h "
#ifndef ${c_name}_H
#define ${c_name}_H
#include \"stdint.h\"
extern uint8_t ${c_name}_data[]\;
extern unsigned ${c_name}_size\;
#endif // ${c_name}_H
")
if (NOT EXISTS ${CMAKE_BINARY_DIR}/file_embed)
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}file_embed)
endif ()
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/${c_name}.c
${output_c})
file(WRITE ${CMAKE_BINARY_DIR}/file_embed/${c_name}.h
${output_h})
set(${generated_c} ${CMAKE_BINARY_DIR}/file_embed/${c_name}.c PARENT_SCOPE)
endfunction()
if (RUN_FILE_EMBED_GENERATE)
FileEmbedGenerate(${FILE_EMBED_GENERATE_PATH} var)
endif ()

View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.13...3.27)
add_executable(dvi-dynamic
src/main.cpp
src/framebuffer.cpp
)
FileEmbedAdd(${CMAKE_CURRENT_SOURCE_DIR}/rtl_bin/mandelbrot.bin)
target_include_directories(dvi-dynamic PRIVATE include)
target_link_libraries(dvi-dynamic
pico_ice_sdk
hardware_dma
file_embed
)
pico_add_extra_outputs(dvi-dynamic)

View File

@ -0,0 +1,12 @@
#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H
#include <cstdint>
constexpr int FRAMEBUFFER_WIDTH = 640;
constexpr int FRAMEBUFFER_HEIGHT = 480;
constexpr int FRAMEBUFFER_SIZE = FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT;
alignas(4) extern uint8_t FRAMEBUFFER[];
#endif//FRAMEBUFFER_H

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<RadiantProject version="4.2" radiant="2024.2.0.3.0" title="mandelbrot" device="iCE40UP5K-SG48I" performance_grade="High-Performance_1.2V" family_int="ice40tp" device_int="itpa08" package_int="SG48" operation_int="IND" speed_int="6" default_implementation="impl_1">
<Options/>
<Implementation title="impl_1" dir="impl_1" description="impl_1" synthesis="synplify" default_strategy="Strategy1">
<Options VerilogStandard="System Verilog" top="top"/>
<Source name="source/impl_1/top.sv" type="Verilog" type_short="Verilog">
<Options VerilogStandard="System Verilog"/>
</Source>
<Source name="source/impl_1/impl_1.pdc" type="Physical Constraints File" type_short="PDC">
<Options/>
</Source>
<Source name="source/impl_1/impl_1.sdc" type="Pre-Synthesis Constraints File" type_short="SDC">
<Options/>
</Source>
</Implementation>
<Strategy name="Strategy1" file="mandelbrot1.sty"/>
</RadiantProject>

View File

@ -0,0 +1,5 @@
ldc_set_location -site 35 [get_ports {clk}]
ldc_set_location -site 39 [get_ports {led_g}]
ldc_set_location -site 40 [get_ports {led_b}]
ldc_set_location -site 41 [get_ports {led_r}]

View File

@ -0,0 +1 @@
create_clock -period 20.83333 [get_ports {clk}]

View File

@ -0,0 +1,21 @@
module top
(
input wire clk,
output wire led_r,
output wire led_g,
output wire led_b
);
localparam N = 22;
reg [N:0] counter;
always_ff @(posedge clk) begin
counter <= counter + 1;
end
assign led_r = 1'b1;
assign led_g = counter[N];
assign led_b = 1'b1;
endmodule

1
dvi-dynamic/rtl_bin/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.bin

View File

@ -0,0 +1,3 @@
#include "framebuffer.h"
alignas(4) uint8_t FRAMEBUFFER[FRAMEBUFFER_SIZE];

273
dvi-dynamic/src/main.cpp Normal file
View File

@ -0,0 +1,273 @@
// Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
// Generate DVI output using the command expander and TMDS encoder in HSTX.
// This example requires an external digital video connector connected to
// GPIOs 12 through 19 (the HSTX-capable GPIOs) with appropriate
// current-limiting resistors, e.g. 270 ohms. The pinout used in this example
// matches the Pico DVI Sock board, which can be soldered onto a Pico 2:
// https://github.com/Wren6991/Pico-DVI-Sock
#include <cstdlib>
#include <hardware/dma.h>
#include <hardware/gpio.h>
#include <hardware/irq.h>
#include <hardware/structs/bus_ctrl.h>
#include <hardware/structs/hstx_ctrl.h>
#include <hardware/structs/hstx_fifo.h>
#include <ice_cram.h>
#include <ice_fpga.h>
#include <ice_led.h>
#include <pico/multicore.h>
#include "framebuffer.h"
#include "mandelbrot_bin.h"
// ----------------------------------------------------------------------------
// DVI constants
#define TMDS_CTRL_00 0x354u
#define TMDS_CTRL_01 0x0abu
#define TMDS_CTRL_10 0x154u
#define TMDS_CTRL_11 0x2abu
#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define MODE_H_SYNC_POLARITY 0
#define MODE_H_FRONT_PORCH 16
#define MODE_H_SYNC_WIDTH 96
#define MODE_H_BACK_PORCH 48
#define MODE_H_ACTIVE_PIXELS 640
#define MODE_V_SYNC_POLARITY 0
#define MODE_V_FRONT_PORCH 10
#define MODE_V_SYNC_WIDTH 2
#define MODE_V_BACK_PORCH 33
#define MODE_V_ACTIVE_LINES 480
#define MODE_H_TOTAL_PIXELS ( \
MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \
MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \
)
#define MODE_V_TOTAL_LINES ( \
MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \
MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \
)
#define HSTX_CMD_RAW (0x0u << 12)
#define HSTX_CMD_RAW_REPEAT (0x1u << 12)
#define HSTX_CMD_TMDS (0x2u << 12)
#define HSTX_CMD_TMDS_REPEAT (0x3u << 12)
#define HSTX_CMD_NOP (0xfu << 12)
// ----------------------------------------------------------------------------
// HSTX command lists
// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly
// pingponging and tripping up the IRQs.
static uint32_t vblank_line_vsync_off[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V1_H1,
HSTX_CMD_NOP
};
static uint32_t vblank_line_vsync_on[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V0_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V0_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V0_H1,
HSTX_CMD_NOP
};
static uint32_t vactive_line[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH,
SYNC_V1_H1,
HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS
};
// ----------------------------------------------------------------------------
// DMA logic
#define DMACH_PING 0
#define DMACH_PONG 1
// First we ping. Then we pong. Then... we ping again.
static bool dma_pong = false;
// A ping and a pong are cued up initially, so the first time we enter this
// handler it is to cue up the second ping after the first ping has completed.
// This is the third scanline overall (-> =2 because zero-based).
static uint v_scanline = 2;
// During the vertical active period, we take two IRQs per scanline: one to
// post the command list, and another to post the pixels.
static bool vactive_cmdlist_posted = false;
void __scratch_x("") dma_irq_handler() {
// dma_pong indicates the channel that just finished, which is the one
// we're about to reload.
uint ch_num = dma_pong ? DMACH_PONG : DMACH_PING;
dma_channel_hw_t *ch = &dma_hw->ch[ch_num];
dma_hw->intr = 1u << ch_num;
dma_pong = !dma_pong;
if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) {
ch->read_addr = (uintptr_t)vblank_line_vsync_on;
ch->transfer_count = count_of(vblank_line_vsync_on);
} else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) {
ch->read_addr = (uintptr_t)vblank_line_vsync_off;
ch->transfer_count = count_of(vblank_line_vsync_off);
} else if (!vactive_cmdlist_posted) {
ch->read_addr = (uintptr_t)vactive_line;
ch->transfer_count = count_of(vactive_line);
vactive_cmdlist_posted = true;
} else {
ch->read_addr = (uintptr_t)&FRAMEBUFFER[(v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES)) * MODE_H_ACTIVE_PIXELS];
ch->transfer_count = MODE_H_ACTIVE_PIXELS / sizeof(uint32_t);
vactive_cmdlist_posted = false;
}
if (!vactive_cmdlist_posted) {
v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES;
}
}
// ----------------------------------------------------------------------------
// Main program
int main(void) {
ice_led_init();
ice_fpga_init(FPGA_DATA, 48);
ice_cram_open(FPGA_DATA);
ice_cram_write(mandelbrot_bin_data, mandelbrot_bin_size);
ice_cram_close();
for (int i = 0; i < FRAMEBUFFER_SIZE; i++)
{
FRAMEBUFFER[i] = 0xFF;
}
// Configure HSTX's TMDS encoder for RGB332
hstx_ctrl_hw->expand_tmds =
2 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB |
0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB |
2 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB |
29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB |
1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB |
26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB;
// Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an
// entire 32-bit word.
hstx_ctrl_hw->expand_shift =
4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB |
8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB |
1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB |
0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB;
// Serial output config: clock period of 5 cycles, pop from command
// expander every 5 cycles, shift the output shiftreg by 2 every cycle.
hstx_ctrl_hw->csr = 0;
hstx_ctrl_hw->csr =
HSTX_CTRL_CSR_EXPAND_EN_BITS |
5u << HSTX_CTRL_CSR_CLKDIV_LSB |
5u << HSTX_CTRL_CSR_N_SHIFTS_LSB |
2u << HSTX_CTRL_CSR_SHIFT_LSB |
HSTX_CTRL_CSR_EN_BITS;
// Note we are leaving the HSTX clock at the SDK default of 125 MHz; since
// we shift out two bits per HSTX clock cycle, this gives us an output of
// 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz).
// If we want the exact rate then we'll have to reconfigure PLLs.
// HSTX outputs 0 through 7 appear on GPIO 12 through 19.
// Pinout on Pico DVI sock:
//
// GP12 D0- GP13 D0+
// GP14 D1- GP15 D1+
// GP16 CK- GP17 CK+
// GP18 D2- GP19 D2+
// Assign clock pair to two neighbouring pins:
hstx_ctrl_hw->bit[5] = HSTX_CTRL_BIT0_CLK_BITS;
hstx_ctrl_hw->bit[4] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS;
for (uint lane = 0; lane < 3; ++lane) {
// For each TMDS lane, assign it to the correct GPIO pair based on the
// desired pinout:
static const int lane_to_output_bit[3] = {0, 2, 6};
int bit = lane_to_output_bit[lane];
// Output even bits during first half of each HSTX cycle, and odd bits
// during second half. The shifter advances by two bits each cycle.
uint32_t lane_data_sel_bits =
(lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB |
(lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB;
// The two halves of each pair get identical data, but one pin is inverted.
hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits;
hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS;
}
for (int i = 12; i <= 19; ++i) {
gpio_set_function(i, GPIO_FUNC_HSTX); // HSTX
}
// Both channels are set up identically, to transfer a whole scanline and
// then chain to the opposite channel. Each time a channel finishes, we
// reconfigure the one that just finished, meanwhile the opposite channel
// is already making progress.
dma_channel_config c;
c = dma_channel_get_default_config(DMACH_PING);
channel_config_set_chain_to(&c, DMACH_PONG);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PING,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
c = dma_channel_get_default_config(DMACH_PONG);
channel_config_set_chain_to(&c, DMACH_PING);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PONG,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
dma_hw->ints0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
dma_hw->inte0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
dma_channel_start(DMACH_PING);
while (true)
{
__wfi();
}
}

View File

@ -0,0 +1,12 @@
cmake_minimum_required(VERSION 3.13...3.27)
add_executable(dvi-example
src/dvi_out_hstx_encoder.c
)
target_include_directories(dvi-example PRIVATE src)
target_link_libraries(dvi-example
pico_ice_sdk
hardware_dma
)
pico_add_extra_outputs(dvi-example)

View File

@ -0,0 +1,264 @@
// Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
// Generate DVI output using the command expander and TMDS encoder in HSTX.
// This example requires an external digital video connector connected to
// GPIOs 12 through 19 (the HSTX-capable GPIOs) with appropriate
// current-limiting resistors, e.g. 270 ohms. The pinout used in this example
// matches the Pico DVI Sock board, which can be soldered onto a Pico 2:
// https://github.com/Wren6991/Pico-DVI-Sock
#include "hardware/dma.h"
#include "hardware/gpio.h"
#include "hardware/irq.h"
#include "hardware/structs/bus_ctrl.h"
#include "hardware/structs/hstx_ctrl.h"
#include "hardware/structs/hstx_fifo.h"
#include "hardware/structs/sio.h"
#include "pico/multicore.h"
#include "pico/sem.h"
#include "mountains_640x480_rgb332.h"
#define framebuf mountains_640x480
// ----------------------------------------------------------------------------
// DVI constants
#define TMDS_CTRL_00 0x354u
#define TMDS_CTRL_01 0x0abu
#define TMDS_CTRL_10 0x154u
#define TMDS_CTRL_11 0x2abu
#define SYNC_V0_H0 (TMDS_CTRL_00 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V0_H1 (TMDS_CTRL_01 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H0 (TMDS_CTRL_10 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define SYNC_V1_H1 (TMDS_CTRL_11 | (TMDS_CTRL_00 << 10) | (TMDS_CTRL_00 << 20))
#define MODE_H_SYNC_POLARITY 0
#define MODE_H_FRONT_PORCH 16
#define MODE_H_SYNC_WIDTH 96
#define MODE_H_BACK_PORCH 48
#define MODE_H_ACTIVE_PIXELS 640
#define MODE_V_SYNC_POLARITY 0
#define MODE_V_FRONT_PORCH 10
#define MODE_V_SYNC_WIDTH 2
#define MODE_V_BACK_PORCH 33
#define MODE_V_ACTIVE_LINES 480
#define MODE_H_TOTAL_PIXELS ( \
MODE_H_FRONT_PORCH + MODE_H_SYNC_WIDTH + \
MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS \
)
#define MODE_V_TOTAL_LINES ( \
MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + \
MODE_V_BACK_PORCH + MODE_V_ACTIVE_LINES \
)
#define HSTX_CMD_RAW (0x0u << 12)
#define HSTX_CMD_RAW_REPEAT (0x1u << 12)
#define HSTX_CMD_TMDS (0x2u << 12)
#define HSTX_CMD_TMDS_REPEAT (0x3u << 12)
#define HSTX_CMD_NOP (0xfu << 12)
// ----------------------------------------------------------------------------
// HSTX command lists
// Lists are padded with NOPs to be >= HSTX FIFO size, to avoid DMA rapidly
// pingponging and tripping up the IRQs.
static uint32_t vblank_line_vsync_off[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V1_H1,
HSTX_CMD_NOP
};
static uint32_t vblank_line_vsync_on[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V0_H1,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V0_H0,
HSTX_CMD_RAW_REPEAT | (MODE_H_BACK_PORCH + MODE_H_ACTIVE_PIXELS),
SYNC_V0_H1,
HSTX_CMD_NOP
};
static uint32_t vactive_line[] = {
HSTX_CMD_RAW_REPEAT | MODE_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_H_BACK_PORCH,
SYNC_V1_H1,
HSTX_CMD_TMDS | MODE_H_ACTIVE_PIXELS
};
// ----------------------------------------------------------------------------
// DMA logic
#define DMACH_PING 0
#define DMACH_PONG 1
// First we ping. Then we pong. Then... we ping again.
static bool dma_pong = false;
// A ping and a pong are cued up initially, so the first time we enter this
// handler it is to cue up the second ping after the first ping has completed.
// This is the third scanline overall (-> =2 because zero-based).
static uint v_scanline = 2;
// During the vertical active period, we take two IRQs per scanline: one to
// post the command list, and another to post the pixels.
static bool vactive_cmdlist_posted = false;
void __scratch_x("") dma_irq_handler() {
// dma_pong indicates the channel that just finished, which is the one
// we're about to reload.
uint ch_num = dma_pong ? DMACH_PONG : DMACH_PING;
dma_channel_hw_t *ch = &dma_hw->ch[ch_num];
dma_hw->intr = 1u << ch_num;
dma_pong = !dma_pong;
if (v_scanline >= MODE_V_FRONT_PORCH && v_scanline < (MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH)) {
ch->read_addr = (uintptr_t)vblank_line_vsync_on;
ch->transfer_count = count_of(vblank_line_vsync_on);
} else if (v_scanline < MODE_V_FRONT_PORCH + MODE_V_SYNC_WIDTH + MODE_V_BACK_PORCH) {
ch->read_addr = (uintptr_t)vblank_line_vsync_off;
ch->transfer_count = count_of(vblank_line_vsync_off);
} else if (!vactive_cmdlist_posted) {
ch->read_addr = (uintptr_t)vactive_line;
ch->transfer_count = count_of(vactive_line);
vactive_cmdlist_posted = true;
} else {
ch->read_addr = (uintptr_t)&framebuf[(v_scanline - (MODE_V_TOTAL_LINES - MODE_V_ACTIVE_LINES)) * MODE_H_ACTIVE_PIXELS];
ch->transfer_count = MODE_H_ACTIVE_PIXELS / sizeof(uint32_t);
vactive_cmdlist_posted = false;
}
if (!vactive_cmdlist_posted) {
v_scanline = (v_scanline + 1) % MODE_V_TOTAL_LINES;
}
}
// ----------------------------------------------------------------------------
// Main program
static __force_inline uint16_t colour_rgb565(uint8_t r, uint8_t g, uint8_t b) {
return ((uint16_t)r & 0xf8) >> 3 | ((uint16_t)g & 0xfc) << 3 | ((uint16_t)b & 0xf8) << 8;
}
static __force_inline uint8_t colour_rgb332(uint8_t r, uint8_t g, uint8_t b) {
return (r & 0xc0) >> 6 | (g & 0xe0) >> 3 | (b & 0xe0) >> 0;
}
void scroll_framebuffer(void);
int main(void) {
// Configure HSTX's TMDS encoder for RGB332
hstx_ctrl_hw->expand_tmds =
2 << HSTX_CTRL_EXPAND_TMDS_L2_NBITS_LSB |
0 << HSTX_CTRL_EXPAND_TMDS_L2_ROT_LSB |
2 << HSTX_CTRL_EXPAND_TMDS_L1_NBITS_LSB |
29 << HSTX_CTRL_EXPAND_TMDS_L1_ROT_LSB |
1 << HSTX_CTRL_EXPAND_TMDS_L0_NBITS_LSB |
26 << HSTX_CTRL_EXPAND_TMDS_L0_ROT_LSB;
// Pixels (TMDS) come in 4 8-bit chunks. Control symbols (RAW) are an
// entire 32-bit word.
hstx_ctrl_hw->expand_shift =
4 << HSTX_CTRL_EXPAND_SHIFT_ENC_N_SHIFTS_LSB |
8 << HSTX_CTRL_EXPAND_SHIFT_ENC_SHIFT_LSB |
1 << HSTX_CTRL_EXPAND_SHIFT_RAW_N_SHIFTS_LSB |
0 << HSTX_CTRL_EXPAND_SHIFT_RAW_SHIFT_LSB;
// Serial output config: clock period of 5 cycles, pop from command
// expander every 5 cycles, shift the output shiftreg by 2 every cycle.
hstx_ctrl_hw->csr = 0;
hstx_ctrl_hw->csr =
HSTX_CTRL_CSR_EXPAND_EN_BITS |
5u << HSTX_CTRL_CSR_CLKDIV_LSB |
5u << HSTX_CTRL_CSR_N_SHIFTS_LSB |
2u << HSTX_CTRL_CSR_SHIFT_LSB |
HSTX_CTRL_CSR_EN_BITS;
// Note we are leaving the HSTX clock at the SDK default of 125 MHz; since
// we shift out two bits per HSTX clock cycle, this gives us an output of
// 250 Mbps, which is very close to the bit clock for 480p 60Hz (252 MHz).
// If we want the exact rate then we'll have to reconfigure PLLs.
// HSTX outputs 0 through 7 appear on GPIO 12 through 19.
// Pinout on Pico DVI sock:
//
// GP12 D0- GP13 D0+
// GP14 D1- GP15 D1+
// GP16 CK- GP17 CK+
// GP18 D2- GP19 D2+
// Assign clock pair to two neighbouring pins:
hstx_ctrl_hw->bit[5] = HSTX_CTRL_BIT0_CLK_BITS;
hstx_ctrl_hw->bit[4] = HSTX_CTRL_BIT0_CLK_BITS | HSTX_CTRL_BIT0_INV_BITS;
for (uint lane = 0; lane < 3; ++lane) {
// For each TMDS lane, assign it to the correct GPIO pair based on the
// desired pinout:
static const int lane_to_output_bit[3] = {0, 2, 6};
int bit = lane_to_output_bit[lane];
// Output even bits during first half of each HSTX cycle, and odd bits
// during second half. The shifter advances by two bits each cycle.
uint32_t lane_data_sel_bits =
(lane * 10 ) << HSTX_CTRL_BIT0_SEL_P_LSB |
(lane * 10 + 1) << HSTX_CTRL_BIT0_SEL_N_LSB;
// The two halves of each pair get identical data, but one pin is inverted.
hstx_ctrl_hw->bit[bit + 1] = lane_data_sel_bits;
hstx_ctrl_hw->bit[bit ] = lane_data_sel_bits | HSTX_CTRL_BIT0_INV_BITS;
}
for (int i = 12; i <= 19; ++i) {
gpio_set_function(i, 0); // HSTX
}
// Both channels are set up identically, to transfer a whole scanline and
// then chain to the opposite channel. Each time a channel finishes, we
// reconfigure the one that just finished, meanwhile the opposite channel
// is already making progress.
dma_channel_config c;
c = dma_channel_get_default_config(DMACH_PING);
channel_config_set_chain_to(&c, DMACH_PONG);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PING,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
c = dma_channel_get_default_config(DMACH_PONG);
channel_config_set_chain_to(&c, DMACH_PING);
channel_config_set_dreq(&c, DREQ_HSTX);
dma_channel_configure(
DMACH_PONG,
&c,
&hstx_fifo_hw->fifo,
vblank_line_vsync_off,
count_of(vblank_line_vsync_off),
false
);
dma_hw->ints0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
dma_hw->inte0 = (1u << DMACH_PING) | (1u << DMACH_PONG);
irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler);
irq_set_enabled(DMA_IRQ_0, true);
bus_ctrl_hw->priority = BUSCTRL_BUS_PRIORITY_DMA_W_BITS | BUSCTRL_BUS_PRIORITY_DMA_R_BITS;
dma_channel_start(DMACH_PING);
while (1)
__wfi();
}

File diff suppressed because it is too large Load Diff