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