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