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 "pic32_c.h" 22 #include "init.h" 23 #include "debug.h" 24 25 /* Specific functionality. */ 26 27 #include "devconfig.h" 28 #include "display.h" 29 #include "display_config.h" 30 #include "font.h" 31 #include "main.h" 32 #include "vga.h" 33 #include "vga_display.h" 34 35 36 37 /* Define DMA channels if not indicated in the build configuration. */ 38 39 /* CPU-based transfers: no channels. */ 40 41 #ifdef TRANSFER_CPU 42 #define LINE_CHANNELS 0 43 #define SCROLL_XSTEP 1 44 45 /* DMA-based transfers: single channel by default. */ 46 47 #else 48 49 #ifndef LINE_CHANNELS 50 #define LINE_CHANNELS 1 51 #endif 52 53 #define SCROLL_XSTEP LINE_CHANNELS 54 #endif 55 56 57 58 /* Define timers if not indicated in the build configuration. */ 59 60 #ifndef LINE_TIMER 61 #define LINE_TIMER 2 62 #endif 63 64 #ifndef TRANSFER_TIMER 65 #define TRANSFER_TIMER 0 66 #endif 67 68 /* Define different output ports and pins for parallel mode. */ 69 70 #ifndef PARALLEL_MODE 71 #define VGA_OUTPUT PORTB 72 #define OC1_PIN RPA0R 73 #define OC2_PIN RPA1R 74 #define LED_PIN (1 << 3) 75 #else 76 #define VGA_OUTPUT PM_REG(0, PMxDIN) 77 #define OC1_PIN RPB4R 78 #define OC2_PIN RPB5R 79 #define LED_PIN (1 << 2) 80 #endif 81 82 83 84 /* Define the relationship between source images and the screen. */ 85 86 #ifndef SOURCE_HEIGHT 87 #define SOURCE_HEIGHT 256 88 #endif 89 90 #define SOURCE_YSTEP (SOURCE_HEIGHT / LINE_COUNT) 91 92 93 94 /* Framebuffer memory. */ 95 96 static uint8_t framebuffer[FRAME_SIZE * FRAME_COUNT]; 97 98 99 100 /* Bundled image and font data. */ 101 102 extern uint8_t screendata[]; 103 extern uint32_t screendata_width, screendata_height; 104 extern uint8_t sprite[]; 105 extern uint32_t sprite_width, sprite_height; 106 107 extern uint8_t fontchars[]; 108 extern uint32_t fonttable[]; 109 extern uint32_t fontbase, fontlimit; 110 111 static font_config_t font_config; 112 113 114 115 /* Busy wait. */ 116 117 static void wait(uint32_t delay) 118 { 119 uint32_t counter = delay; 120 121 if (!delay) return; 122 while (counter--) __asm__(""); /* retain loop */ 123 } 124 125 /* Blink an attached LED with delays implemented using a loop. */ 126 127 static void blink(uint32_t delay, uint32_t port, uint32_t pins) 128 { 129 /* Clear outputs (LED). */ 130 131 CLR_REG(port, pins); 132 133 while (1) 134 { 135 wait(delay); 136 137 /* Invert outputs (LED). */ 138 139 INV_REG(port, pins); 140 } 141 } 142 143 /* Copy to the store from the display, then blit the image. */ 144 145 static void plot_sprite(uint8_t *background, int x, int y) 146 { 147 copy_display(&display_config, background, 148 sprite_width, sprite_height / SOURCE_YSTEP, 1, 149 x, y, -1, 0); 150 copy_display(&display_config, sprite, 151 sprite_width, sprite_height, SOURCE_YSTEP, 152 x, y, 0x8c, 1); 153 } 154 155 /* Copy to the display from the store, restoring the original background. */ 156 157 static void unplot_sprite(uint8_t *background, int x, int y) 158 { 159 copy_display(&display_config, background, 160 sprite_width, sprite_height / SOURCE_YSTEP, 1, 161 x, y, -1, 1); 162 } 163 164 /* Wrap a value within the bounds [0, limit). */ 165 166 static int wrap_value(int value, int limit) 167 { 168 if (value < 0) 169 return limit - (-value % limit); 170 else if (value >= limit) 171 return (value - limit) % limit; 172 else 173 return value; 174 } 175 176 /* Plot the revealed region at the edge of the screen after scrolling. */ 177 178 static void plot_screen_edge(int xorigin, int yorigin, int xstep) 179 { 180 /* Determine positions within the image. */ 181 182 int xpos = wrap_value(xorigin, screendata_width); 183 int ypos = wrap_value(yorigin, screendata_height); 184 185 /* The display region is either the left or right edge. */ 186 187 int xdisplay = xstep < 0 ? 0 : screendata_width - xstep; 188 189 /* The source region depends on the origin within the background image. */ 190 191 int xsource = wrap_value(xdisplay + xpos, screendata_width); 192 193 /* The column width is the absolute increment. */ 194 195 int width = xstep < 0 ? -xstep : xstep; 196 197 /* Not all of the column may be available if close to the edge of the 198 image, requiring multiple slices. */ 199 200 int available = screendata_width - xsource; 201 202 while (width) 203 { 204 /* Plot a column in two pieces if the vertical origin is 205 non-zero. The first piece is at (xdisplay, 0) and 206 provides the lower part of the background image displaced 207 upwards (or downwards having wrapped around) on the 208 screen. */ 209 210 copy_display_section(&display_config, screendata, 211 screendata_width, screendata_height, 212 xsource, ypos, 213 width, screendata_height - ypos, 214 SOURCE_YSTEP, 215 xdisplay, 0, 216 -1, 1); 217 218 /* The second column is at (xdisplay, h - ypos) and 219 provides the upper part of the background image displaced 220 downwards (or upwards having wrapped around) on the 221 screen. */ 222 223 if (ypos) 224 copy_display_section(&display_config, screendata, 225 screendata_width, screendata_height, 226 xsource, 0, 227 width, ypos, 228 SOURCE_YSTEP, 229 xdisplay, (screendata_height - ypos) / SOURCE_YSTEP, 230 -1, 1); 231 232 /* Get the next slice of the column. */ 233 234 if (available < width) 235 { 236 width -= available; 237 xsource = 0; 238 xdisplay = wrap_value(xdisplay + available, screendata_width); 239 available = screendata_width; 240 } 241 else 242 width = 0; 243 } 244 } 245 246 /* Move a sprite around on the framebuffer. */ 247 248 static void animate(uint32_t delay) 249 { 250 /* Stored region behind the sprite. */ 251 252 uint8_t background[FRAME_COUNT][(sprite_width * sprite_height) / SOURCE_YSTEP]; 253 254 /* Positions of the stored regions for each frame. */ 255 256 int background_x[FRAME_COUNT], background_y[FRAME_COUNT]; 257 int background_stored[FRAME_COUNT]; 258 259 /* Screen start values for each frame. */ 260 261 uint32_t frame_offset[FRAME_COUNT]; 262 263 /* Sprite position. */ 264 265 int x, y; 266 267 /* Scrolling directions. */ 268 269 int dir[] = {1, 0, -1, 0, 1}; 270 int dirindex = 0; 271 272 /* Scrolling position. */ 273 274 int xorigin = 0, yorigin = 0; 275 276 /* Scrolling positions for each frame. */ 277 278 int xorigins[FRAME_COUNT], yorigins[FRAME_COUNT]; 279 280 /* Scroll increments and replotted column details. */ 281 282 int xdir, ydir, xstep, ystep; 283 284 /* Current frame being accessed. */ 285 286 int frame; 287 288 for (frame = 0; frame < display_config.frames; frame++) 289 { 290 background_stored[frame] = 0; 291 frame_offset[frame] = 0; 292 xorigins[frame] = xorigin; 293 yorigins[frame] = yorigin; 294 } 295 296 frame = display_config.frame; 297 298 /* Animation loop. */ 299 300 while (1) 301 { 302 for (y = 0; y < display_config.line_count - sprite_height; y++) 303 { 304 for (x = 0; x < display_config.line_length - sprite_width; x++) 305 { 306 plot_sprite(background[frame], x, y); 307 308 /* Record the stored background. */ 309 310 background_x[frame] = x; 311 background_y[frame] = y; 312 background_stored[frame] = 1; 313 314 /* Update the display with the frame details. */ 315 316 vga_set_frame(&display_config); 317 wait(delay); 318 319 /* Select the next frame to plot to. */ 320 321 frame = wrap_value(frame + 1, display_config.frames); 322 select_frame(&display_config, frame, frame_offset[frame]); 323 324 /* Prepare the frame for updates. */ 325 326 if (background_stored[frame]) 327 unplot_sprite(background[frame], background_x[frame], background_y[frame]); 328 329 /* Scroll in the indicated direction. */ 330 331 xdir = dir[dirindex]; 332 ydir = dir[dirindex + 1]; 333 334 /* Update the origin if appropriate. */ 335 336 /* Due to the effect of a simple screen start increment in the 337 dual channel configuration, horizontal scrolling involves two 338 pixel increments and thus requires a two-pixel column to be 339 plotted per scrolling increment. */ 340 341 if (xdir) 342 xorigin += xdir * SCROLL_XSTEP; 343 344 /* For vertically-scaled backgrounds, the full resolution image 345 is traversed by multiples of the scrolling increment. */ 346 347 if (ydir) 348 yorigin += ydir * SOURCE_YSTEP; 349 350 /* Determine the magnitude of the scrolling required from this 351 frame to the current origin. */ 352 353 xstep = xorigin - xorigins[frame]; 354 ystep = yorigin - yorigins[frame]; 355 356 /* Scroll the frame. */ 357 358 scroll_display(&display_config, xstep / SCROLL_XSTEP, 359 ystep / SOURCE_YSTEP); 360 361 /* Record the new screen start offset. */ 362 363 frame_offset[frame] = get_start_offset(&display_config); 364 365 /* For horizontal scrolling, plot the exposed column at the left 366 (if scrolling left) or at the right (if scrolling right). */ 367 368 if (xstep) 369 plot_screen_edge(xorigin, yorigin, xstep); 370 371 /* Record the origin for this frame. */ 372 373 xorigins[frame] = xorigin; 374 yorigins[frame] = yorigin; 375 } 376 377 /* Switch direction periodically. */ 378 379 dirindex = wrap_value(dirindex + 1, 4); 380 } 381 } 382 } 383 384 /* Fill the screen with characters. */ 385 386 static void write_chars(void) 387 { 388 int x = 0, y = 0; 389 char c; 390 391 font_config.chars = (char_definition_t *) fontchars; 392 font_config.table = fonttable; 393 font_config.base = fontbase; 394 font_config.limit = fontlimit; 395 396 while (y < display_config.line_count) 397 for (c = (char) font_config.base; c < (char) font_config.limit; c++) 398 { 399 x = write_char(&display_config, &font_config, c, x, y, 0xff); 400 401 if (x > display_config.line_length) 402 { 403 x = 0; y += 9; 404 } 405 } 406 } 407 408 /* Set up a background. */ 409 410 static void setup(void) 411 { 412 int frame; 413 414 for (frame = 0; frame < display_config.frames; frame++) 415 { 416 /* Obtain the frame. */ 417 418 select_frame(&display_config, frame, 0); 419 420 /* Plot the image centred on the screen. */ 421 422 copy_display(&display_config, screendata, 423 screendata_width, screendata_height, 424 SOURCE_YSTEP, 425 (display_config.line_length - screendata_width) / 2, 426 (display_config.line_count - (screendata_height / SOURCE_YSTEP)) / 2, 427 -1, 1); 428 429 /* Write a sequence of characters. */ 430 431 write_chars(); 432 } 433 434 select_frame(&display_config, 0, 0); 435 } 436 437 438 439 /* Main program. */ 440 441 void main(void) 442 { 443 init_memory(); 444 init_pins(); 445 init_outputs(); 446 447 unlock_config(); 448 config_oc(); 449 config_uart(); 450 lock_config(); 451 452 #ifndef TRANSFER_CPU 453 init_dma(); 454 #endif 455 456 #ifdef PARALLEL_MODE 457 init_pm(); 458 459 /* Configure parallel master mode. */ 460 461 pm_init(0, 0b10); 462 pm_set_output(0, 1, 0); 463 pm_on(0); 464 #endif 465 466 uart_init(1, FPB, 115200); 467 uart_on(1); 468 469 /* Initialise memory for a double-buffered display. */ 470 471 init_display(&display_config, framebuffer, LINE_LENGTH, LINE_COUNT, FRAME_COUNT); 472 473 /* Initialise VGA output with one or two line channels, configuring a line 474 timer and any transfer timer, with an initiating channel being introduced 475 if a transfer timer is specified. */ 476 477 init_vga_with_timers(&display_config, LINE_CHANNELS, LINE_TIMER, TRANSFER_TIMER); 478 479 /* Configure VGA output transfer to the output register, also configuring 480 output compare units for horizontal and vertical sync. */ 481 482 vga_configure_transfer(VGA_OUTPUT); 483 vga_configure_sync(1, 2); 484 485 interrupts_on(); 486 487 /* Move a sprite around on the screen with a delay between each movement. */ 488 489 setup(); 490 animate(1 << 18); 491 } 492 493 494 495 /* Exception and interrupt handlers. */ 496 497 void exception_handler(void) 498 { 499 blink(1 << 22, PORTA, LED_PIN); 500 } 501 502 void interrupt_handler(void) 503 { 504 vga_interrupt_handler(); 505 } 506 507 508 509 /* Peripheral pin configuration. */ 510 511 void config_oc(void) 512 { 513 /* Map OC1. */ 514 515 REG(OC1_PIN) = 0b0101; 516 517 /* Map OC2. */ 518 519 REG(OC2_PIN) = 0b0101; 520 } 521 522 void config_uart(void) 523 { 524 /* Map U1RX to RPB13. */ 525 526 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 527 528 /* Map U1TX to RPB15. */ 529 530 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 531 532 /* Set RPB13 to input. */ 533 534 SET_REG(TRISB, 1 << 13); 535 }