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 31 32 /* Initialise the state machine. */ 33 34 void init_vga(display_config_t *display_config, 35 void (*start_visible)(), 36 void (*update_visible)(), 37 void (*stop_visible)(), 38 void (*vsync_high)(), 39 void (*vsync_low)()) 40 { 41 /* Display parameters. */ 42 43 vga_display.display_config = display_config; 44 45 /* Display state handlers. */ 46 47 vga_display.start_visible = start_visible; 48 vga_display.update_visible = update_visible; 49 vga_display.stop_visible = stop_visible; 50 51 /* Vertical sync operations. */ 52 53 vga_display.vsync_high = vsync_high; 54 vga_display.vsync_low = vsync_low; 55 56 /* Initial state. */ 57 58 vga_display.state_handler = vbp_active; 59 vga_display.line = 0; 60 } 61 62 /* Configure a timer and output compare units for horizontal and vertical 63 sync. */ 64 65 void vga_configure_sync(int hsync_unit, int vsync_unit, int timer) 66 { 67 /* Configure a timer for the horizontal sync. The timer has no prescaling 68 (0). */ 69 70 timer_init(timer, 0, vga_display.display_config->hfreq_limit); 71 timer_on(timer); 72 73 /* Horizontal sync. */ 74 75 /* Configure output compare in dual compare (continuous output) mode using 76 the timer as time base. The interrupt condition drives the first DMA 77 channel and is handled to drive the display state machine. */ 78 79 oc_init(hsync_unit, 0b101, timer); 80 oc_set_pulse(hsync_unit, vga_display.display_config->hsync_end); 81 oc_set_pulse_end(hsync_unit, vga_display.display_config->hsync_start); 82 oc_init_interrupt(hsync_unit, 7, 3); 83 oc_on(hsync_unit); 84 85 /* Vertical sync. */ 86 87 /* Configure output compare in single compare (output driven low) mode using 88 the timer as time base. The unit is enabled later. It is only really used 89 to achieve precisely-timed level transitions in hardware. */ 90 91 oc_init(vsync_unit, 0b010, timer); 92 oc_set_pulse(vsync_unit, 0); 93 } 94 95 96 97 /* Interrupt handlers. */ 98 99 void vga_interrupt_handler(void) 100 { 101 vga_display.line += 1; 102 vga_display.state_handler(); 103 } 104 105 106 107 /* Vertical back porch region. */ 108 109 void vbp_active(void) 110 { 111 if (vga_display.line < vga_display.display_config->visible_start) 112 return; 113 114 /* Enter the visible region. */ 115 116 vga_display.state_handler = visible_active; 117 118 /* Set the line address. */ 119 120 vga_display.linedata = vga_display.display_config->screen_start; 121 vga_display.start_visible(&vga_display); 122 } 123 124 /* Visible region. */ 125 126 void visible_active(void) 127 { 128 if (vga_display.line < vga_display.display_config->vfp_start) 129 { 130 /* Update the line address and handle wraparound. */ 131 132 if (!(vga_display.line % vga_display.display_config->line_multiplier)) 133 { 134 vga_display.linedata += vga_display.display_config->line_length; 135 136 if (vga_display.linedata >= vga_display.display_config->screen_limit) 137 vga_display.linedata -= vga_display.display_config->screen_size; 138 } 139 140 vga_display.update_visible(&vga_display); 141 return; 142 } 143 144 /* End the visible region. */ 145 146 vga_display.state_handler = vfp_active; 147 148 /* Disable the channel for the next line. */ 149 150 vga_display.stop_visible(&vga_display); 151 } 152 153 /* Vertical front porch region. */ 154 155 void vfp_active(void) 156 { 157 if (vga_display.line < vga_display.display_config->vsync_start) 158 return; 159 160 /* Enter the vertical sync region. */ 161 162 vga_display.state_handler = vsync_active; 163 164 /* Bring vsync low when the next line starts. */ 165 166 vga_display.vsync_low(); 167 } 168 169 /* Vertical sync region. */ 170 171 void vsync_active(void) 172 { 173 if (vga_display.line < vga_display.display_config->vsync_end) 174 return; 175 176 /* Start again at the top of the display. */ 177 178 vga_display.line = 0; 179 vga_display.state_handler = vbp_active; 180 181 /* Bring vsync high when the next line starts. */ 182 183 vga_display.vsync_high(); 184 }