Skip to content

Add picodvi support for 800x480 DVI resolution for Adafruit PID 2260 #10502

@TheKitty

Description

@TheKitty

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)
*/

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy