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