1 /* 2 * Generate a VGA signal using a PIC32 microcontroller. 3 * 4 * Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 21 #include "debug.h" 22 #include "display.h" 23 #include "font.h" 24 #include "init.h" 25 #include "pic32_c.h" 26 #include "utils.h" 27 #include "vga_display.h" 28 #include "viewport.h" 29 30 /* Specific functionality. */ 31 32 #include "devconfig.h" 33 #include "display_config.h" 34 #include "main.h" 35 36 37 38 /* Define DMA channels if not indicated in the build configuration. */ 39 40 /* CPU-based transfers: no channels. */ 41 42 #ifdef TRANSFER_CPU 43 #define SCROLL_XSTEP 1 44 45 /* DMA-based transfers: single channel by default. */ 46 47 #else 48 #define SCROLL_XSTEP LINE_CHANNELS 49 #endif 50 51 52 53 /* Define timers if not indicated in the build configuration. */ 54 55 #ifndef LINE_TIMER 56 #define LINE_TIMER 2 57 #endif 58 59 #ifndef TRANSFER_TIMER 60 #define TRANSFER_TIMER 0 61 #endif 62 63 /* Define different output ports and pins for parallel mode. */ 64 65 #ifndef PARALLEL_MODE 66 #define VGA_OUTPUT PORTB 67 #define OC1_PIN RPA0R 68 #define OC2_PIN RPA1R 69 #define LED_PIN (1 << 3) 70 #else 71 #define VGA_OUTPUT PM_REG(0, PMxDIN) 72 #define OC1_PIN RPB4R 73 #define OC2_PIN RPB5R 74 #define LED_PIN (1 << 2) 75 #endif 76 77 78 79 /* Define the relationship between source images and the screen. */ 80 81 #ifndef SOURCE_HEIGHT 82 #define SOURCE_HEIGHT 256 83 #endif 84 85 #define SOURCE_YSTEP (SOURCE_HEIGHT / LINE_COUNT) 86 87 88 89 /* Framebuffer memory. */ 90 91 static uint8_t framebuffer[FRAME_SIZE * FRAME_COUNT]; 92 93 /* Screen start values for each frame. */ 94 95 uint8_t *screen_starts[FRAME_COUNT]; 96 97 /* Origin details for the viewport. */ 98 99 int xorigins[FRAME_COUNT], yorigins[FRAME_COUNT]; 100 101 102 103 /* Bundled image and font data. */ 104 105 extern uint8_t screendata[]; 106 extern uint32_t screendata_width, screendata_height; 107 extern uint8_t sprite[]; 108 extern uint32_t sprite_width, sprite_height; 109 110 extern uint8_t fontchars[]; 111 extern uint32_t fonttable[]; 112 extern uint32_t fontbase, fontlimit; 113 114 static font_config_t font_config; 115 116 117 118 /* Copy to the store from the display, then blit the image. */ 119 120 static void plot_sprite(uint8_t *background, int x, int y) 121 { 122 display_copy(&display_config, background, 123 sprite_width, sprite_height / SOURCE_YSTEP, 1, 124 x, y, -1, 0); 125 display_copy(&display_config, sprite, 126 sprite_width, sprite_height, SOURCE_YSTEP, 127 x, y, 0x8c, 1); 128 } 129 130 /* Copy to the display from the store, restoring the original background. */ 131 132 static void unplot_sprite(uint8_t *background, int x, int y) 133 { 134 display_copy(&display_config, background, 135 sprite_width, sprite_height / SOURCE_YSTEP, 1, 136 x, y, -1, 1); 137 } 138 139 /* Plot the revealed region at the edge of the screen after scrolling. */ 140 141 static void plot_screen_edge(viewport_t *v, int xorigin, int yorigin, 142 int xstep, int ystep) 143 { 144 /* Determine positions within the image. */ 145 146 int xpos = wrap_value(xorigin, screendata_width); 147 int ypos = wrap_value(yorigin, screendata_height); 148 149 /* The display region is either the left or right edge. */ 150 151 int xdisplay = xstep < 0 ? 0 : screendata_width - xstep; 152 153 /* The source region depends on the origin within the background image. */ 154 155 int xsource = wrap_value(xdisplay + xpos, screendata_width); 156 157 /* The column width is the absolute increment. */ 158 159 int width = xstep < 0 ? -xstep : xstep; 160 161 /* Not all of the column may be available if close to the edge of the 162 image, requiring multiple slices. */ 163 164 int available = screendata_width - xsource; 165 166 while (width) 167 { 168 /* Plot a column in two pieces if the vertical origin is 169 non-zero. The first piece is at (xdisplay, 0) and 170 provides the lower part of the background image displaced 171 upwards (or downwards having wrapped around) on the 172 screen. */ 173 174 display_copy_section(&display_config, screendata, 175 screendata_width, screendata_height, 176 xsource, ypos, 177 width, screendata_height - ypos, 178 v->yscale, 179 xdisplay, 0, 180 -1, 1); 181 182 /* The second column is at (xdisplay, h - ypos) and 183 provides the upper part of the background image displaced 184 downwards (or upwards having wrapped around) on the 185 screen. */ 186 187 if (ypos) 188 display_copy_section(&display_config, screendata, 189 screendata_width, screendata_height, 190 xsource, 0, 191 width, ypos, 192 v->yscale, 193 xdisplay, (screendata_height - ypos) / v->yscale, 194 -1, 1); 195 196 /* Get the next slice of the column. */ 197 198 if (available < width) 199 { 200 width -= available; 201 xsource = 0; 202 xdisplay = wrap_value(xdisplay + available, screendata_width); 203 available = screendata_width; 204 } 205 else 206 width = 0; 207 } 208 } 209 210 /* Move a sprite around on the framebuffer. */ 211 212 static void animate(uint32_t delay) 213 { 214 /* Stored region behind the sprite. */ 215 216 uint8_t background[FRAME_COUNT][(sprite_width * sprite_height) / SOURCE_YSTEP]; 217 218 /* Positions of the stored regions for each frame. */ 219 220 int background_x[FRAME_COUNT], background_y[FRAME_COUNT]; 221 int background_stored[FRAME_COUNT]; 222 223 /* Sprite position. */ 224 225 int x, y; 226 227 /* Scrolling directions. */ 228 229 int dir[] = {1, 0, -1, 0, 1}; 230 int dirindex = 0; 231 232 /* Scroll increments. */ 233 234 int xdir, ydir; 235 236 /* Scrolling viewport. */ 237 238 viewport_t v; 239 240 /* Initialise the backing stores. */ 241 242 int frame; 243 244 for (frame = 0; frame < display_config.frames; frame++) 245 background_stored[frame] = 0; 246 247 /* Initialise the viewport. */ 248 249 init_viewport(&v, &display_config, xorigins, yorigins, 250 SCROLL_XSTEP, SOURCE_YSTEP, plot_screen_edge); 251 252 /* Animation loop. */ 253 254 while (1) 255 { 256 for (y = 0; y < display_config.line_count - sprite_height; y++) 257 { 258 for (x = 0; x < display_config.line_length - sprite_width; x++) 259 { 260 plot_sprite(background[display_config.frame], x, y); 261 262 /* Record the stored background. */ 263 264 background_x[display_config.frame] = x; 265 background_y[display_config.frame] = y; 266 background_stored[display_config.frame] = 1; 267 268 /* Update the display with the frame details. */ 269 270 vga_set_frame(&display_config); 271 wait(delay); 272 273 /* Select the next frame to plot to. */ 274 275 display_select_next_frame(&display_config); 276 277 /* Prepare the frame for updates. */ 278 279 if (background_stored[display_config.frame]) 280 unplot_sprite(background[display_config.frame], 281 background_x[display_config.frame], 282 background_y[display_config.frame]); 283 284 /* Scroll in the indicated direction. */ 285 286 xdir = dir[dirindex]; 287 ydir = dir[dirindex + 1]; 288 289 /* Update the origin if appropriate. */ 290 291 /* Due to the effect of a simple screen start increment in the 292 dual channel configuration, horizontal scrolling involves two 293 pixel increments and thus requires a two-pixel column to be 294 plotted per scrolling increment. */ 295 296 /* For vertically-scaled backgrounds, the full resolution image 297 is traversed by multiples of the scrolling increment. */ 298 299 viewport_update_origin(&v, xdir * SCROLL_XSTEP, 300 ydir * SOURCE_YSTEP); 301 } 302 303 /* Switch direction periodically. */ 304 305 dirindex = wrap_value(dirindex + 1, 4); 306 } 307 } 308 } 309 310 /* Fill the screen with characters. */ 311 312 static void write_chars(void) 313 { 314 const int line_height = 9; 315 int x = 0, y = 0; 316 char c; 317 318 init_font(&font_config, fontchars, fonttable, fontbase, fontlimit); 319 320 while (y + line_height < display_config.line_count) 321 { 322 for (c = (char) font_config.base; c < (char) font_config.limit; c++) 323 { 324 if (x + get_char_definition(&font_config, c)->width > display_config.line_length) 325 { 326 x = 0; y += line_height; 327 } 328 329 if (y + line_height >= display_config.line_count) 330 break; 331 332 x = write_char(&display_config, &font_config, c, x, y, 0xff); 333 } 334 } 335 } 336 337 /* Set up a background. */ 338 339 static void setup(void) 340 { 341 int frame; 342 343 for (frame = 0; frame < display_config.frames; frame++) 344 { 345 /* Obtain the frame. */ 346 347 display_select_frame(&display_config, frame); 348 349 /* Plot the image centred on the screen. */ 350 351 display_copy(&display_config, screendata, 352 screendata_width, screendata_height, 353 SOURCE_YSTEP, 354 (display_config.line_length - screendata_width) / 2, 355 (display_config.line_count - (screendata_height / SOURCE_YSTEP)) / 2, 356 -1, 1); 357 358 /* Write a sequence of characters. */ 359 360 write_chars(); 361 } 362 363 display_select_frame(&display_config, 0); 364 } 365 366 367 368 /* Main program. */ 369 370 void main(void) 371 { 372 init_memory(); 373 init_pins(); 374 init_outputs(); 375 376 unlock_config(); 377 config_oc(); 378 config_uart(); 379 lock_config(); 380 381 #ifndef TRANSFER_CPU 382 init_dma(); 383 #endif 384 385 #ifdef PARALLEL_MODE 386 init_pm(); 387 388 /* Configure parallel master mode. */ 389 390 pm_init(0, 0b10); 391 pm_set_output(0, 1, 0); 392 pm_on(0); 393 #endif 394 395 uart_init(1, FPB, 115200); 396 uart_on(1); 397 398 /* Initialise memory for a double-buffered display. */ 399 400 init_display(&display_config, framebuffer, LINE_LENGTH, LINE_COUNT, 401 FRAME_COUNT, screen_starts); 402 403 /* Initialise VGA output with one or two line channels, configuring a line 404 timer and any transfer timer, with an initiating channel being introduced 405 if a transfer timer is specified. */ 406 407 init_vga_with_timers(&display_config, LINE_CHANNELS, LINE_TIMER, TRANSFER_TIMER); 408 409 /* Configure VGA output transfer to the output register, also configuring 410 output compare units for horizontal and vertical sync. */ 411 412 vga_configure_transfer(VGA_OUTPUT); 413 vga_configure_sync(1, 2); 414 415 interrupts_on(); 416 417 /* Move a sprite around on the screen with a delay between each movement. */ 418 419 setup(); 420 animate(1 << 18); 421 } 422 423 424 425 /* Exception and interrupt handlers. */ 426 427 void exception_handler(void) 428 { 429 blink(1 << 22, PORTA, LED_PIN); 430 } 431 432 void interrupt_handler(void) 433 { 434 vga_interrupt_handler(); 435 } 436 437 438 439 /* Peripheral pin configuration. */ 440 441 void config_oc(void) 442 { 443 /* Map OC1. */ 444 445 REG(OC1_PIN) = 0b0101; 446 447 /* Map OC2. */ 448 449 REG(OC2_PIN) = 0b0101; 450 } 451 452 void config_uart(void) 453 { 454 /* Map U1RX to RPB13. */ 455 456 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 457 458 /* Map U1TX to RPB15. */ 459 460 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 461 462 /* Set RPB13 to input. */ 463 464 SET_REG(TRISB, 1 << 13); 465 }