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