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