forked from breakintoprogram/pico-mposite
-
Notifications
You must be signed in to change notification settings - Fork 2
/
cvideo.c
350 lines (296 loc) · 12.1 KB
/
cvideo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
//
// Title: Pico-mposite Video Output
// Description: The composite video stuff
// Author: Dean Belfield
// Created: 26/01/2021
// Last Updated: 25/02/2022
//
// Modinfo:
// 15/02/2021: Border buffers now have horizontal sync pulse set correctly
// Decreased RAM usage by updating the image buffer scanline on the fly during the horizontal interrupt
// Fixed logic error in cvideo_dma_handler; initial memcpy done twice
// 31/01/2022: Refactored to use less memory
// Split the video generation into two state machines; sync and data
// 01/02/2022: Added a handful of graphics primitives
// 02/02/2022: Split main loop out into main.c
// 04/02/2022: Added set_border
// 05/02/2022: Added support for colour, fixed bug in video generation
// 20/02/2022: Bitmap is now dynamically allocated; added two higher resolution video modes
// 25/02/2022: Lengthened HSYNC to 12us
#include <stdlib.h>
#include "memory.h"
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "charset.h" // The character set
#include "cvideo.h"
#include "graphics.h"
#include "cvideo_sync.pio.h" // The assembled PIO code
#include "cvideo_data.pio.h"
PIO pio_0; // The PIO that this uses
uint offset_0; // Program offsets
uint offset_1;
uint dma_channel_0; // DMA channel for transferring sync data to PIO
uint dma_channel_1; // DMA channel for transferring pixel data data to PIO
uint vline; // Current PAL(ish) video line being processed
uint bline; // Line in the bitmap to fetch
uint vblank_count; // Vblank counter
unsigned char * bitmap; // Bitmap buffer
int width = 320; // Bitmap dimensions
int height = 256;
/*
* The sync tables consist of 32 entries, each one corresponding to a 2us slice of the 64us
* horizontal sync. The value 0x00 is reserved as a control byte for the horizontal sync;
* cvideo_sync will not write to the GPIO for that block of 0x00's.
*
* All sync pulses are active low
*/
// Horizontal sync with gap for pixel data
//
unsigned short hsync[32] = {
HSLO, HSLO, HSHI, HSHI, HSHI, HSHI, BORD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, BORD, BORD, BORD, BORD,
};
// Horizontal sync for top and bottom borders
//
unsigned short border[32] = {
HSLO, HSLO, HSHI, HSHI, HSHI, HSHI, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD,
BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD, BORD,
};
#if opt_colour!=2
// Vertical sync (long/long)
//
unsigned short vsync_ll[32] = {
VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse
VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse
};
// Vertical sync (short/short)
//
unsigned short vsync_ss[32] = {
VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse
VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse
};
// Vertical sync (long/short)
//
unsigned short vsync_ls[32] = {
VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSHI, // Long sync pulse
VSLO, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, VSHI, // Short sync pulse
};
#else
unsigned short vsync_ll[32] = {
VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO,
VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO, VSLO,
};
#define vsync_ss border
#define vsync_ls border
#endif
/*
* The main routine sets up the whole shebang
*/
int initialise_cvideo(void) {
for(short i = 0; i < gpio_count; i++) {
gpio_init( i + gpio_base);
gpio_set_dir( i + gpio_base, GPIO_OUT);
}
pio_0 = pio0; // Assign the PIO
// Load up the PIO programs
//
offset_0 = pio_add_program(pio_0, &cvideo_sync_program);
offset_1 = pio_add_program(pio_0, &cvideo_data_program);
dma_channel_0 = dma_claim_unused_channel(true); // Claim a DMA channel for the sync
dma_channel_1 = dma_claim_unused_channel(true); // And one for the pixel data
vline = 1; // Initialise the video scan line counter to 1
bline = 0; // And the index into the bitmap pixel buffer to 0
vblank_count = 0; // And the vblank counter
// Initialise the first PIO (video sync)
//
pio_sm_set_enabled(pio_0, sm_sync, false); // Disable the PIO state machine
pio_sm_clear_fifos(pio_0, sm_sync); // Clear the PIO FIFO buffers
cvideo_sync_initialise_pio( // Initialise the PIO (function in cvideo.pio)
pio_0, // The PIO to attach this state machine to
sm_sync, // The state machine number
offset_0, // And offset
gpio_base, // Start pin in the GPIO
gpio_count, // Number of pins
piofreq_0 // State machine clock frequency
);
cvideo_configure_pio_dma( // Configure the DMA for Sync
pio_0, // The PIO to attach this DMA to
sm_sync, // The state machine number
dma_channel_0, // The DMA channel
DMA_SIZE_16, // Size of each transfer
32, // Number of transfers to make
cvideo_dma_handler // The DMA handler
);
pio_sm_set_enabled(pio_0, sm_sync, true); // Enable the PIO state machine
bitmap = malloc(width * height); // Allocate the bitmap memory
// Initialise the second PIO (pixel data)
//
cvideo_data_initialise_pio(
pio_0,
sm_data,
offset_1,
gpio_base,
gpio_data_count,
piofreq_1_320
);
// Initialise the DMA for data (ie pixels)
//
cvideo_configure_pio_dma(
pio_0,
sm_data,
dma_channel_1, // On DMA channel 1
DMA_SIZE_8, // Size of each transfer
width, // The bitmap width - number of transfers to make
NULL // But there is no DMA interrupt for the pixel data
);
pio_sm_set_enabled(pio_0, sm_data, true); // Enable the PIO state machine
irq_set_exclusive_handler( // Set up the PIO IRQ handler
PIO0_IRQ_0, // The IRQ #
cvideo_pio_handler // And handler routine
);
pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS; // Just for IRQ 0 (triggered by irq set 0 in PIO)
irq_set_enabled(PIO0_IRQ_0, true); // Enable it
set_border(0); // Set the border colour
cls(0); // Clear the screen
return 0;
}
// Set the graphics mode
// mode - The graphics mode (0 = 256x192, 1 = 320 x 192, 2 = 640 x 192)
//
int set_mode(int mode) {
double dfreq;
wait_vblank();
switch(mode) { // Get the video mode
case 1:
width = 320; // Set screen width and
dfreq = piofreq_1_320; // pixel dot frequency accordingly
break;
case 2:
width = 640;
dfreq = piofreq_1_640;
break;
default:
width = 256;
dfreq = piofreq_1_256;
break;
}
if(bitmap != NULL) {
free(bitmap);
}
bitmap = malloc(width * height); // Allocate the bitmap memory
cvideo_configure_pio_dma( // Reconfigure the DMA
pio_0,
sm_data,
dma_channel_1, // On DMA channel 1
DMA_SIZE_8, // Size of each transfer
width, // The bitmap width
NULL // But there is no DMA interrupt for the pixel data
);
pio_0->sm[sm_data].clkdiv = (uint32_t) (dfreq * (1 << 16));
return 0;
}
// Set the border colour
// - colour: Border colour
//
void set_border(unsigned char colour) {
if(colour > colour_max) {
return;
}
unsigned short c = BORD | (colour_base + colour);
for(int i = 6; i <32; i++) { // Skip the first three hsync values
if((hsync[i] & BORD) == BORD) { // If the border bit is set in the hsync
hsync[i] = c; // Then write out the new colour (with the BORD bit set)
}
border[i] = c; // We can just write the values out to the border table
}
}
// Wait for vblank
//
void wait_vblank(void) {
uint c = vblank_count; // Get the current vblank count
while(c == vblank_count) { // Wait until it changes
sleep_us(4); // Need to sleep for a minimum of 4us
}
}
// The PIO interrupt handler
// This sets up the DMA for cvideo_data with pixel data and is triggered by the irq set 0
// instruction at the end of the PIO
//
void cvideo_pio_handler(void) {
if(bline >= height) {
bline = 0;
}
dma_channel_set_read_addr(dma_channel_1, &bitmap[width * bline++], true); // Line up the next block of pixels
hw_set_bits(&pio0->irq, 1u); // Reset the IRQ
}
// The DMA interrupt handler
// This feeds the state machine cvideo_sync with data for the PAL(ish) video signal
//
void cvideo_dma_handler(void) {
// Switch condition on the vertical scanline number (vline)
// Each statement does a dma_channel_set_read_addr to point the PIO to the next data to output
//
switch(vline) {
// First deal with the vertical sync scanlines
//
case 1 ... 2:
dma_channel_set_read_addr(dma_channel_0, vsync_ll, true);
break;
case 3:
dma_channel_set_read_addr(dma_channel_0, vsync_ls, true);
break;
case 4 ... 5:
case 310 ... 312:
dma_channel_set_read_addr(dma_channel_0, vsync_ss, true);
break;
// Then the border scanlines
//
case 6 ... 36:
case 293 ... 309:
dma_channel_set_read_addr(dma_channel_0, border, true);
break;
// Now point the dma at the first buffer for the pixel data,
// and preload the data for the next scanline
//
default:
dma_channel_set_read_addr(dma_channel_0, hsync, true);
break;
}
// Increment and wrap the counters
//
if(vline++ >= 312) { // If we've gone past the bottom scanline then
vline = 1; // Reset the scanline counter
vblank_count++;
}
// Finally, clear the interrupt request ready for the next horizontal sync interrupt
//
dma_hw->ints0 = 1u << dma_channel_0;
}
// Configure the PIO DMA
// Parameters:
// - pio: The PIO to attach this to
// - sm: The state machine number
// - dma_channel: The DMA channel
// - transfer_size: Size of each DMA bus transfer (DMA_SIZE_8, DMA_SIZE_16 or DMA_SIZE_32)
// - buffer_size_words: Number of bytes to transfer
// - handler: Address of the interrupt handler, or NULL for no interrupts
//
void cvideo_configure_pio_dma(PIO pio, uint sm, uint dma_channel, uint transfer_size, size_t buffer_size, irq_handler_t handler) {
dma_channel_config c = dma_channel_get_default_config(dma_channel);
channel_config_set_transfer_data_size(&c, transfer_size);
channel_config_set_read_increment(&c, true);
channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true));
dma_channel_configure(dma_channel, &c,
&pio->txf[sm], // Destination pointer
NULL, // Source pointer
buffer_size, // Size of buffer
true // Start flag (true = start immediately)
);
if(handler != NULL) {
dma_channel_set_irq0_enabled(dma_channel, true);
irq_set_exclusive_handler(DMA_IRQ_0, handler);
irq_set_enabled(DMA_IRQ_0, true);
}
}