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