forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Closed
Labels
Milestone
Description
Here is code for implementing the 800x480 @65hz for the Adafruit 2260 display
/*
* CircuitPython RP2350 PicoDVI 800x480 Support
*
* This code adds 800x480@65Hz resolution support to CircuitPython's picodvi implementation
* for RP2350 microcontrollers with PSRAM.
*
* Timing calculations for 800x480@65Hz:
* - Pixel clock: 32.5 MHz
* - TMDS clock: 325 MHz
* - Horizontal total: 1000 pixels (800 + 40 + 80 + 80)
* - Vertical total: 500 lines (480 + 1 + 3 + 16)
* - Refresh rate: 65.00 Hz
*/
// =============================================================================
// 1. Add timing constants to the header file or at top of source file
// =============================================================================
// MODE 800x480@65Hz timing constants
#define MODE_800_H_ACTIVE_PIXELS 800
#define MODE_800_H_FRONT_PORCH 40
#define MODE_800_H_SYNC_WIDTH 80
#define MODE_800_H_BACK_PORCH 80
#define MODE_800_H_TOTAL_PIXELS (MODE_800_H_ACTIVE_PIXELS + MODE_800_H_FRONT_PORCH + MODE_800_H_SYNC_WIDTH + MODE_800_H_BACK_PORCH)
#define MODE_800_V_ACTIVE_LINES 480
#define MODE_800_V_FRONT_PORCH 1
#define MODE_800_V_SYNC_WIDTH 3
#define MODE_800_V_BACK_PORCH 16
#define MODE_800_V_TOTAL_LINES (MODE_800_V_ACTIVE_LINES + MODE_800_V_FRONT_PORCH + MODE_800_V_SYNC_WIDTH + MODE_800_V_BACK_PORCH)
// Sync command definitions for 800x400 mode
#define SYNC_V0_H0 (HSTX_CMD_RAW | (0u << HSTX_CMD_RAW_DATA_LSB))
#define SYNC_V0_H1 (HSTX_CMD_RAW | (1u << HSTX_CMD_RAW_DATA_LSB))
#define SYNC_V1_H0 (HSTX_CMD_RAW | (2u << HSTX_CMD_RAW_DATA_LSB))
#define SYNC_V1_H1 (HSTX_CMD_RAW | (3u << HSTX_CMD_RAW_DATA_LSB))
// =============================================================================
// 2. Add HSTX command arrays for 800x480 mode
// =============================================================================
static uint32_t vsync_line800[VSYNC_LEN] = {
HSTX_CMD_RAW_REPEAT | MODE_800_H_FRONT_PORCH,
SYNC_V0_H1,
HSTX_CMD_RAW_REPEAT | MODE_800_H_SYNC_WIDTH,
SYNC_V0_H0,
HSTX_CMD_RAW_REPEAT | (MODE_800_H_BACK_PORCH + MODE_800_H_ACTIVE_PIXELS),
SYNC_V0_H1
};
static uint32_t vactive_line800[VACTIVE_LEN] = {
HSTX_CMD_RAW_REPEAT | MODE_800_H_FRONT_PORCH,
SYNC_V1_H1,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_800_H_SYNC_WIDTH,
SYNC_V1_H0,
HSTX_CMD_NOP,
HSTX_CMD_RAW_REPEAT | MODE_800_H_BACK_PORCH,
SYNC_V1_H1,
HSTX_CMD_TMDS | MODE_800_H_ACTIVE_PIXELS
};
// =============================================================================
// 3. Modified validation function to support 800x480
// =============================================================================
bool common_hal_picodvi_framebuffer_deinit(picodvi_framebuffer_obj_t *self) {
// ... existing deinit code ...
}
static bool _valid_resolution(mp_uint_t width, mp_uint_t height, mp_uint_t color_depth) {
bool full_resolution = color_depth == 1 || color_depth == 2 || color_depth == 4;
bool doubled = color_depth == 8 || color_depth == 16 || color_depth == 32;
// Support for existing resolutions (640x480, 720x400)
if (width == 640 && height == 480) {
return full_resolution;
}
if (width == 320 && height == 240) {
return doubled;
}
if (width == 160 && height == 120) {
return doubled;
}
if (width == 720 && height == 400) {
return full_resolution;
}
if (width == 360 && height == 200) {
return doubled;
}
if (width == 180 && height == 100) {
return doubled;
}
// NEW: Support for 800x480 resolution
if (width == 800 && height == 480) {
return full_resolution;
}
if (width == 400 && height == 240) {
return doubled;
}
if (width == 200 && height == 120) {
return doubled;
}
return false;
}
// =============================================================================
// 4. Modified constructor function with 800x480 support
// =============================================================================
void common_hal_picodvi_framebuffer_construct(picodvi_framebuffer_obj_t *self,
mp_uint_t width, mp_uint_t height,
const mcu_pin_obj_t *clk_dp, const mcu_pin_obj_t *clk_dn,
const mcu_pin_obj_t *red_dp, const mcu_pin_obj_t *red_dn,
const mcu_pin_obj_t *green_dp, const mcu_pin_obj_t *green_dn,
const mcu_pin_obj_t *blue_dp, const mcu_pin_obj_t *blue_dn,
mp_uint_t color_depth) {
// Validate resolution
if (!_valid_resolution(width, height, color_depth)) {
mp_raise_ValueError(MP_ERROR_TEXT("Invalid resolution"));
}
// Determine output scaling and dimensions
mp_uint_t output_scaling = 1;
if (color_depth == 8 || color_depth == 16 || color_depth == 32) {
output_scaling = 2;
}
// Set output dimensions based on framebuffer size
if (width * output_scaling == 640) {
self->output_width = 640;
self->output_height = 480;
} else if (width * output_scaling == 720) {
self->output_width = 720;
self->output_height = 400;
} else if (width * output_scaling == 800) { // NEW: 800x480 support
self->output_width = 800;
self->output_height = 480;
} else {
mp_raise_ValueError(MP_ERROR_TEXT("Unsupported resolution"));
}
// Pin validation for HSTX (GPIO 12-19)
const mcu_pin_obj_t *pins[] = {clk_dp, clk_dn, red_dp, red_dn, green_dp, green_dn, blue_dp, blue_dn};
size_t pin_numbers[8];
size_t all_allocated = 0;
static const qstr pin_names[] = {
MP_QSTR_clk_dp, MP_QSTR_clk_dn,
MP_QSTR_red_dp, MP_QSTR_red_dn,
MP_QSTR_green_dp, MP_QSTR_green_dn,
MP_QSTR_blue_dp, MP_QSTR_blue_dn
};
for (size_t i = 0; i < 8; i++) {
if (!(12 <= pins[i]->number && pins[i]->number <= 19)) {
raise_ValueError_invalid_pin_name(pin_names[i]);
}
pin_numbers[i] = pins[i]->number - 12;
size_t mask = 1 << pin_numbers[i];
if ((all_allocated & mask) != 0) {
raise_ValueError_invalid_pin_name(pin_names[i]);
}
all_allocated |= mask;
}
// Initialize framebuffer properties
self->width = width;
self->height = height;
self->color_depth = color_depth;
// Calculate pitch (32-bit words per line)
size_t pitch_bytes = (self->width * color_depth) / 8;
self->pitch = (pitch_bytes + sizeof(uint32_t) - 1) / sizeof(uint32_t);
size_t framebuffer_size = self->pitch * self->height;
// Allocate framebuffer (will use PSRAM for large buffers)
self->framebuffer = (uint32_t *)port_malloc(framebuffer_size * sizeof(uint32_t), true);
if (self->framebuffer == NULL || ((size_t)self->framebuffer & 0xf0000000) == 0x10000000) {
common_hal_picodvi_framebuffer_deinit(self);
m_malloc_fail(framebuffer_size * sizeof(uint32_t));
return;
}
memset(self->framebuffer, 0, framebuffer_size * sizeof(uint32_t));
// Calculate DMA command buffer size
size_t dma_command_size = 2;
if (output_scaling > 1) {
dma_command_size = 4;
}
// Set DMA command length based on resolution
if (self->output_width == 640) {
self->dma_commands_len = (MODE_640_V_FRONT_PORCH + MODE_640_V_SYNC_WIDTH +
MODE_640_V_BACK_PORCH + 2 * MODE_640_V_ACTIVE_LINES + 1) * dma_command_size;
} else if (self->output_width == 720) {
self->dma_commands_len = (MODE_720_V_FRONT_PORCH + MODE_720_V_SYNC_WIDTH +
MODE_720_V_BACK_PORCH + 2 * MODE_720_V_ACTIVE_LINES + 1) * dma_command_size;
} else if (self->output_width == 800) { // NEW: 800x400 support
self->dma_commands_len = (MODE_800_V_FRONT_PORCH + MODE_800_V_SYNC_WIDTH +
MODE_800_V_BACK_PORCH + 2 * MODE_800_V_ACTIVE_LINES + 1) * dma_command_size;
}
// Allocate DMA command buffer
self->dma_commands = (uint32_t *)port_malloc(self->dma_commands_len * sizeof(uint32_t), true);
if (self->dma_commands == NULL || ((size_t)self->dma_commands & 0xf0000000) == 0x10000000) {
common_hal_picodvi_framebuffer_deinit(self);
m_malloc_fail(self->dma_commands_len * sizeof(uint32_t));
return;
}
// Claim DMA channels
self->dma_pixel_channel = dma_claim_unused_channel(false);
self->dma_command_channel = dma_claim_unused_channel(false);
if (self->dma_pixel_channel < 0 || self->dma_command_channel < 0) {
common_hal_picodvi_framebuffer_deinit(self);
mp_raise_RuntimeError(MP_ERROR_TEXT("Internal resource(s) in use"));
return;
}
// Build DMA command sequence
size_t command_word = 0;
size_t frontporch_start, frontporch_end, vsync_start, vsync_end, backporch_start, backporch_end, active_start, active_end;
// Calculate line ranges based on resolution
if (self->output_width == 640) {
frontporch_start = MODE_640_V_TOTAL_LINES - MODE_640_V_FRONT_PORCH;
frontporch_end = frontporch_start + MODE_640_V_FRONT_PORCH;
vsync_start = frontporch_end;
vsync_end = vsync_start + MODE_640_V_SYNC_WIDTH;
backporch_start = vsync_end;
backporch_end = backporch_start + MODE_640_V_BACK_PORCH;
active_start = backporch_end;
active_end = active_start + MODE_640_V_ACTIVE_LINES;
} else if (self->output_width == 720) {
frontporch_start = MODE_720_V_TOTAL_LINES - MODE_720_V_FRONT_PORCH;
frontporch_end = frontporch_start + MODE_720_V_FRONT_PORCH;
vsync_start = frontporch_end;
vsync_end = vsync_start + MODE_720_V_SYNC_WIDTH;
backporch_start = vsync_end;
backporch_end = backporch_start + MODE_720_V_BACK_PORCH;
active_start = backporch_end;
active_end = active_start + MODE_720_V_ACTIVE_LINES;
} else if (self->output_width == 800) { // NEW: 800x400 support
frontporch_start = MODE_800_V_TOTAL_LINES - MODE_800_V_FRONT_PORCH;
frontporch_end = frontporch_start + MODE_800_V_FRONT_PORCH;
vsync_start = frontporch_end;
vsync_end = vsync_start + MODE_800_V_SYNC_WIDTH;
backporch_start = vsync_end;
backporch_end = backporch_start + MODE_800_V_BACK_PORCH;
active_start = backporch_end;
active_end = active_start + MODE_800_V_ACTIVE_LINES;
}
// Generate DMA commands for each line type
for (size_t line = 0; line < frontporch_start; line++) {
// Front porch lines use vsync command sequence
uint32_t *line_commands = (self->output_width == 640) ? vsync_line640 :
(self->output_width == 720) ? vsync_line720 : vsync_line800;
for (size_t i = 0; i < VSYNC_LEN; i += 2) {
self->dma_commands[command_word++] = line_commands[i + 1]; // control word
self->dma_commands[command_word++] = line_commands[i]; // data word
if (output_scaling > 1) {
self->dma_commands[command_word++] = line_commands[i + 1];
self->dma_commands[command_word++] = line_commands[i];
}
}
}
// Configure HSTX peripheral for the appropriate timing
if (self->output_width == 800) {
// Set system clock for 800x480@65Hz
// 32.5 MHz pixel clock requires 325 MHz TMDS clock
set_sys_clock_khz(325000, true); // 65Hz refresh rate
}
// ... rest of HSTX configuration code ...
// The implementation continues with HSTX peripheral setup,
// DMA configuration, and interrupt handlers following the
// same pattern as existing modes but using the 800x400 timing constants
}
// =============================================================================
// 5. Usage Example
// =============================================================================
/*
Python usage example for 800x480@65Hz mode:
import board
import picodvi
import framebufferio
import displayio
displayio.release_displays()
# Create 800x480 framebuffer at 8-bit color depth (65Hz refresh)
fb = picodvi.Framebuffer(800, 480,
clk_dp=board.CKP, clk_dn=board.CKN,
red_dp=board.D0P, red_dn=board.D0N,
green_dp=board.D1P, green_dn=board.D1N,
blue_dp=board.D2P, blue_dn=board.D2N,
color_depth=8)
display = framebufferio.FramebufferDisplay(fb)
# For memory-constrained scenarios, use smaller framebuffer with pixel doubling:
fb_small = picodvi.Framebuffer(400, 240, # Will be doubled to 800x480 output
clk_dp=board.CKP, clk_dn=board.CKN,
red_dp=board.D0P, red_dn=board.D0N,
green_dp=board.D1P, green_dn=board.D1N,
blue_dp=board.D2P, blue_dn=board.D2N,
color_depth=16) # 16-bit color with pixel doubling
# Memory requirements for 800x480:
# - 8-bit color: ~384KB (requires PSRAM)
# - 16-bit color: ~768KB (requires PSRAM)
# - 1-bit mono: ~48KB (fits in internal SRAM)
# - 400x240@16-bit: ~192KB (fits in internal SRAM with pixel doubling)
*/