Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HDMI with audio support #45

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions Readme.md
@@ -1,5 +1,10 @@
Bitbanged DVI on the RP2040 Microcontroller
===========================================
Bitbanged DVI on the RP2040 Microcontroller with HDMI and audio support
==================================================================================
Big Kudos to @shuichitakano to fix the audio support

Using https://github.com/shuichitakano/pico_lib C++ implementation of the HDMI with audio support
For picoDVI from https://github.com/Wren6991/PicoDVI
DVI start/stop works, audio works with some limitations of cpu usage, if line or pixel doubling is enabled works

![](img/mountains.jpg)

Expand Down
43 changes: 43 additions & 0 deletions software/Readme.md
Expand Up @@ -3,6 +3,49 @@ Building

You need to have the Pico SDK installed, as well as a recent CMake and arm-none-eabi-gcc.

# Setup
- Download the pico-sdk, pico-extras, and pico-playground repositories
- Define PICO_SDK_PATH in your ~/.bashrc•
- Download the pico-sdk repository

# Build
```
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
```
Go to the specific folder and

`make -j4`

# Visual Studio Code integration
Install the extensions as explained in the [Pico Getting started manual](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf)

Or just download [VScode](https://code.visualstudio.com/Download) and add the following extension

```
code --install-extension marus25.cortex-debug
code --install-extension ms-vscode.cmake-tools
code --install-extension ms-vscode.cpptools
```

# Debug
Install open ocd as explained in the [Pico Getting started manual section Installing OpenOCD](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf)

#Using Pico probe
On the same document check **Appendix A: Using Picoprobe**

Open deploy and run
```
openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -c "program <specific elf file> verify reset exit"
```

# Read RP2040 to uf2
```
picotool save -a pico_rgb2hdmi_xyz.uf2
```

# Above this line is the original readme

```bash
mkdir build
Expand Down
1 change: 1 addition & 0 deletions software/apps/CMakeLists.txt
Expand Up @@ -7,6 +7,7 @@ add_subdirectory(hello_dvi)
add_subdirectory(mandelbrot)
add_subdirectory(moon)
add_subdirectory(sprite_bounce)
add_subdirectory(sprite_bounce_audio)
add_subdirectory(terminal)
add_subdirectory(tiles)
add_subdirectory(tiles_and_sprites)
Expand Down
24 changes: 24 additions & 0 deletions software/apps/sprite_bounce_audio/CMakeLists.txt
@@ -0,0 +1,24 @@
# Replace TMDS with 10 bit UART (same baud rate):
# add_definitions(-DDVI_SERIAL_DEBUG=1)
# add_definitions(-DRUN_FROM_CRYSTAL)

add_executable(sprite_bounce_audio
main.c
)

target_compile_options(sprite_bounce_audio PRIVATE -Wall)

target_compile_definitions(sprite_bounce_audio PRIVATE
DVI_DEFAULT_SERIAL_CONFIG=${DVI_DEFAULT_SERIAL_CONFIG}
)

target_link_libraries(sprite_bounce_audio
pico_stdlib
pico_multicore
pico_util
libdvi
libsprite
)

# create map/bin/hex file etc.
pico_add_extra_outputs(sprite_bounce_audio)
235 changes: 235 additions & 0 deletions software/apps/sprite_bounce_audio/main.c
@@ -0,0 +1,235 @@
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/clocks.h"
#include "hardware/irq.h"
#include "hardware/sync.h"
#include "hardware/gpio.h"
#include "hardware/vreg.h"
#include "pico/sem.h"

#include "dvi.h"
#include "dvi_serialiser.h"
#include "common_dvi_pin_configs.h"
#include "sprite.h"

// Pick one:
#define MODE_640x480_60Hz
// #define MODE_800x480_60Hz
// #define MODE_800x600_60Hz
// #define MODE_960x540p_60Hz
// #define MODE_1280x720_30Hz

#include "raspberry_128x128_rgab5515.h"
#include "eben_128x128_rgab5515.h"

#if defined(MODE_640x480_60Hz)
// DVDD 1.2V (1.1V seems ok too)
#define FRAME_WIDTH 320
#define FRAME_HEIGHT 240
#define VREG_VSEL VREG_VOLTAGE_1_20
#define DVI_TIMING dvi_timing_640x480p_60hz

#elif defined(MODE_800x480_60Hz)
#define FRAME_WIDTH 400
#define FRAME_HEIGHT 240
#define VREG_VSEL VREG_VOLTAGE_1_20
#define DVI_TIMING dvi_timing_800x480p_60hz

#elif defined(MODE_800x600_60Hz)
// DVDD 1.3V, going downhill with a tailwind
#define FRAME_WIDTH 400
#define FRAME_HEIGHT 300
#define VREG_VSEL VREG_VOLTAGE_1_30
#define DVI_TIMING dvi_timing_800x600p_60hz

#elif defined(MODE_960x540p_60Hz)
// DVDD 1.25V (slower silicon may need the full 1.3, or just not work)
// Frame resolution is almost the same as a PSP :)
#define FRAME_WIDTH 480
#define FRAME_HEIGHT 270
#define VREG_VSEL VREG_VOLTAGE_1_25
#define DVI_TIMING dvi_timing_960x540p_60hz

#elif defined(MODE_1280x720_30Hz)
// 1280x720p 30 Hz (nonstandard)
// DVDD 1.25V (slower silicon may need the full 1.3, or just not work)
#define FRAME_WIDTH 640
#define FRAME_HEIGHT 360
#define VREG_VSEL VREG_VOLTAGE_1_25
#define DVI_TIMING dvi_timing_1280x720p_30hz

#else
#error "Select a video mode!"
#endif

#define N_BERRIES 65
#define LED_PIN 21

struct dvi_inst dvi0;

void core1_main() {
dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
while (queue_is_empty(&dvi0.q_colour_valid))
__wfe();
dvi_start(&dvi0);
dvi_scanbuf_main_16bpp(&dvi0);
__builtin_unreachable();
}

static inline int clip(int x, int min, int max) {
return x < min ? min : x > max ? max : x;
}

#define N_SCANLINE_BUFFERS 4
uint16_t static_scanbuf[N_SCANLINE_BUFFERS][FRAME_WIDTH];

sprite_t berry[N_BERRIES];
int vx[N_BERRIES];
int vy[N_BERRIES];
int vt[N_BERRIES];
uint8_t theta[N_BERRIES];
affine_transform_t atrans[N_BERRIES];

const int xmin = -100;
const int xmax = FRAME_WIDTH - 30;
const int ymin = -100;
const int ymax = FRAME_HEIGHT - 30;
const int vmax = 4;

//Audio Related
#define AUDIO_BUFFER_SIZE 256
audio_sample_t audio_buffer[AUDIO_BUFFER_SIZE];
struct repeating_timer audio_timer;
const int16_t sine[32] = {
0x8000,0x98f8,0xb0fb,0xc71c,0xda82,0xea6d,0xf641,0xfd89,
0xffff,0xfd89,0xf641,0xea6d,0xda82,0xc71c,0xb0fb,0x98f8,
0x8000,0x6707,0x4f04,0x38e3,0x257d,0x1592,0x9be,0x276,
0x0,0x276,0x9be,0x1592,0x257d,0x38e3,0x4f04,0x6707
};

bool audio_timer_callback(struct repeating_timer *t) {
int size = get_write_size(&dvi0.audio_ring, false);
audio_sample_t *audio_ptr = get_write_pointer(&dvi0.audio_ring);
audio_sample_t sample;
static uint sample_count = 0;
for (int cnt = 0; cnt < size; cnt++) {
sample.channels[0] = sine[sample_count % 32];
sample.channels[1] = sine[sample_count % 32];
*audio_ptr++ = sample;
sample_count++;
}
increase_write_pointer(&dvi0.audio_ring, size);

return true;
}


void __not_in_flash("render") render_loop() {
uint heartbeat = 0;
uint frame_ctr = 0;
while (1) {
if (++heartbeat >= 30) {
heartbeat = 0;
gpio_xor_mask(1u << LED_PIN);
}
for (uint y = 0; y < FRAME_HEIGHT; ++y) {
uint16_t *pixbuf;
queue_remove_blocking(&dvi0.q_colour_free, &pixbuf);
// sprite_blit16(pixbuf, (const uint16_t *)testcard_320x240 + (y + frame_ctr / 2) % 240 * FRAME_WIDTH, 320);
sprite_fill16(pixbuf, 0x07ff, FRAME_WIDTH);
for (int i = 0; i < N_BERRIES; ++i)
// sprite_asprite16(pixbuf, &berry[i], atrans[i], y, FRAME_WIDTH);
sprite_sprite16(pixbuf, &berry[i], y, FRAME_WIDTH);
queue_add_blocking(&dvi0.q_colour_valid, &pixbuf);
}
// Update during vblank
for (int i = 0; i < N_BERRIES; ++i) {
berry[i].x += vx[i];
berry[i].y += vy[i];
theta[i] += vt[i];
affine_identity(atrans[i]);
affine_scale(atrans[i], 7 * AF_ONE / 8, 7 * AF_ONE / 8);
affine_translate(atrans[i], -56, -56);
affine_rotate(atrans[i], theta[i]);
affine_translate(atrans[i], 60, 60);
int xclip = clip(berry[i].x, xmin, xmax);
int yclip = clip(berry[i].y, ymin, ymax);
if (xclip != berry[i].x || yclip != berry[i].y) {
berry[i].x = xclip;
berry[i].y = yclip;
vx[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
vy[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
vt[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
berry[i].hflip = vx[i] < 0;
berry[i].vflip = vy[i] < 0;
}
}
++frame_ctr;
}
}




int main() {
vreg_set_voltage(VREG_VSEL);
sleep_ms(10);
#ifdef RUN_FROM_CRYSTAL
// Slow everything down uniformly, so signals are probeable but the code runs
// identically (note this actually uses the PLL with low feedback and max PD1/PD2)
set_sys_clock_khz(12000, true);
#else
// Run system at TMDS bit clock
set_sys_clock_khz(DVI_TIMING.bit_clk_khz, true);
#endif

setup_default_uart();

gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);

printf("Configuring DVI\n");

dvi0.timing = &DVI_TIMING;
dvi0.ser_cfg = DVI_DEFAULT_SERIAL_CONFIG;
dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num());

// HDMI Audio related
dvi_get_blank_settings(&dvi0)->top = 4 * 2;
dvi_get_blank_settings(&dvi0)->bottom = 4 * 2;
dvi_audio_sample_buffer_set(&dvi0, audio_buffer, AUDIO_BUFFER_SIZE);
dvi_set_audio_freq(&dvi0, 44100, 28000, 6272);
add_repeating_timer_ms(2, audio_timer_callback, NULL, &audio_timer);

printf("Core 1 start\n");
multicore_launch_core1(core1_main);

printf("Allocating scanline buffers\n");
for (int i = 0; i < N_SCANLINE_BUFFERS; ++i) {
void *bufptr = &static_scanbuf[i];
queue_add_blocking((void*)&dvi0.q_colour_free, &bufptr);
}

for (int i = 0; i < N_BERRIES; ++i) {
berry[i].x = rand() % (xmax - xmin + 1) + xmin;
berry[i].y = rand() % (ymax - ymin + 1) + ymin;
berry[i].img = i % 2 ? eben_128x128 : raspberry_128x128;
berry[i].log_size = 7;
berry[i].has_opacity_metadata = true; // Much faster non-AT blitting
berry[i].hflip = false;
berry[i].vflip = false;
vx[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
vy[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
vt[i] = (rand() % vmax + 1) * (rand() & 0x8000 ? 1 : -1);
theta[i] = 0;
affine_identity(atrans[i]);
}

// Core 1 will fire up the DVI once it sees the first colour buffer has been rendered
printf("Start rendering\n");
render_loop();
__builtin_unreachable();
}

21 changes: 15 additions & 6 deletions software/include/common_dvi_pin_configs.h
Expand Up @@ -61,13 +61,22 @@ static const struct dvi_serialiser_cfg micromod_cfg = {
};

// Pico DVI Sock (small hat on the bottom) which solders to the end of a Pico
// static const struct dvi_serialiser_cfg pico_sock_cfg = {
// .pio = DVI_DEFAULT_PIO_INST,
// .sm_tmds = {0, 1, 2},
// .pins_tmds = {12, 18, 16},
// .pins_clk = 14,
// .invert_diffpairs = false
// };

// pico-RGB2HDMI
static const struct dvi_serialiser_cfg pico_sock_cfg = {
.pio = DVI_DEFAULT_PIO_INST,
.sm_tmds = {0, 1, 2},
.pins_tmds = {12, 18, 16},
.pins_clk = 14,
.invert_diffpairs = false
};
.pio = pio0,
.sm_tmds = {0, 1, 2},
.pins_tmds = {5, 7, 9},
.pins_clk = 3,
.invert_diffpairs = true
};

// The HDMI socket on Pimoroni Pico Demo HDMI
// (we would talk about rev B if we had a rev B...)
Expand Down
4 changes: 4 additions & 0 deletions software/libdvi/CMakeLists.txt
Expand Up @@ -17,6 +17,10 @@ target_sources(libdvi INTERFACE
${CMAKE_CURRENT_LIST_DIR}/tmds_table.h
${CMAKE_CURRENT_LIST_DIR}/tmds_table_fullres.h
${CMAKE_CURRENT_LIST_DIR}/util_queue_u32_inline.h
${CMAKE_CURRENT_LIST_DIR}/data_packet.c
${CMAKE_CURRENT_LIST_DIR}/data_packet.h
${CMAKE_CURRENT_LIST_DIR}/audio_ring.c
${CMAKE_CURRENT_LIST_DIR}/audio_ring.h
)

target_include_directories(libdvi INTERFACE ${CMAKE_CURRENT_LIST_DIR})
Expand Down