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