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