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