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 31 32 33 /* Display state. */ 34 35 static void (*state_handler)(void); 36 static uint32_t line; 37 38 /* Pixel data. */ 39 40 static uint8_t linedata[LINE_LENGTH]; 41 static const uint8_t zerodata[ZERO_LENGTH] = {0}; 42 43 44 45 static void test_linedata(void) 46 { 47 int i; 48 49 for (i = 0; i < LINE_LENGTH; i++) 50 linedata[i] = (i % 2) ? 0xff : 0x00; 51 } 52 53 /* Blink an attached LED with delays implemented using a loop. */ 54 55 static void blink(uint32_t delay, uint32_t port, uint32_t pins) 56 { 57 uint32_t counter; 58 59 /* Clear outputs (LED). */ 60 61 CLR_REG(port, pins); 62 63 while (1) 64 { 65 counter = delay; 66 67 while (counter--) __asm__(""); /* retain loop */ 68 69 /* Invert outputs (LED). */ 70 71 INV_REG(port, pins); 72 rbits(PM_REG(0, PMxCON)); uart_write_nl(); 73 rhex(PM_REG(0, PMxMODE)); uart_write_nl(); 74 } 75 } 76 77 78 79 /* Main program. */ 80 81 void main(void) 82 { 83 line = 0; 84 state_handler = vbp_active; 85 test_linedata(); 86 87 init_memory(); 88 init_pins(); 89 init_outputs(); 90 91 unlock_config(); 92 config_oc(); 93 config_uart(); 94 lock_config(); 95 96 init_dma(); 97 init_pm(); 98 99 /* Configure parallel master mode. */ 100 101 pm_init(0, 0b10); 102 pm_set_output(0, 1, 0); 103 pm_on(0); 104 105 /* Initiate DMA on the Timer2 interrupt transferring line data to the first 106 byte of PORTB. Do not enable the channel for initiation until the visible 107 region is about to start. */ 108 109 dma_init(0, 3); 110 dma_set_auto_enable(0, 1); 111 dma_set_interrupt(0, T2, 1); 112 dma_set_transfer(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH, 113 HW_PHYSICAL(PM_REG(0, PMxDIN)), 1, 114 TRANSFER_CELL_SIZE); 115 dma_init_interrupt(0, 0b1000, 1, 3); 116 117 /* Enable DMA on the preceding channel's completion, with this also 118 initiating transfers. This "reset" or "zero" transfer is employed to set 119 the pixel level to black in a connected flip-flop. Without the flip-flop 120 it is superfluous. */ 121 122 dma_init(1, 3); 123 dma_set_chaining(1, dma_chain_previous); 124 dma_set_interrupt(1, DMA0, 1); 125 dma_set_transfer(1, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 126 HW_PHYSICAL(PM_REG(0, PMxDIN)), 1, 127 ZERO_LENGTH); 128 dma_set_receive_events(1, 1); 129 130 /* Configure a timer for the horizontal sync. The timer has no prescaling 131 (0). */ 132 133 timer_init(2, 0, HFREQ_LIMIT); 134 timer_on(2); 135 136 /* Horizontal sync. */ 137 138 /* Configure output compare in dual compare (continuous output) mode using 139 Timer2 as time base. The interrupt condition drives the first DMA channel 140 and is handled to drive the display state machine. */ 141 142 oc_init(1, 0b101, 2); 143 oc_set_pulse(1, HSYNC_END); 144 oc_set_pulse_end(1, HSYNC_START); 145 oc_init_interrupt(1, 7, 3); 146 oc_on(1); 147 148 /* Vertical sync. */ 149 150 /* Configure output compare in single compare (output driven low) mode using 151 Timer2 as time base. The unit is enabled later. It is only really used to 152 achieve precisely-timed level transitions in hardware. */ 153 154 oc_init(2, 0b010, 2); 155 oc_set_pulse(2, 0); 156 157 uart_init(1, FPB, 115200); 158 uart_on(1); 159 160 interrupts_on(); 161 162 blink(3 << 24, PORTA, 1 << 2); 163 } 164 165 166 167 /* Exception and interrupt handlers. */ 168 169 void exception_handler(void) 170 { 171 blink(3 << 12, PORTA, 1 << 2); 172 } 173 174 void interrupt_handler(void) 175 { 176 uint32_t ifs; 177 178 /* Check for a OC1 interrupt condition. */ 179 180 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 181 182 if (ifs) 183 { 184 line += 1; 185 state_handler(); 186 CLR_REG(OCIFS, ifs); 187 } 188 } 189 190 191 192 /* Vertical back porch region. */ 193 194 void vbp_active(void) 195 { 196 if (line < VISIBLE_START) 197 return; 198 199 /* Enter the visible region. */ 200 201 state_handler = visible_active; 202 203 /* NOTE: Set the line address. */ 204 205 /* Enable the channel for the next line. */ 206 207 dma_on(0); 208 } 209 210 /* Visible region. */ 211 212 void visible_active(void) 213 { 214 uint32_t ifs; 215 216 /* Remove any DMA interrupt condition (CHBCIF). */ 217 218 ifs = REG(DMAIFS) & DMA_INT_FLAGS(0, DCHxIF); 219 220 if (ifs) 221 { 222 CLR_REG(DMA_REG(0, DCHxINT), 0b11111111); 223 CLR_REG(DMAIFS, ifs); 224 } 225 226 if (line < VFP_START) 227 { 228 /* NOTE: Update the line address and handle wraparound. */ 229 230 return; 231 } 232 233 /* End the visible region. */ 234 235 state_handler = vfp_active; 236 237 /* Disable the channel for the next line. */ 238 239 dma_off(0); 240 } 241 242 /* Vertical front porch region. */ 243 244 void vfp_active(void) 245 { 246 if (line < VSYNC_START) 247 return; 248 249 /* Enter the vertical sync region. */ 250 251 state_handler = vsync_active; 252 253 /* Bring vsync low (single compare, output driven low) when the next line 254 starts. */ 255 256 oc_init(2, 0b010, 2); 257 oc_on(2); 258 } 259 260 /* Vertical sync region. */ 261 262 void vsync_active(void) 263 { 264 if (line < VSYNC_END) 265 return; 266 267 /* Start again at the top of the display. */ 268 269 line = 0; 270 state_handler = vbp_active; 271 272 /* Bring vsync high (single compare, output driven high) when the next line 273 starts. */ 274 275 oc_init(2, 0b001, 2); 276 oc_on(2); 277 } 278 279 280 281 /* Peripheral pin configuration. */ 282 283 void config_oc(void) 284 { 285 /* Map OC1 to RPB4. */ 286 287 REG(RPB4R) = 0b0101; /* RPB4R<3:0> = 0101 (OC1) */ 288 289 /* Map OC2 to RPB5. */ 290 291 REG(RPB5R) = 0b0101; /* RPB5R<3:0> = 0101 (OC2) */ 292 } 293 294 void config_uart(void) 295 { 296 /* Map U1RX to RPB13. */ 297 298 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 299 300 /* Map U1TX to RPB15. */ 301 302 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 303 304 /* Set RPB13 to input. */ 305 306 SET_REG(TRISB, 1 << 13); 307 }