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