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_cpu.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, int line_timer) 35 { 36 /* Display parameters. */ 37 38 vga_display.display_config = display_config; 39 40 /* Initial state. */ 41 42 vga_display.state_handler = vbp_active; 43 vga_display.line = 0; 44 45 /* Configure a general display timer to start line data transfer and for the 46 horizontal sync. */ 47 48 vga_display.line_timer = line_timer; 49 } 50 51 /* Initialise a separate transfer timer if different from the general display 52 timer. */ 53 54 void init_vga_with_timer(display_config_t *display_config, int line_timer) 55 { 56 /* Initialise the basic properties of the display. */ 57 58 init_vga(display_config, line_timer); 59 60 /* Configure a line timer for horizontal sync and line data transfers. The 61 interrupt is handled to produce line data and to drive the display state 62 machine. */ 63 64 /* The timer has no prescaling (0). */ 65 66 timer_init(line_timer, 0, display_config->hfreq_limit); 67 timer_init_interrupt(line_timer, 7, 3); 68 timer_on(line_timer); 69 } 70 71 72 73 /* Configure the output port. */ 74 75 void vga_configure_transfer(uint32_t output) 76 { 77 vga_display.output = output; 78 } 79 80 /* Configure output compare units for horizontal and vertical sync. */ 81 82 void vga_configure_sync(int hsync_unit, int vsync_unit) 83 { 84 /* Record the peripherals in use. */ 85 86 vga_display.hsync_unit = hsync_unit; 87 vga_display.vsync_unit = vsync_unit; 88 89 /* Horizontal sync. */ 90 91 /* Configure output compare in dual compare (continuous output) mode using 92 the timer as time base. */ 93 94 oc_init(hsync_unit, 0b101, vga_display.line_timer); 95 oc_set_pulse(hsync_unit, vga_display.display_config->hsync_end); 96 oc_set_pulse_end(hsync_unit, vga_display.display_config->hsync_start); 97 oc_init_interrupt(hsync_unit, 7, 3); 98 oc_on(hsync_unit); 99 100 /* Vertical sync. */ 101 102 /* Configure output compare in single compare (output driven low) mode using 103 the timer as time base. The unit is enabled later. It is only really used 104 to achieve precisely-timed level transitions in hardware. */ 105 106 oc_init(vsync_unit, 0b010, vga_display.line_timer); 107 oc_set_pulse(vsync_unit, 0); 108 } 109 110 111 112 /* Interrupt handlers. */ 113 114 void vga_interrupt_handler(void) 115 { 116 vga_display.line += 1; 117 vga_display.state_handler(); 118 } 119 120 /* Visible region pixel output handler. */ 121 122 void vga_transfer_interrupt_handler(void) 123 { 124 display_config_t *cfg = vga_display.display_config; 125 uint8_t *current, *end, *output; 126 127 if (vga_display.state_handler != visible_active) 128 return; 129 130 /* Generate the pixel signal. */ 131 132 output = (uint8_t *) vga_display.output; 133 end = vga_display.linedata + cfg->line_length; 134 135 /* This is potentially not as efficient as loading words and shifting bytes 136 but it appears difficult to implement that approach without experiencing 137 data load exceptions. */ 138 139 for (current = vga_display.linedata; current < end; current++) 140 REG(output) = *current; 141 142 /* Reset the signal level. */ 143 144 REG(output) = 0; 145 } 146 147 148 149 /* Vertical back porch region. */ 150 151 void vbp_active(void) 152 { 153 if (vga_display.line < vga_display.display_config->visible_start) 154 return; 155 156 /* Enter the visible region. */ 157 158 vga_display.state_handler = visible_active; 159 160 /* Set the line address. */ 161 162 vga_display.linedata = vga_display.display_config->screen_start; 163 } 164 165 /* Visible region. */ 166 167 void visible_active(void) 168 { 169 display_config_t *cfg = vga_display.display_config; 170 171 if (vga_display.line < cfg->vfp_start) 172 { 173 /* Update the line address and handle wraparound. */ 174 175 if (!(vga_display.line % cfg->line_multiplier)) 176 { 177 vga_display.linedata += cfg->line_length; 178 179 if (vga_display.linedata >= cfg->screen_limit) 180 vga_display.linedata -= cfg->screen_size; 181 } 182 183 return; 184 } 185 186 /* End the visible region. */ 187 188 vga_display.state_handler = vfp_active; 189 } 190 191 /* Vertical front porch region. */ 192 193 void vfp_active(void) 194 { 195 if (vga_display.line < vga_display.display_config->vsync_start) 196 return; 197 198 /* Enter the vertical sync region. */ 199 200 vga_display.state_handler = vsync_active; 201 202 /* Bring vsync low when the next line starts. */ 203 204 vsync_low(); 205 } 206 207 /* Vertical sync region. */ 208 209 void vsync_active(void) 210 { 211 if (vga_display.line < vga_display.display_config->vsync_end) 212 return; 213 214 /* Start again at the top of the display. */ 215 216 vga_display.line = 0; 217 vga_display.state_handler = vbp_active; 218 219 /* Bring vsync high when the next line starts. */ 220 221 vsync_high(); 222 } 223 224 225 226 /* Bring vsync low (single compare, output driven low) when the next line 227 starts. */ 228 229 void vsync_low(void) 230 { 231 oc_init(vga_display.vsync_unit, 0b010, vga_display.line_timer); 232 oc_on(vga_display.vsync_unit); 233 } 234 235 /* Bring vsync high (single compare, output driven high) when the next line 236 starts. */ 237 238 void vsync_high(void) 239 { 240 oc_init(vga_display.vsync_unit, 0b001, vga_display.line_timer); 241 oc_on(vga_display.vsync_unit); 242 }