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