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 #include "pic32_c.h" 21 #include "init.h" 22 #include "vga_display.h" 23 24 25 26 /* Display state. */ 27 28 vga_display_t vga_display; 29 30 /* Pixel data. */ 31 32 static const int ZERO_LENGTH = 1; 33 static const uint8_t zerodata[1] = {0}; 34 35 36 37 /* Initialise the state machine. */ 38 39 void init_vga(display_config_t *display_config, int line_channels, 40 int initiating_int_num) 41 { 42 /* Display parameters. */ 43 44 vga_display.display_config = display_config; 45 vga_display.line_channels = line_channels; 46 vga_display.initiating_int_num = initiating_int_num; 47 48 /* Initial state. */ 49 50 vga_display.state_handler = vbp_active; 51 vga_display.line = 0; 52 } 53 54 55 56 /* Configure DMA channels for the transfer of pixel data. */ 57 58 void vga_configure_transfer(int transfer_int_num, uint32_t output) 59 { 60 int initiating_channel = vga_display.initiating_int_num >= 0; 61 int dual_channel = vga_display.line_channels == 2; 62 int channel = 0; 63 64 /* Where dual line channels are involved, put the first before any 65 initiating channel, chaining it to such a channel. */ 66 67 if (dual_channel) 68 { 69 vga_configure_line_channel(channel++, transfer_int_num, 70 initiating_channel ? dma_chain_next : dma_chain_none, 71 output); 72 } 73 74 /* Introduce a special initiating channel if an interrupt has been 75 indicated. */ 76 77 if (initiating_channel) 78 { 79 vga_configure_zero_channel(channel++, vga_display.initiating_int_num, 1, 80 output); 81 } 82 83 /* A line channel is always configured, chaining it to any initiating 84 channel. */ 85 86 vga_configure_line_channel(channel++, transfer_int_num, 87 initiating_channel ? dma_chain_previous : dma_chain_none, 88 output); 89 90 /* A zero channel is always configured, chaining it to the preceding line 91 channel, with the same transfer interrupt initiating the transfer. */ 92 93 vga_configure_zero_channel(channel, transfer_int_num, 0, output); 94 } 95 96 void vga_configure_line_channel(int channel, int int_num, enum dma_chain chain, 97 uint32_t output) 98 { 99 dma_init(channel, 2); 100 101 /* If initiating, configure to always be enabled. */ 102 103 if (chain == dma_chain_none) 104 dma_set_auto_enable(channel, 1); 105 106 /* Chain to either the previous or next channel where a special initiating 107 channel is used. */ 108 109 else 110 dma_set_chaining(channel, chain); 111 112 /* Initiate DMA on the transfer interrupt, transferring line data to the 113 first byte of the output word. */ 114 115 dma_set_interrupt(channel, int_num, 1); 116 117 dma_set_destination(channel, HW_PHYSICAL(output), 1); 118 dma_set_cell(channel, vga_display.display_config->transfer_cell_size); 119 } 120 121 void vga_configure_zero_channel(int channel, int int_num, int initiating, 122 uint32_t output) 123 { 124 dma_init(channel, 3); 125 126 /* If initiating, configure to always be enabled. */ 127 128 if (initiating) 129 dma_set_auto_enable(channel, 1); 130 131 /* Otherwise, chain to the preceding channel and register prior transfer 132 events before the channel is activated. */ 133 134 else 135 { 136 dma_set_chaining(channel, dma_chain_previous); 137 dma_set_receive_events(channel, 1); 138 } 139 140 /* Transfer upon the indicated interrupt condition. */ 141 142 dma_set_interrupt(channel, int_num, 1); 143 dma_set_transfer(channel, PHYSICAL((uint32_t) zerodata), 144 ZERO_LENGTH, 145 HW_PHYSICAL(output), 1, 146 ZERO_LENGTH); 147 } 148 149 /* Configure a timer and output compare units for horizontal and vertical 150 sync. */ 151 152 void vga_configure_sync(int hsync_unit, int vsync_unit, int timer) 153 { 154 /* Record the peripherals in use. */ 155 156 vga_display.hsync_unit = hsync_unit; 157 vga_display.vsync_unit = vsync_unit; 158 vga_display.timer = timer; 159 160 /* Configure a timer for the horizontal sync. The timer has no prescaling 161 (0). */ 162 163 timer_init(timer, 0, vga_display.display_config->hfreq_limit); 164 timer_on(timer); 165 166 /* Horizontal sync. */ 167 168 /* Configure output compare in dual compare (continuous output) mode using 169 the timer as time base. The interrupt condition drives the first DMA 170 channel and is handled to drive the display state machine. */ 171 172 oc_init(hsync_unit, 0b101, timer); 173 oc_set_pulse(hsync_unit, vga_display.display_config->hsync_end); 174 oc_set_pulse_end(hsync_unit, vga_display.display_config->hsync_start); 175 oc_init_interrupt(hsync_unit, 7, 3); 176 oc_on(hsync_unit); 177 178 /* Vertical sync. */ 179 180 /* Configure output compare in single compare (output driven low) mode using 181 the timer as time base. The unit is enabled later. It is only really used 182 to achieve precisely-timed level transitions in hardware. */ 183 184 oc_init(vsync_unit, 0b010, timer); 185 oc_set_pulse(vsync_unit, 0); 186 } 187 188 189 190 /* Interrupt handlers. */ 191 192 void vga_interrupt_handler(void) 193 { 194 vga_display.line += 1; 195 vga_display.state_handler(); 196 } 197 198 199 200 /* Vertical back porch region. */ 201 202 void vbp_active(void) 203 { 204 if (vga_display.line < vga_display.display_config->visible_start) 205 return; 206 207 /* Enter the visible region. */ 208 209 vga_display.state_handler = visible_active; 210 211 /* Set the line address. */ 212 213 vga_display.linedata = vga_display.display_config->screen_start; 214 start_visible(); 215 } 216 217 /* Visible region. */ 218 219 void visible_active(void) 220 { 221 if (vga_display.line < vga_display.display_config->vfp_start) 222 { 223 /* Update the line address and handle wraparound. */ 224 225 if (!(vga_display.line % vga_display.display_config->line_multiplier)) 226 { 227 vga_display.linedata += vga_display.display_config->line_length; 228 229 if (vga_display.linedata >= vga_display.display_config->screen_limit) 230 vga_display.linedata -= vga_display.display_config->screen_size; 231 } 232 233 update_visible(); 234 return; 235 } 236 237 /* End the visible region. */ 238 239 vga_display.state_handler = vfp_active; 240 241 /* Disable the channel for the next line. */ 242 243 stop_visible(); 244 } 245 246 /* Vertical front porch region. */ 247 248 void vfp_active(void) 249 { 250 if (vga_display.line < vga_display.display_config->vsync_start) 251 return; 252 253 /* Enter the vertical sync region. */ 254 255 vga_display.state_handler = vsync_active; 256 257 /* Bring vsync low when the next line starts. */ 258 259 vsync_low(); 260 } 261 262 /* Vertical sync region. */ 263 264 void vsync_active(void) 265 { 266 if (vga_display.line < vga_display.display_config->vsync_end) 267 return; 268 269 /* Start again at the top of the display. */ 270 271 vga_display.line = 0; 272 vga_display.state_handler = vbp_active; 273 274 /* Bring vsync high when the next line starts. */ 275 276 vsync_high(); 277 } 278 279 280 281 /* Enable the channels for the next line. */ 282 283 void start_visible(void) 284 { 285 update_visible(); 286 update_transfers(1); 287 } 288 289 /* Update the channels for the next line. */ 290 291 void update_visible(void) 292 { 293 uint32_t transfer_start = (uint32_t) vga_display.linedata; 294 uint32_t transfer_length = vga_display.display_config->line_length / 295 vga_display.line_channels; 296 297 /* Determine whether an initiating channel is used. */ 298 299 int initiating_channel = vga_display.initiating_int_num >= 0; 300 int channel = 0; 301 302 /* Update the source of a secondary line channel. */ 303 304 if (vga_display.line_channels == 2) 305 { 306 dma_set_source(channel, PHYSICAL(transfer_start), transfer_length); 307 transfer_start += transfer_length; 308 channel++; 309 } 310 311 /* Skip any initiating channel. */ 312 313 if (initiating_channel) 314 channel++; 315 316 /* Update the source of a line channel, with potentially updated start 317 address. */ 318 319 dma_set_source(channel, PHYSICAL(transfer_start), transfer_length); 320 } 321 322 /* Disable the channels for the next line. */ 323 324 void stop_visible(void) 325 { 326 update_transfers(0); 327 } 328 329 /* Enable or disable transfers. */ 330 331 void update_transfers(int enable) 332 { 333 int initiating_channel = vga_display.initiating_int_num >= 0; 334 int channel = 0; 335 void (*fn)() = enable ? dma_on : dma_off; 336 337 /* Update line channels if no initiating channel is used. */ 338 339 if (vga_display.line_channels == 2) 340 { 341 if (!initiating_channel) 342 fn(channel); 343 channel++; 344 } 345 346 /* Update any initiating channel. */ 347 348 if (initiating_channel) 349 { 350 fn(channel); 351 channel++; 352 } 353 354 /* Update line channels if no initiating channel is used. */ 355 356 if (!initiating_channel) 357 fn(channel); 358 } 359 360 361 362 /* Bring vsync low (single compare, output driven low) when the next line 363 starts. */ 364 365 void vsync_low(void) 366 { 367 oc_init(vga_display.vsync_unit, 0b010, vga_display.timer); 368 oc_on(vga_display.vsync_unit); 369 } 370 371 /* Bring vsync high (single compare, output driven high) when the next line 372 starts. */ 373 374 void vsync_high(void) 375 { 376 oc_init(vga_display.vsync_unit, 0b001, vga_display.timer); 377 oc_on(vga_display.vsync_unit); 378 }