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