Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 4387

General • PIO won't generate 8-bit VGA signals

$
0
0
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.

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() {  }
"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.

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);}%}
"hsync.pio.h" - generates HSYNC signal -- not changed

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
"vsync.pio.h" - generates VSYNC signal -- not changed
// -------------------------------------------------- //
// 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
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).
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



Viewing all articles
Browse latest Browse all 4387

Trending Articles