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