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