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