So I am developing a project which will need a VGA output at 640x480 at 8 bits / pixel (256 colors). The obvious choice would be to use the RP2350's PIO to make it happen so that's what I'm trying to do, so I tried adapting a code made by Hunter Adams: https://github.com/vha3/Hunter-Adams-RP ... elbrot_Set
That was made to work with Pico 1, so it couldn't store the whole 640x480 framebuffer in it's 256KB of RAM so the code displayed 2 pixels per byte of framebuffer, so 4 bits / pixel (16 colors), thus cutting the framebuffer RAM requirement in half.
My project will use the RP2350, which has more than enough RAM to accommodate the full 640x480 framebuffer at 8 bits per pixel (307KB), so I tried adapting Hunter Adam's code it so that it will output a single pixel for every byte. The display detects the signal correctly, but displays no color, it's all black.
Pin Configuration:
GP8 ... resistor 1K0 to VGA Blue input
GP9 ... resistor 390 to VGA Blue input
GP10 ... resistor 2K2 to VGA Green input
GP11 ... resistor 1K0 to VGA Green input
GP12 ... resistor 470 to VGA Green input
GP13 ... resistor 2K2 to VGA Red input
GP14 ... resistor 1K0 to VGA Red input
GP15 ... resistor 470 to VGA Red input
Here are the codes:
Main file - the only changes here was the "TXCOUNT" value which was changed from 153600 (half of 640x480 framebuffer) to 307200 (full 640x480 framebuffer) and "RGB_ACTIVE" value which was changed from 319 (half of horizontal active -1) to 639 (horizontal active - 1).
The framebuffer is started with all pixels set to white."rgb.pio.h" (not assembled for ease of reading) - generates the pixel data -- the only thing I changed in this file from the original was the amount of pins that would be output to and the amount of bits to be pushed out of OSR to those pins."hsync.pio.h" - generates HSYNC signal -- not changed"vsync.pio.h" - generates VSYNC signal -- not changed
I thank everyone brave enough to try to solve this...
That was made to work with Pico 1, so it couldn't store the whole 640x480 framebuffer in it's 256KB of RAM so the code displayed 2 pixels per byte of framebuffer, so 4 bits / pixel (16 colors), thus cutting the framebuffer RAM requirement in half.
My project will use the RP2350, which has more than enough RAM to accommodate the full 640x480 framebuffer at 8 bits per pixel (307KB), so I tried adapting Hunter Adam's code it so that it will output a single pixel for every byte. The display detects the signal correctly, but displays no color, it's all black.
Pin Configuration:
GP8 ... resistor 1K0 to VGA Blue input
GP9 ... resistor 390 to VGA Blue input
GP10 ... resistor 2K2 to VGA Green input
GP11 ... resistor 1K0 to VGA Green input
GP12 ... resistor 470 to VGA Green input
GP13 ... resistor 2K2 to VGA Red input
GP14 ... resistor 1K0 to VGA Red input
GP15 ... resistor 470 to VGA Red input
Here are the codes:
Main file - the only changes here was the "TXCOUNT" value which was changed from 153600 (half of 640x480 framebuffer) to 307200 (full 640x480 framebuffer) and "RGB_ACTIVE" value which was changed from 319 (half of horizontal active -1) to 639 (horizontal active - 1).
The framebuffer is started with all pixels set to white.
Code:
#include "pico/stdlib.h"#include "hardware/pio.h"#include "hardware/dma.h"#include "hsync.pio.h"#include "vsync.pio.h"#include "rgb.pio.h"#define TXCOUNT 307200 // Total pixelsuint8_t vga_data_array[TXCOUNT];uint8_t * address_pointer = &vga_data_array[0] ;#define HSYNC 07 #define VSYNC 06// VGA timing constants#define H_ACTIVE 655 // (active + frontporch - 1) - one cycle delay for mov#define V_ACTIVE 479 // (active - 1)#define RGB_ACTIVE 639 // (horizontal active) - 1// Screen width/height#define _width 640#define _height 480void setup() { memset(vga_data_array, 0b11111111, TXCOUNT); Serial.begin(115200); PIO pio = pio0; uint hsync_offset = pio_add_program(pio, &hsync_program); uint vsync_offset = pio_add_program(pio, &vsync_program); uint rgb_offset = pio_add_program(pio, &rgb_program); // Manually select a few state machines from pio instance pio0. uint hsync_sm = 0; uint vsync_sm = 1; uint rgb_sm = 2; // Call the initialization functions that are defined within each PIO file. // Why not create these programs here? By putting the initialization function in // the pio file, then all information about how to use/setup that state machine // is consolidated in one place. Here in the C, we then just import and use it. hsync_program_init(pio, hsync_sm, hsync_offset, HSYNC); vsync_program_init(pio, vsync_sm, vsync_offset, VSYNC); rgb_program_init(pio, rgb_sm, rgb_offset, 8); ///////////////////////////////////////////////////////////////////////////////////////////////////// // ============================== PIO DMA Channels ================================================= ///////////////////////////////////////////////////////////////////////////////////////////////////// // DMA channels - 0 sends color data, 1 reconfigures and restarts 0 int rgb_chan_0 = dma_claim_unused_channel(true); int rgb_chan_1 = dma_claim_unused_channel(true); // Channel Zero (sends color data to PIO VGA machine) dma_channel_config c0 = dma_channel_get_default_config(rgb_chan_0); // default configs channel_config_set_transfer_data_size(&c0, DMA_SIZE_8); // 8-bit txfers channel_config_set_read_increment(&c0, true); // yes read incrementing channel_config_set_write_increment(&c0, false); // no write incrementing channel_config_set_dreq(&c0, DREQ_PIO0_TX2) ; // DREQ_PIO0_TX2 pacing (FIFO) channel_config_set_chain_to(&c0, rgb_chan_1); // chain to other channel dma_channel_configure( rgb_chan_0, // Channel to be configured &c0, // The configuration we just created &pio->txf[rgb_sm], // write address (RGB PIO TX FIFO) &vga_data_array, // The initial read address (pixel color array) TXCOUNT, // Number of transfers; in this case each is 1 byte. false // Don't start immediately. ); // Channel One (reconfigures the first channel) dma_channel_config c1 = dma_channel_get_default_config(rgb_chan_1); // default configs channel_config_set_transfer_data_size(&c1, DMA_SIZE_32); // 32-bit txfers channel_config_set_read_increment(&c1, false); // no read incrementing channel_config_set_write_increment(&c1, false); // no write incrementing channel_config_set_chain_to(&c1, rgb_chan_0); // chain to other channel dma_channel_configure( rgb_chan_1, // Channel to be configured &c1, // The configuration we just created &dma_hw->ch[rgb_chan_0].read_addr, // Write address (channel 0 read address) &address_pointer, // Read address (POINTER TO AN ADDRESS) 1, // Number of transfers, in this case each is 4 byte false // Don't start immediately. ); ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// // Initialize PIO state machine counters. This passes the information to the state machines // that they retrieve in the first 'pull' instructions, before the .wrap_target directive // in the assembly. Each uses these values to initialize some counting registers. pio_sm_put_blocking(pio, hsync_sm, H_ACTIVE); pio_sm_put_blocking(pio, vsync_sm, V_ACTIVE); pio_sm_put_blocking(pio, rgb_sm, RGB_ACTIVE); // Start the two pio machine IN SYNC // Note that the RGB state machine is running at full speed, // so synchronization doesn't matter for that one. But, we'll // start them all simultaneously anyway. pio_enable_sm_mask_in_sync(pio, ((1u << hsync_sm) | (1u << vsync_sm) | (1u << rgb_sm))); // Start DMA channel 0. Once started, the contents of the pixel color array // will be continously DMA's to the PIO machines that are driving the screen. // To change the contents of the screen, we need only change the contents // of that array. dma_start_channel_mask((1u << rgb_chan_0)) ;}void loop() { }
Code:
;; Hunter Adams (vha3@cornell.edu); RGB generation for VGA driver; mod by Bruce Land for 16 colors; Program name.program rgbpull block ; Pull from FIFO to OSR (only once)mov y, osr ; Copy value from OSR to y scratch register.wrap_targetset pins, 0 ; Zero RGB pins in blankingmov x, y ; Initialize counter variablewait 1 irq 1 [3]; Wait for vsync active mode (starts 5 cycles after execution)colorout:pull block; Pull color valueout pins, 8[2]; Push out to pins (first byte)jmp x-- colorout; Stay here thru horizontal active mode.wrap% c-sdk {static inline void rgb_program_init(PIO pio, uint sm, uint offset, uint pin) { // creates state machine configuration object c, sets // to default configurations. I believe this function is auto-generated // and gets a name of <program name>_program_get_default_config // Yes, page 40 of SDK guide pio_sm_config c = rgb_program_get_default_config(offset); // Map the state machine's SET and OUT pin group to three pins, the `pin` // parameter to this function is the lowest one. These groups overlap. sm_config_set_set_pins(&c, pin, 8); sm_config_set_out_pins(&c, pin, 8); // Set clock division (Commented out, this one runs at full speed) // sm_config_set_clkdiv(&c, 5) ; // Set this pin's GPIO function (connect PIO to the pad) pio_gpio_init(pio, pin); pio_gpio_init(pio, pin+1); pio_gpio_init(pio, pin+2); pio_gpio_init(pio, pin+3); pio_gpio_init(pio, pin+4); pio_gpio_init(pio, pin+5); pio_gpio_init(pio, pin+6); pio_gpio_init(pio, pin+7); // Set the pin direction to output at the PIO (8 pins) pio_sm_set_consecutive_pindirs(pio, sm, pin, 8, true); // Load our configuration, and jump to the start of the program pio_sm_init(pio, sm, offset, &c); // Set the state machine running (commented out, I'll start this in the C) // pio_sm_set_enabled(pio, sm, true);}%}
Code:
// -------------------------------------------------- //// This file is autogenerated by pioasm; do not edit! //// -------------------------------------------------- //#pragma once#if !PICO_NO_HARDWARE#include "hardware/pio.h"#endif// ----- //// hsync //// ----- //#define hsync_wrap_target 1#define hsync_wrap 8static const uint16_t hsync_program_instructions[] = { 0x80a0, // 0: pull block // .wrap_target 0xa027, // 1: mov x, osr 0x0042, // 2: jmp x--, 2 0xff00, // 3: set pins, 0 [31] 0xff00, // 4: set pins, 0 [31] 0xff00, // 5: set pins, 0 [31] 0xff01, // 6: set pins, 1 [31] 0xec01, // 7: set pins, 1 [12] 0xc100, // 8: irq nowait 0 [1] // .wrap};#if !PICO_NO_HARDWAREstatic const struct pio_program hsync_program = { .instructions = hsync_program_instructions, .length = 9, .origin = -1,};static inline pio_sm_config hsync_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + hsync_wrap_target, offset + hsync_wrap); return c;}static inline void hsync_program_init(PIO pio, uint sm, uint offset, uint pin) { // creates state machine configuration object c, sets // to default configurations. I believe this function is auto-generated // and gets a name of <program name>_program_get_default_config // Yes, page 40 of SDK guide pio_sm_config c = hsync_program_get_default_config(offset); // Map the state machine's SET pin group to one pin, namely the `pin` // parameter to this function. sm_config_set_set_pins(&c, pin, 1); // Set clock division (div by 5.9583 for 25 MHz state machine) sm_config_set_clkdiv(&c, 5.9583) ; // Set this pin's GPIO function (connect PIO to the pad) pio_gpio_init(pio, pin); // pio_gpio_init(pio, pin+1); // Set the pin direction to output at the PIO pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); // Load our configuration, and jump to the start of the program pio_sm_init(pio, sm, offset, &c); // Set the state machine running (commented out so can be synchronized w/ vsync) // pio_sm_set_enabled(pio, sm, true);}#endif
Strangely enough, when I try outputting 7 bits instead of 8 on the "rgb.pio.h" file, it works normally and will display 127 colors (7 bit color).// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// vsync //
// ----- //
#define vsync_wrap_target 1
#define vsync_wrap 13
static const uint16_t vsync_program_instructions[] = {
0x80a0, // 0: pull block
// .wrap_target
0xa027, // 1: mov x, osr
0x20c0, // 2: wait 1 irq, 0
0xc001, // 3: irq nowait 1
0x0042, // 4: jmp x--, 2
0xe049, // 5: set y, 9
0x20c0, // 6: wait 1 irq, 0
0x0086, // 7: jmp y--, 6
0xe000, // 8: set pins, 0
0x20c0, // 9: wait 1 irq, 0
0x20c0, // 10: wait 1 irq, 0
0xe05f, // 11: set y, 31
0x38c0, // 12: wait 1 irq, 0 side 1
0x008c, // 13: jmp y--, 12
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program vsync_program = {
.instructions = vsync_program_instructions,
.length = 14,
.origin = -1,
};
static inline pio_sm_config vsync_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + vsync_wrap_target, offset + vsync_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void vsync_program_init(PIO pio, uint sm, uint offset, uint pin) {
// creates state machine configuration object c, sets
// to default configurations. I believe this function is auto-generated
// and gets a name of <program name>_program_get_default_config
// Yes, page 40 of SDK guide
pio_sm_config c = vsync_program_get_default_config(offset);
// Map the state machine's SET pin group to one pin, namely the `pin`
// parameter to this function.
sm_config_set_set_pins(&c, pin, 1);
sm_config_set_sideset_pins(&c, pin);
// Set clock division (div by 5.9583 for 25 MHz state machine)
sm_config_set_clkdiv(&c, 5.9583) ;
// Set this pin's GPIO function (connect PIO to the pad)
pio_gpio_init(pio, pin);
// pio_gpio_init(pio, pin+1);
// Set the pin direction to output at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &c);
// Set the state machine running (commented out so can be synchronized with hsync)
// pio_sm_set_enabled(pio, sm, true);
}
#endif
I thank everyone brave enough to try to solve this...
Statistics: Posted by PU5PSY — Thu Feb 20, 2025 2:10 am — Replies 3 — Views 68