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 /* Framebuffer memory. */ 85 86 static uint8_t framebuffer[FRAME_SIZE * FRAME_COUNT]; 87 88 89 90 /* Bundled image and font data. */ 91 92 extern uint8_t screendata[]; 93 extern uint32_t screendata_width, screendata_height; 94 extern uint8_t sprite[]; 95 extern uint32_t sprite_width, sprite_height; 96 97 extern uint8_t fontchars[]; 98 extern uint32_t fonttable[]; 99 extern uint32_t fontbase, fontlimit; 100 101 static font_config_t font_config; 102 103 104 105 /* Busy wait. */ 106 107 static void wait(uint32_t delay) 108 { 109 uint32_t counter = delay; 110 111 if (!delay) return; 112 while (counter--) __asm__(""); /* retain loop */ 113 } 114 115 /* Blink an attached LED with delays implemented using a loop. */ 116 117 static void blink(uint32_t delay, uint32_t port, uint32_t pins) 118 { 119 /* Clear outputs (LED). */ 120 121 CLR_REG(port, pins); 122 123 while (1) 124 { 125 wait(delay); 126 127 /* Invert outputs (LED). */ 128 129 INV_REG(port, pins); 130 } 131 } 132 133 /* Copy to the store from the display, then blit the image. */ 134 135 static void plot_sprite(uint8_t *background, int x, int y) 136 { 137 copy_display(&display_config, background, 138 sprite_width, sprite_height, 139 x, y, -1, 0); 140 copy_display(&display_config, sprite, 141 sprite_width, sprite_height, 142 x, y, 0x8c, 1); 143 } 144 145 /* Copy to the display from the store, restoring the original background. */ 146 147 static void unplot_sprite(uint8_t *background, int x, int y) 148 { 149 copy_display(&display_config, background, 150 sprite_width, sprite_height, 151 x, y, -1, 1); 152 } 153 154 /* Plot the revealed region at the edge of the screen after scrolling. */ 155 156 static void plot_screen_edge(int xorigin, int yorigin, int xstep) 157 { 158 /* The display region is either the left or right edge. */ 159 160 int xdisplay = xstep < 0 ? 0 : screendata_width - xstep; 161 162 /* The source region depends on the origin within the background image. */ 163 164 int xsource = (xdisplay + xorigin) % screendata_width; 165 166 /* The column width is the absolute increment. */ 167 168 int width = xstep < 0 ? -xstep : xstep; 169 170 /* Plot a column in two pieces if the vertical origin is 171 non-zero. The first piece is at (xdisplay, 0) and 172 provides the lower part of the background image displaced 173 upwards (or downwards having wrapped around) on the 174 screen. */ 175 176 copy_display_section(&display_config, screendata, 177 screendata_width, screendata_height, 178 xsource, yorigin, 179 width, screendata_height - yorigin, 180 xdisplay, 0, 181 -1, 1); 182 183 /* The second column is at (xdisplay, h - yorigin) and 184 provides the upper part of the background image displaced 185 downwards (or upwards having wrapped around) on the 186 screen. */ 187 188 if (yorigin) 189 copy_display_section(&display_config, screendata, 190 screendata_width, screendata_height, 191 xsource, 0, 192 width, yorigin, 193 xdisplay, screendata_height - yorigin, 194 -1, 1); 195 } 196 197 /* Wrap a value within the bounds [0, limit) provided value is already within 198 the bounds [-limit, limit * 2). */ 199 200 static int wrap_value(int value, int limit) 201 { 202 if (value < 0) 203 return value + limit; 204 else if (value >= limit) 205 return value - limit; 206 else 207 return value; 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[sprite_width * sprite_height]; 217 218 /* Sprite position. */ 219 220 int x, y; 221 222 /* Scrolling directions. */ 223 224 int dir[] = {1, 0, -1, 0, 1}; 225 int dirindex = 0; 226 227 /* Scrolling position. */ 228 229 int xorigin = 0, yorigin = 0; 230 231 /* Scroll increments and replotted column details. */ 232 233 int xdir, ydir, xstep; 234 235 while (1) 236 { 237 for (y = 0; y < screendata_height - sprite_height; y++) 238 { 239 for (x = 0; x < screendata_width - sprite_width; x++) 240 { 241 plot_sprite(background, x, y); 242 243 /* Update the display with the frame details. */ 244 245 vga_set_frame(&display_config); 246 wait(delay); 247 248 unplot_sprite(background, x, y); 249 250 /* Scroll in the indicated direction. */ 251 252 xdir = dir[dirindex]; 253 ydir = dir[dirindex + 1]; 254 255 scroll_display(&display_config, xdir, ydir); 256 257 /* Update the vertical origin if appropriate. */ 258 259 if (ydir) 260 yorigin = wrap_value(yorigin + ydir, screendata_height); 261 262 /* For horizontal scrolling, plot the exposed column at the left 263 (if scrolling left) or at the right (if scrolling right). */ 264 265 if (xdir) 266 { 267 /* Due to the effect of a simple screen start increment in 268 the dual channel configuration, horizontal scrolling 269 involves two pixel increments and thus requires a two- 270 pixel column to be plotted. */ 271 272 xstep = xdir * SCROLL_XSTEP; 273 274 /* Determine the location of the column to be plotted. */ 275 276 xorigin = wrap_value(xorigin + xstep, screendata_width); 277 278 /* Plot either at the left or right edge. */ 279 280 plot_screen_edge(xorigin, yorigin, xstep); 281 } 282 } 283 284 /* Switch direction periodically. */ 285 286 dirindex = wrap_value(dirindex + 1, 4); 287 } 288 } 289 } 290 291 /* Fill the screen with characters. */ 292 293 static void write_chars(void) 294 { 295 int x = 0, y = 0; 296 char c; 297 298 font_config.chars = (char_definition_t *) fontchars; 299 font_config.table = fonttable; 300 font_config.base = fontbase; 301 font_config.limit = fontlimit; 302 303 while (y < display_config.line_count) 304 for (c = (char) font_config.base; c < (char) font_config.limit; c++) 305 { 306 x = write_char(&display_config, &font_config, c, x, y, 0xff); 307 308 if (x > display_config.line_length) 309 { 310 x = 0; y += 9; 311 } 312 } 313 } 314 315 316 317 /* Main program. */ 318 319 void main(void) 320 { 321 init_memory(); 322 init_pins(); 323 init_outputs(); 324 325 unlock_config(); 326 config_oc(); 327 config_uart(); 328 lock_config(); 329 330 #ifndef TRANSFER_CPU 331 init_dma(); 332 #endif 333 334 #ifdef PARALLEL_MODE 335 init_pm(); 336 337 /* Configure parallel master mode. */ 338 339 pm_init(0, 0b10); 340 pm_set_output(0, 1, 0); 341 pm_on(0); 342 #endif 343 344 uart_init(1, FPB, 115200); 345 uart_on(1); 346 347 /* Initialise memory for a double-buffered display. */ 348 349 init_display(&display_config, framebuffer, LINE_LENGTH, LINE_COUNT, FRAME_COUNT); 350 351 /* Initialise VGA output with one or two line channels, configuring a line 352 timer and any transfer timer, with an initiating channel being introduced 353 if a transfer timer is specified. */ 354 355 init_vga_with_timers(&display_config, LINE_CHANNELS, LINE_TIMER, TRANSFER_TIMER); 356 357 /* Configure VGA output transfer to the output register, also configuring 358 output compare units for horizontal and vertical sync. */ 359 360 vga_configure_transfer(VGA_OUTPUT); 361 vga_configure_sync(1, 2); 362 363 interrupts_on(); 364 365 /* Plot the image centred on the screen. */ 366 367 copy_display(&display_config, screendata, screendata_width, screendata_height, 368 (display_config.line_length - screendata_width) / 2, 369 (display_config.line_count - screendata_height) / 2, -1, 1); 370 371 /* Write a sequence of characters. */ 372 373 write_chars(); 374 375 /* Move a sprite around on the screen with a delay between each movement. */ 376 377 animate(1 << 18); 378 } 379 380 381 382 /* Exception and interrupt handlers. */ 383 384 void exception_handler(void) 385 { 386 blink(1 << 22, PORTA, LED_PIN); 387 } 388 389 void interrupt_handler(void) 390 { 391 vga_interrupt_handler(); 392 } 393 394 395 396 /* Peripheral pin configuration. */ 397 398 void config_oc(void) 399 { 400 /* Map OC1. */ 401 402 REG(OC1_PIN) = 0b0101; 403 404 /* Map OC2. */ 405 406 REG(OC2_PIN) = 0b0101; 407 } 408 409 void config_uart(void) 410 { 411 /* Map U1RX to RPB13. */ 412 413 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 414 415 /* Map U1TX to RPB15. */ 416 417 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 418 419 /* Set RPB13 to input. */ 420 421 SET_REG(TRISB, 1 << 13); 422 }