Added DVI hstx examples, started work on mandelbrot
This commit is contained in:
parent
df6d93ac32
commit
9130db7d4f
@ -5,17 +5,22 @@ list(APPEND PICO_BOARD_HEADER_DIRS lib/pico-ice-sdk/include/boards)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/.gitignore" "*")
|
||||
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)
|
||||
set(PICO_SDK_PATH ${CMAKE_SOURCE_DIR}/lib/pico-ice-sdk/lib/pico-sdk)
|
||||
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
|
||||
|
||||
project(pico2-ice-projects C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
pico_sdk_init()
|
||||
|
||||
include(cmake/FileEmbed.cmake)
|
||||
FileEmbedSetup()
|
||||
|
||||
add_subdirectory(lib/pico-ice-sdk)
|
||||
|
||||
add_subdirectory(blinky)
|
||||
add_subdirectory(dvi-example)
|
||||
add_subdirectory(dvi-dynamic)
|
||||
|
88
cmake/FileEmbed.cmake
Normal file
88
cmake/FileEmbed.cmake
Normal 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 ()
|
16
dvi-dynamic/CMakeLists.txt
Normal file
16
dvi-dynamic/CMakeLists.txt
Normal 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)
|
12
dvi-dynamic/include/framebuffer.h
Normal file
12
dvi-dynamic/include/framebuffer.h
Normal 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
|
17
dvi-dynamic/rtl/mandelbrot.rdf
Normal file
17
dvi-dynamic/rtl/mandelbrot.rdf
Normal 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>
|
5
dvi-dynamic/rtl/source/impl_1/impl_1.pdc
Normal file
5
dvi-dynamic/rtl/source/impl_1/impl_1.pdc
Normal 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}]
|
1
dvi-dynamic/rtl/source/impl_1/impl_1.sdc
Normal file
1
dvi-dynamic/rtl/source/impl_1/impl_1.sdc
Normal file
@ -0,0 +1 @@
|
||||
create_clock -period 20.83333 [get_ports {clk}]
|
21
dvi-dynamic/rtl/source/impl_1/top.sv
Normal file
21
dvi-dynamic/rtl/source/impl_1/top.sv
Normal 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
1
dvi-dynamic/rtl_bin/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.bin
|
3
dvi-dynamic/src/framebuffer.cpp
Normal file
3
dvi-dynamic/src/framebuffer.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
#include "framebuffer.h"
|
||||
|
||||
alignas(4) uint8_t FRAMEBUFFER[FRAMEBUFFER_SIZE];
|
273
dvi-dynamic/src/main.cpp
Normal file
273
dvi-dynamic/src/main.cpp
Normal 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();
|
||||
}
|
||||
}
|
12
dvi-example/CMakeLists.txt
Normal file
12
dvi-example/CMakeLists.txt
Normal 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)
|
264
dvi-example/src/dvi_out_hstx_encoder.c
Normal file
264
dvi-example/src/dvi_out_hstx_encoder.c
Normal 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();
|
||||
}
|
19207
dvi-example/src/mountains_640x480_rgb332.h
Normal file
19207
dvi-example/src/mountains_640x480_rgb332.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user