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 -> DMA0: zerodata -> PORTB (visible region) 102 | 103 Timer3 -> DMA1: linedata -> PORTB 104 | 105 DMA1 -> DMA2: zerodata -> PORTB 106 */ 107 108 /* Initiate DMA on the Timer2 interrupt condition, transferring line data to 109 the first byte of PORTB. Do not enable the channel for initiation until 110 the visible region is about to start. */ 111 112 dma_init(0, 3); 113 dma_set_auto_enable(0, 1); 114 dma_set_interrupt(0, T2, 1); 115 dma_set_transfer(0, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 116 HW_PHYSICAL(PORTB), 1, 117 ZERO_LENGTH); 118 119 /* Enable DMA on the preceding channel's completion, with the Timer3 120 interrupt condition initiating transfers. */ 121 122 dma_init(1, 3); 123 dma_set_chaining(1, dma_chain_previous); 124 dma_set_interrupt(1, T3, 1); 125 dma_set_transfer(1, PHYSICAL((uint32_t) screenstart), LINE_LENGTH, 126 HW_PHYSICAL(PORTB), 1, 127 1); 128 dma_init_interrupt(1, 0b00001000, 1, 3); 129 130 /* Enable DMA on the preceding channel's completion, with this also 131 initiating transfers. */ 132 133 dma_init(2, 3); 134 dma_set_chaining(2, dma_chain_previous); 135 dma_set_interrupt(2, DMA1, 1); 136 dma_set_transfer(2, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 137 HW_PHYSICAL(PORTB), 1, 138 ZERO_LENGTH); 139 dma_set_receive_events(2, 1); 140 141 /* Configure a timer for the horizontal sync. The timer has no prescaling 142 (0). */ 143 144 timer_init(2, 0, HFREQ_LIMIT); 145 timer_on(2); 146 147 /* Configure a timer for line data transfers. */ 148 149 timer_init(3, 0, 1); 150 timer_on(3); 151 152 /* Horizontal sync. */ 153 154 /* Configure output compare in dual compare (continuous output) mode using 155 Timer2 as time base. The interrupt condition drives the first DMA channel 156 and is handled to drive the display state machine. */ 157 158 oc_init(1, 0b101, 2); 159 oc_set_pulse(1, HSYNC_END); 160 oc_set_pulse_end(1, HSYNC_START); 161 oc_init_interrupt(1, 7, 3); 162 oc_on(1); 163 164 /* Vertical sync. */ 165 166 /* Configure output compare in single compare (output driven low) mode using 167 Timer2 as time base. The unit is enabled later. It is only really used to 168 achieve precisely-timed level transitions in hardware. */ 169 170 oc_init(2, 0b010, 2); 171 oc_set_pulse(2, 0); 172 173 uart_init(1, 115200); 174 uart_on(1); 175 176 interrupts_on(); 177 178 blink(3 << 24, PORTA, 1 << 3); 179 } 180 181 182 183 /* Exception and interrupt handlers. */ 184 185 void exception_handler(void) 186 { 187 blink(3 << 12, PORTA, 1 << 3); 188 } 189 190 void interrupt_handler(void) 191 { 192 uint32_t ifs; 193 194 /* Check for a OC1 interrupt condition. */ 195 196 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 197 198 if (ifs) 199 { 200 line += 1; 201 state_handler(); 202 CLR_REG(OCIFS, ifs); 203 } 204 } 205 206 207 208 /* Vertical back porch region. */ 209 210 void vbp_active(void) 211 { 212 if (line < VISIBLE_START) 213 return; 214 215 /* Enter the visible region. */ 216 217 state_handler = visible_active; 218 219 /* Set the line address. */ 220 221 linedata = screenstart; 222 dma_set_source(1, PHYSICAL((uint32_t) linedata), LINE_LENGTH); 223 224 /* Enable the channel for the next line. */ 225 226 dma_on(0); 227 } 228 229 /* Visible region. */ 230 231 void visible_active(void) 232 { 233 uint32_t ifs; 234 235 /* Remove any DMA interrupt condition (CHBCIF). */ 236 237 ifs = REG(DMAIFS) & DMA_INT_FLAGS(1, DCHxIF); 238 239 if (ifs) 240 { 241 CLR_REG(DMA_REG(1, DCHxINT), 0b11111111); 242 CLR_REG(DMAIFS, ifs); 243 INV_REG(PORTA, 1 << 2); 244 } 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(1, PHYSICAL((uint32_t) linedata), LINE_LENGTH); 258 return; 259 } 260 261 /* End the visible region. */ 262 263 state_handler = vfp_active; 264 265 /* Disable the channel for the next line. */ 266 267 dma_off(0); 268 } 269 270 /* Vertical front porch region. */ 271 272 void vfp_active(void) 273 { 274 if (line < VSYNC_START) 275 return; 276 277 /* Enter the vertical sync region. */ 278 279 state_handler = vsync_active; 280 281 /* Bring vsync low (single compare, output driven low) when the next line 282 starts. */ 283 284 oc_init(2, 0b010, 2); 285 oc_on(2); 286 } 287 288 /* Vertical sync region. */ 289 290 void vsync_active(void) 291 { 292 if (line < VSYNC_END) 293 return; 294 295 /* Start again at the top of the display. */ 296 297 line = 0; 298 state_handler = vbp_active; 299 300 /* Bring vsync high (single compare, output driven high) when the next line 301 starts. */ 302 303 oc_init(2, 0b001, 2); 304 oc_on(2); 305 } 306 307 308 309 /* Peripheral pin configuration. */ 310 311 void config_oc(void) 312 { 313 /* Map OC1 to RPA0. */ 314 315 REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */ 316 317 /* Map OC2 to RPA1. */ 318 319 REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */ 320 } 321 322 void config_uart(void) 323 { 324 /* Map U1RX to RPB13. */ 325 326 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 327 328 /* Map U1TX to RPB15. */ 329 330 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 331 332 /* Set RPB13 to input. */ 333 334 SET_REG(TRISB, 1 << 13); 335 }