Reputation: 1208
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
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
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