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