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 32 33 34 /* Display state. */ 35 36 static void (*state_handler)(void); 37 static uint32_t line; 38 39 /* Pointers to pixel lines. */ 40 41 static uint8_t *linedata, *linedatalimit, *screenstart; 42 43 /* Pixel data. */ 44 45 static const uint8_t zerodata[ZERO_LENGTH] = {0}; 46 static uint8_t framebuffer[SCREEN_SIZE]; 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(framebuffer); 81 82 /* Initialise the current display line pointer and display limit. */ 83 84 linedatalimit = framebuffer + SCREEN_SIZE; 85 screenstart = framebuffer; 86 87 init_memory(); 88 init_pins(); 89 init_outputs(); 90 91 unlock_config(); 92 config_oc(); 93 config_uart(); 94 lock_config(); 95 96 init_dma(); 97 98 /* Peripheral relationships: 99 100 Timer2 -> OC1 101 -> OC2 (vertical sync region) 102 -> DMA1: zerodata -> PORTB (visible region) 103 | 104 Timer3 -> DMA0: linedata -> PORTB 105 Timer3 -> DMA2: linedata -> PORTB 106 | 107 Timer3 -> DMA3: zerodata -> PORTB 108 */ 109 110 /* Initiate DMA on the Timer2 interrupt condition, transferring line data to 111 the first byte of PORTB. Do not enable the channel for initiation until 112 the visible region is about to start. */ 113 114 dma_init(1, 3); 115 dma_set_auto_enable(1, 1); 116 dma_set_interrupt(1, T2, 1); 117 dma_set_transfer(1, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 118 HW_PHYSICAL(PORTB), 1, 119 ZERO_LENGTH); 120 121 /* Enable DMA on the zero channel's completion, with the Timer3 122 interrupt condition initiating transfers. */ 123 124 dma_init(0, 3); 125 dma_set_chaining(0, dma_chain_next); 126 dma_set_interrupt(0, T3, 1); 127 dma_set_transfer(0, PHYSICAL((uint32_t) screenstart), LINE_LENGTH / 2, 128 HW_PHYSICAL(PORTB), 1, 129 TRANSFER_CELL_SIZE); 130 131 /* Enable DMA on the zero channel's completion, with the Timer3 132 interrupt condition initiating transfers. */ 133 134 dma_init(2, 3); 135 dma_set_chaining(2, dma_chain_previous); 136 dma_set_interrupt(2, T3, 1); 137 dma_set_transfer(2, PHYSICAL((uint32_t) screenstart + LINE_LENGTH / 2), LINE_LENGTH / 2, 138 HW_PHYSICAL(PORTB), 1, 139 TRANSFER_CELL_SIZE); 140 141 /* Enable DMA on the preceding channel's completion, with this also 142 initiating transfers. */ 143 144 dma_init(3, 3); 145 dma_set_chaining(3, dma_chain_previous); 146 dma_set_interrupt(3, T3, 1); 147 dma_set_transfer(3, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 148 HW_PHYSICAL(PORTB), 1, 149 ZERO_LENGTH); 150 dma_set_receive_events(3, 1); 151 152 /* Configure a timer for the horizontal sync. The timer has no prescaling 153 (0). */ 154 155 timer_init(2, 0, HFREQ_LIMIT); 156 timer_on(2); 157 158 /* Configure a timer for line data transfers. */ 159 160 timer_init(3, 0, 1); 161 timer_on(3); 162 163 /* Horizontal sync. */ 164 165 /* Configure output compare in dual compare (continuous output) mode using 166 Timer2 as time base. The interrupt condition drives the first DMA channel 167 and is handled to drive the display state machine. */ 168 169 oc_init(1, 0b101, 2); 170 oc_set_pulse(1, HSYNC_END); 171 oc_set_pulse_end(1, HSYNC_START); 172 oc_init_interrupt(1, 7, 3); 173 oc_on(1); 174 175 /* Vertical sync. */ 176 177 /* Configure output compare in single compare (output driven low) mode using 178 Timer2 as time base. The unit is enabled later. It is only really used to 179 achieve precisely-timed level transitions in hardware. */ 180 181 oc_init(2, 0b010, 2); 182 oc_set_pulse(2, 0); 183 184 uart_init(1, FPB, 115200); 185 uart_on(1); 186 187 interrupts_on(); 188 189 blink(3 << 24, PORTA, 1 << 3); 190 } 191 192 193 194 /* Exception and interrupt handlers. */ 195 196 void exception_handler(void) 197 { 198 blink(3 << 12, PORTA, 1 << 3); 199 } 200 201 void interrupt_handler(void) 202 { 203 uint32_t ifs; 204 205 /* Check for a OC1 interrupt condition. */ 206 207 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 208 209 if (ifs) 210 { 211 line += 1; 212 state_handler(); 213 CLR_REG(OCIFS, ifs); 214 } 215 } 216 217 218 219 /* Vertical back porch region. */ 220 221 void vbp_active(void) 222 { 223 if (line < VISIBLE_START) 224 return; 225 226 /* Enter the visible region. */ 227 228 state_handler = visible_active; 229 230 /* Set the line address. */ 231 232 linedata = screenstart; 233 dma_set_source(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH / 2); 234 dma_set_source(2, PHYSICAL((uint32_t) linedata + LINE_LENGTH / 2), LINE_LENGTH / 2); 235 236 /* Enable the channels for the next line. */ 237 238 dma_on(1); 239 } 240 241 /* Visible region. */ 242 243 void visible_active(void) 244 { 245 INV_REG(PORTA, 1 << 2); 246 247 if (line < VFP_START) 248 { 249 /* Update the line address and handle wraparound. */ 250 251 if (!(line % LINE_MULTIPLIER)) 252 { 253 linedata += LINE_LENGTH; 254 if (linedata >= linedatalimit) 255 linedata -= SCREEN_SIZE; 256 } 257 258 dma_set_source(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH / 2); 259 dma_set_source(2, PHYSICAL((uint32_t) linedata + LINE_LENGTH / 2), 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 }