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