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 "main.h" 35 #include "vga.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 /* Initialise memory for a multi-frame display. */ 91 92 static Display(display_config, LINE_LENGTH, LINE_COUNT, FRAME_COUNT); 93 94 95 96 /* Bundled image and font data. */ 97 98 extern image_t screendata; 99 extern image_t sprite; 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 /* Plot the revealed region at the edge of the screen after scrolling. */ 110 111 static void plot_screen_edge(viewport_t *v, int xorigin, int yorigin, 112 int xstep, int ystep) 113 { 114 /* Determine positions within the image. */ 115 116 int xpos = wrap_value(xorigin, screendata.width); 117 int ypos = wrap_value(yorigin, screendata.height); 118 119 /* The display region is either the left or right edge. */ 120 121 int xdisplay = xstep < 0 ? 0 : screendata.width - xstep; 122 123 /* The source region depends on the origin within the background image. */ 124 125 int xsource = wrap_value(xdisplay + xpos, screendata.width); 126 127 /* The column width is the absolute increment. */ 128 129 int width = xstep < 0 ? -xstep : xstep; 130 131 /* Not all of the column may be available if close to the edge of the 132 image, requiring multiple slices. */ 133 134 int available = screendata.width - xsource; 135 136 while (width) 137 { 138 /* Plot a column in two pieces if the vertical origin is 139 non-zero. The first piece is at (xdisplay, 0) and 140 provides the lower part of the background image displaced 141 upwards (or downwards having wrapped around) on the 142 screen. */ 143 144 display_copy_section(&display_config, screendata.image, 145 screendata.width, screendata.height, 146 xsource, ypos, 147 width, screendata.height - ypos, 148 v->yscale, 149 xdisplay, 0, 150 -1, 1); 151 152 /* The second column is at (xdisplay, h - ypos) and 153 provides the upper part of the background image displaced 154 downwards (or upwards having wrapped around) on the 155 screen. */ 156 157 if (ypos) 158 display_copy_section(&display_config, screendata.image, 159 screendata.width, screendata.height, 160 xsource, 0, 161 width, ypos, 162 v->yscale, 163 xdisplay, (screendata.height - ypos) / v->yscale, 164 -1, 1); 165 166 /* Get the next slice of the column. */ 167 168 if (available < width) 169 { 170 width -= available; 171 xsource = 0; 172 xdisplay = wrap_value(xdisplay + available, screendata.width); 173 available = screendata.width; 174 } 175 else 176 width = 0; 177 } 178 } 179 180 /* Move a sprite around on the framebuffer. */ 181 182 static void animate(uint32_t delay) 183 { 184 /* Stores of background details, replotted when moving the sprite. */ 185 186 Sprite(s, &sprite, &display_config, SOURCE_YSTEP); 187 188 /* Sprite position. */ 189 190 int x, y; 191 192 /* Scrolling directions. */ 193 194 int dir[] = {1, 0, -1, 0, 1}; 195 int dirindex = 0; 196 197 /* Scroll increments. */ 198 199 int xdir, ydir; 200 201 /* Scrolling viewport. */ 202 203 Viewport(v, &display_config, SCROLL_XSTEP, SOURCE_YSTEP, plot_screen_edge); 204 205 /* Reset the viewport to a well-defined state. */ 206 207 viewport_set_origin(&v, 0, 0); 208 209 /* Animation loop. */ 210 211 while (1) 212 { 213 for (y = 0; y < display_config.line_count - s.image->height; y++) 214 { 215 for (x = 0; x < display_config.line_length - s.image->width; x++) 216 { 217 image_plot_sprite(&s, x, y, 0x8c); 218 219 /* Update the display with the frame details. */ 220 221 vga_set_frame(&display_config); 222 wait(delay); 223 224 /* Select the next frame to plot to. */ 225 226 display_select_next_frame(&display_config); 227 228 /* Prepare the frame for updates. */ 229 230 image_unplot_sprite(&s); 231 232 /* Scroll in the indicated direction. */ 233 234 xdir = dir[dirindex]; 235 ydir = dir[dirindex + 1]; 236 237 /* Update the origin if appropriate. */ 238 239 /* Due to the effect of a simple screen start increment in the 240 dual channel configuration, horizontal scrolling involves two 241 pixel increments and thus requires a two-pixel column to be 242 plotted per scrolling increment. */ 243 244 /* For vertically-scaled backgrounds, the full resolution image 245 is traversed by multiples of the scrolling increment. */ 246 247 viewport_update_origin(&v, xdir * SCROLL_XSTEP, 248 ydir * SOURCE_YSTEP); 249 } 250 251 /* Switch direction periodically. */ 252 253 dirindex = wrap_value(dirindex + 1, 4); 254 } 255 } 256 } 257 258 /* Fill the screen with characters. */ 259 260 static void write_chars(void) 261 { 262 const int line_height = 9; 263 int x = 0, y = 0; 264 char c; 265 266 init_font(&font_config, fontchars, fonttable, fontbase, fontlimit); 267 268 while (y + line_height < display_config.line_count) 269 { 270 for (c = (char) font_config.base; c < (char) font_config.limit; c++) 271 { 272 if (x + get_char_definition(&font_config, c)->width > display_config.line_length) 273 { 274 x = 0; y += line_height; 275 } 276 277 if (y + line_height >= display_config.line_count) 278 break; 279 280 x = write_char(&display_config, &font_config, c, x, y, 0xff); 281 } 282 } 283 } 284 285 /* Set up a background. */ 286 287 static void setup(void) 288 { 289 int frame; 290 291 for (frame = 0; frame < display_config.frames; frame++) 292 { 293 /* Obtain the frame. */ 294 295 display_select_frame(&display_config, frame); 296 297 /* Plot the image centred on the screen. */ 298 299 display_copy(&display_config, screendata.image, 300 screendata.width, screendata.height, 301 SOURCE_YSTEP, 302 (display_config.line_length - screendata.width) / 2, 303 (display_config.line_count - (screendata.height / SOURCE_YSTEP)) / 2, 304 -1, 1); 305 306 /* Write a sequence of characters. */ 307 308 write_chars(); 309 } 310 311 display_select_frame(&display_config, 0); 312 } 313 314 315 316 /* Main program. */ 317 318 void main(void) 319 { 320 init_memory(); 321 init_pins(); 322 init_outputs(); 323 324 unlock_config(); 325 config_oc(); 326 config_uart(); 327 lock_config(); 328 329 #ifndef TRANSFER_CPU 330 init_dma(); 331 #endif 332 333 #ifdef PARALLEL_MODE 334 init_pm(); 335 336 /* Configure parallel master mode. */ 337 338 pm_init(0, 0b10); 339 pm_set_output(0, 1, 0); 340 pm_on(0); 341 #endif 342 343 uart_init(1, FPB, 115200); 344 uart_on(1); 345 346 /* Set the display to a well-defined state. */ 347 348 init_display(&display_config); 349 350 /* Initialise VGA output with one or two line channels, configuring a line 351 timer and any transfer timer, with an initiating channel being introduced 352 if a transfer timer is specified. */ 353 354 init_vga_with_timers(&display_config, LINE_CHANNELS, LINE_TIMER, TRANSFER_TIMER); 355 356 /* Configure VGA output transfer to the output register, also configuring 357 output compare units for horizontal and vertical sync. */ 358 359 vga_configure_transfer(VGA_OUTPUT); 360 vga_configure_sync(1, 2); 361 362 interrupts_on(); 363 364 /* Move a sprite around on the screen with a delay between each movement. */ 365 366 setup(); 367 animate(1 << 18); 368 } 369 370 371 372 /* Exception and interrupt handlers. */ 373 374 void exception_handler(void) 375 { 376 blink(1 << 22, PORTA, LED_PIN); 377 } 378 379 void interrupt_handler(void) 380 { 381 vga_interrupt_handler(); 382 } 383 384 385 386 /* Peripheral pin configuration. */ 387 388 void config_oc(void) 389 { 390 /* Map OC1. */ 391 392 REG(OC1_PIN) = 0b0101; 393 394 /* Map OC2. */ 395 396 REG(OC2_PIN) = 0b0101; 397 } 398 399 void config_uart(void) 400 { 401 /* Map U1RX to RPB13. */ 402 403 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 404 405 /* Map U1TX to RPB15. */ 406 407 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 408 409 /* Set RPB13 to input. */ 410 411 SET_REG(TRISB, 1 << 13); 412 }