Smek
Smek

Reputation: 1208

Raspberry Pi Pico 64 color VGA

I have found these examples from Hunter Adams to generate a VGA signal using the Raspberry Pi Pico. It is a simple setup that I want to extend. It uses only 3 pins for the RGB colors so it is able to use 8 colors. I added 3 more pins 2 for each channel so that I can generate 64 colors. To make it work I used a lower resolution (640x350) because otherwise it is using too much memory (307.2 KB while the Pico only has 264KB).

Hunter Adams crams two pixels in a byte because he is only using 3 bits per pixel. I'm using 6 bits so I can only get one pixel in there but this way I'm wasting 2 bits in every byte (76.8KB). So I came up with an idea it should be possible to cram 5 pixels in a 32-bit unsigned integer it should be possible to setup the DMA to pass 32-bit unsigned integers to the pio program. So I changed my program to do that but something in my code must be incorrect because it does not work.

Here is my vga.c:

#include <stdio.h>
#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 H_ACTIVE 655   // 640+16-1
#define V_ACTIVE 479   // 480-1
#define RGB_ACTIVE 127 // 640/5-1
#define RED_PIN 0
#define HSYNC 6
#define VSYNC 7
#define TXCOUNT 61440

uint32_t vga_data_array[TXCOUNT];
uint32_t *address_pointer = &vga_data_array[0];

void drawPixel(int x, int y, char color)
{
    if (x > 639)
        x = 639;
    if (x < 0)
        x = 0;
    if (y < 0)
        y = 0;
    if (y > 479)
        y = 479;

    int pixel = ((640 * y) + x);
    // Put 5 pixel values into a single 32-bit integer
    vga_data_array[pixel / 5] |= (color << (24 - ((pixel % 5) * 6)));
}

int main()
{
    stdio_init_all();
    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);

    uint hsync_sm = 0;
    uint vsync_sm = 1;
    uint rgb_sm = 2;

    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, RED_PIN);

    int rgb_chan_0 = 0;
    int rgb_chan_1 = 1;

    dma_channel_config c0 = dma_channel_get_default_config(rgb_chan_0); // default configs
    channel_config_set_transfer_data_size(&c0, DMA_SIZE_32);            // 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 4 bytes.
        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.
    );

    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);
    pio_enable_sm_mask_in_sync(pio, ((1u << hsync_sm) | (1u << vsync_sm) | (1u << rgb_sm)));
    dma_start_channel_mask((1u << rgb_chan_0));

    while (true)
    {
        int index = 0;
        int xcounter = 0;
        int ycounter = 0;

        for (int y = 0; y < 480; y++)
        {
            if (ycounter == 8)
            {
                ycounter = 0;
                index = (index + 1) % 64;
            }
            ycounter += 1;

            for (int x = 0; x < 640; x++)
            {
                if (xcounter == 10)
                {
                    xcounter = 0;
                    index = (index + 1) % 64;
                }

                xcounter += 1;
                drawPixel(x, y, index);
            }
        }
    }
}

I changed the rgb.pio so that is should push out 6 bits 5 times per 32-uint that is pulled from the DMA.

.program rgb

pull block                  ; Pull RGB_ACTIVE from FIFO to OSR (only once)
mov y, osr                  ; Copy value from OSR to y scratch register
.wrap_target

set pins, 0                 ; Zero RGB pins in blanking
mov x, y                    ; Initialize counter variable

wait 1 irq 1 [3]            ; Wait for vsync active mode (starts 5 cycles after execution)

colorout:
    pull block              ; Pull color value 32-bits from DMA
    out pins, 6 [4]         ; Push 6 bits out to pins
    out pins, 6 [4]         ; Push 6 bits out to pins
    out pins, 6 [4]         ; Push 6 bits out to pins
    out pins, 6 [4]         ; Push 6 bits out to pins
    out pins, 6 [2]         ; Push 6 bits out to pins
    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) {
    pio_sm_config c = rgb_program_get_default_config(offset);
    sm_config_set_set_pins(&c, pin, 6);
    sm_config_set_out_pins(&c, pin, 6);
    for(int i=0; i<6;i++) {
        pio_gpio_init(pio, pin+i);
    }
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 6, true);
    pio_sm_init(pio, sm, offset, &c);
}
%}

I think there is an issue with passing the pixel data to the DMA.

I also changed the size of the array from 153600 to 61440 (the amount of 32-bit integers I need to fit my 640x480x6 bits) and changed and the type from:

unsigned char vga_data_array[TXCOUNT];
char * address_pointer = &vga_data_array[0];

To:

uint32_t vga_data_array[TXCOUNT];
uint32_t *address_pointer = &vga_data_array[0];

I changed the RGB_ACTIVE constant from 319 ((horizontal active)/2 - 1 the original code crammed 2 colors in one byte) to 127 (640 pixels / 5 - 1) as I want to put 5 pixels into one 32-bit int.

In the original code drawPixel had:

// Is this pixel stored in the first 3 bits
// of the vga data array index, or the second
// 3 bits? Check, then mask.
if (pixel & 1) {
    vga_data_array[pixel>>1] |= (color << 3);
}
else {
    vga_data_array[pixel>>1] |= (color);
}

I changed that to:

// pixel divided by 5 to get the index I want to put 5 pixels into
// the 32-bit int. And some bit shifting to get the bits in the correct
// position.
vga_data_array[pixel / 5] |= (color << (24 - ((pixel % 5) * 6)));

I changed here is the configuration of the first DMA channel:

dma_channel_config c1 = dma_channel_get_default_config(rgb_chan_1);   // default configs
    channel_config_set_transfer_data_size(&c1, DMA_SIZE_8);              // 8-bit txfers 

To:

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

Here is the original colorout:

colorout:
    pull block              ; Pull color value
    out pins, 3 [4]         ; Push out to pins (first pixel)
    out pins, 3 [2]         ; Push out to pins (next pixel)
    jmp x-- colorout        ; Stay here thru horizontal active mode

I just do not see what I'm doing wrong. The pixel array is correct, I tested it and with another resolution 640x350 it just works. What could be the problem?

Upvotes: 4

Views: 588

Answers (2)

Jalil Faalkhah
Jalil Faalkhah

Reputation: 71

I't seems that no need for 24 in line:

vga_data_array[pixel / 5] |= (color << (24 - ((pixel % 5) * 6)));

because it made mirror the characters.

Upvotes: 0

Smek
Smek

Reputation: 1208

After running it in the Wokwi simulator I found out my pio programs use to many instructions the maximum is 32 per pio and I was using 35. So I reduced the pio program instructions by 3 and now everything works.

In the hsync.pio I removed the second set pins instruction and put a higher delay to the irq 0 instruction:

backporch:
    set pins, 1 [31]    ; High for back porch (32 cycles)
    ; set pins, 1 [12]    ; High for back porch (45 cycles)
    irq 0       [13]     ; Set IRQ to signal end of line (47 cycles)

In the vsync program I removed the set y, 9 and jmp instruction and added a delay of 7 to the set pins instruction of the sync pulse:

; FRONTPORCH
; set y, 9                          ;
frontporch:
    wait 1 irq 0       [2]           ;
    ; jmp y-- frontporch            ;

; SYNC PULSE
set pins, 0      [7]                 ; Set pin low

Upvotes: 0

Related Questions