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