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 "pic32_c.h" 22 #include "init.h" 23 #include "debug.h" 24 25 /* Specific functionality. */ 26 27 #include "main.h" 28 #include "devconfig.h" 29 #include "vga.h" 30 #include "display.h" 31 #include "display_config.h" 32 33 34 35 /* Display state. */ 36 37 static void (*state_handler)(void); 38 static uint32_t line; 39 40 /* Pointers to pixel lines. */ 41 42 static uint8_t *linedata; 43 44 /* Pixel data. */ 45 46 static const uint8_t zerodata[ZERO_LENGTH] = {0}; 47 48 49 50 /* Blink an attached LED with delays implemented using a loop. */ 51 52 static void blink(uint32_t delay, uint32_t port, uint32_t pins) 53 { 54 uint32_t counter; 55 56 /* Clear outputs (LED). */ 57 58 CLR_REG(port, pins); 59 60 while (1) 61 { 62 counter = delay; 63 64 while (counter--) __asm__(""); /* retain loop */ 65 66 /* Invert outputs (LED). */ 67 68 INV_REG(port, pins); 69 } 70 } 71 72 73 74 /* Main program. */ 75 76 void main(void) 77 { 78 line = 0; 79 state_handler = vbp_active; 80 test_linedata(&display_config); 81 82 init_memory(); 83 init_pins(); 84 init_outputs(); 85 86 unlock_config(); 87 config_oc(); 88 config_uart(); 89 lock_config(); 90 91 init_dma(); 92 93 /* Peripheral relationships: 94 95 Timer2 -> OC1 96 -> OC2 (vertical sync region) 97 -> DMA1: zerodata -> PORTB (visible region) 98 | 99 Timer3 -> DMA0: linedata -> PORTB 100 Timer3 -> DMA2: linedata -> PORTB 101 | 102 Timer3 -> DMA3: zerodata -> PORTB 103 */ 104 105 /* Initiate DMA on the Timer2 interrupt condition, transferring line data to 106 the first byte of PORTB. Do not enable the channel for initiation until 107 the visible region is about to start. */ 108 109 dma_init(1, 3); 110 dma_set_auto_enable(1, 1); 111 dma_set_interrupt(1, T2, 1); 112 dma_set_transfer(1, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 113 HW_PHYSICAL(PORTB), 1, 114 ZERO_LENGTH); 115 116 /* Enable DMA on the zero channel's completion, with the Timer3 117 interrupt condition initiating transfers. */ 118 119 dma_init(0, 3); 120 dma_set_chaining(0, dma_chain_next); 121 dma_set_interrupt(0, T3, 1); 122 dma_set_transfer(0, PHYSICAL((uint32_t) display_config.screen_start), 123 display_config.line_length / 2, 124 HW_PHYSICAL(PORTB), 1, 125 TRANSFER_CELL_SIZE); 126 127 /* Enable DMA on the zero channel's completion, with the Timer3 128 interrupt condition initiating transfers. */ 129 130 dma_init(2, 3); 131 dma_set_chaining(2, dma_chain_previous); 132 dma_set_interrupt(2, T3, 1); 133 dma_set_transfer(2, PHYSICAL((uint32_t) display_config.screen_start + 134 display_config.line_length / 2), 135 display_config.line_length / 2, 136 HW_PHYSICAL(PORTB), 1, 137 TRANSFER_CELL_SIZE); 138 139 /* Enable DMA on the preceding channel's completion, with this also 140 initiating transfers. */ 141 142 dma_init(3, 3); 143 dma_set_chaining(3, dma_chain_previous); 144 dma_set_interrupt(3, T3, 1); 145 dma_set_transfer(3, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 146 HW_PHYSICAL(PORTB), 1, 147 ZERO_LENGTH); 148 dma_set_receive_events(3, 1); 149 150 /* Configure a timer for the horizontal sync. The timer has no prescaling 151 (0). */ 152 153 timer_init(2, 0, HFREQ_LIMIT); 154 timer_on(2); 155 156 /* Configure a timer for line data transfers. */ 157 158 timer_init(3, 0, 1); 159 timer_on(3); 160 161 /* Horizontal sync. */ 162 163 /* Configure output compare in dual compare (continuous output) mode using 164 Timer2 as time base. The interrupt condition drives the first DMA channel 165 and is handled to drive the display state machine. */ 166 167 oc_init(1, 0b101, 2); 168 oc_set_pulse(1, HSYNC_END); 169 oc_set_pulse_end(1, HSYNC_START); 170 oc_init_interrupt(1, 7, 3); 171 oc_on(1); 172 173 /* Vertical sync. */ 174 175 /* Configure output compare in single compare (output driven low) mode using 176 Timer2 as time base. The unit is enabled later. It is only really used to 177 achieve precisely-timed level transitions in hardware. */ 178 179 oc_init(2, 0b010, 2); 180 oc_set_pulse(2, 0); 181 182 uart_init(1, FPB, 115200); 183 uart_on(1); 184 185 interrupts_on(); 186 187 blink(3 << 24, PORTA, 1 << 3); 188 } 189 190 191 192 /* Exception and interrupt handlers. */ 193 194 void exception_handler(void) 195 { 196 blink(3 << 12, PORTA, 1 << 3); 197 } 198 199 void interrupt_handler(void) 200 { 201 uint32_t ifs; 202 203 /* Check for a OC1 interrupt condition. */ 204 205 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 206 207 if (ifs) 208 { 209 line += 1; 210 state_handler(); 211 CLR_REG(OCIFS, ifs); 212 } 213 } 214 215 216 217 /* Vertical back porch region. */ 218 219 void vbp_active(void) 220 { 221 if (line < VISIBLE_START) 222 return; 223 224 /* Enter the visible region. */ 225 226 state_handler = visible_active; 227 228 /* Set the line address. */ 229 230 linedata = display_config.screen_start; 231 dma_set_source(0, PHYSICAL((uint32_t) linedata), display_config.line_length / 2); 232 dma_set_source(2, PHYSICAL((uint32_t) linedata + display_config.line_length / 2), 233 display_config.line_length / 2); 234 235 /* Enable the channels for the next line. */ 236 237 dma_on(1); 238 } 239 240 /* Visible region. */ 241 242 void visible_active(void) 243 { 244 INV_REG(PORTA, 1 << 2); 245 246 if (line < VFP_START) 247 { 248 /* Update the line address and handle wraparound. */ 249 250 if (!(line % display_config.line_multiplier)) 251 { 252 linedata += display_config.line_length; 253 if (linedata >= display_config.screen_limit) 254 linedata -= display_config.screen_size; 255 } 256 257 dma_set_source(0, PHYSICAL((uint32_t) linedata), display_config.line_length / 2); 258 dma_set_source(2, PHYSICAL((uint32_t) linedata + display_config.line_length / 2), 259 display_config.line_length / 2); 260 return; 261 } 262 263 /* End the visible region. */ 264 265 state_handler = vfp_active; 266 267 /* Disable the channels for the next line. */ 268 269 dma_off(1); 270 } 271 272 /* Vertical front porch region. */ 273 274 void vfp_active(void) 275 { 276 if (line < VSYNC_START) 277 return; 278 279 /* Enter the vertical sync region. */ 280 281 state_handler = vsync_active; 282 283 /* Bring vsync low (single compare, output driven low) when the next line 284 starts. */ 285 286 oc_init(2, 0b010, 2); 287 oc_on(2); 288 } 289 290 /* Vertical sync region. */ 291 292 void vsync_active(void) 293 { 294 if (line < VSYNC_END) 295 return; 296 297 /* Start again at the top of the display. */ 298 299 line = 0; 300 state_handler = vbp_active; 301 302 /* Bring vsync high (single compare, output driven high) when the next line 303 starts. */ 304 305 oc_init(2, 0b001, 2); 306 oc_on(2); 307 } 308 309 310 311 /* Peripheral pin configuration. */ 312 313 void config_oc(void) 314 { 315 /* Map OC1 to RPA0. */ 316 317 REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */ 318 319 /* Map OC2 to RPA1. */ 320 321 REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */ 322 } 323 324 void config_uart(void) 325 { 326 /* Map U1RX to RPB13. */ 327 328 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 329 330 /* Map U1TX to RPB15. */ 331 332 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 333 334 /* Set RPB13 to input. */ 335 336 SET_REG(TRISB, 1 << 13); 337 }