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 /* Initiate DMA on the Timer2 interrupt transferring line data to the first 87 byte of PORTB. Do not enable the channels for initiation until the visible 88 region is about to start. 89 90 Here, two DMA channels are interleaved. This appears to provide a more 91 stable picture. */ 92 93 dma_init(0, 3); 94 dma_set_auto_enable(0, 1); 95 dma_set_interrupt(0, T2, 1); 96 dma_set_transfer(0, PHYSICAL((uint32_t) display_config.screen_start), 97 display_config.line_length / 2, 98 HW_PHYSICAL(PORTB), 1, 99 TRANSFER_CELL_SIZE); 100 101 dma_init(1, 3); 102 dma_set_auto_enable(1, 1); 103 dma_set_interrupt(1, T2, 1); 104 dma_set_transfer(1, PHYSICAL((uint32_t) display_config.screen_start + 105 display_config.line_length / 2), 106 display_config.line_length / 2, 107 HW_PHYSICAL(PORTB), 1, 108 TRANSFER_CELL_SIZE); 109 110 /* Enable DMA on the preceding channel's completion, with the timer event 111 initiating the transfer. */ 112 113 dma_init(2, 3); 114 dma_set_chaining(2, dma_chain_previous); 115 dma_set_interrupt(2, T2, 1); 116 dma_set_transfer(2, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, 117 HW_PHYSICAL(PORTB), 1, 118 ZERO_LENGTH); 119 dma_set_receive_events(2, 1); 120 121 /* Configure a timer and output compare units for horizontal and vertical 122 sync. */ 123 124 vga_configure_sync(1, 2, 2); 125 126 uart_init(1, FPB, 115200); 127 uart_on(1); 128 129 interrupts_on(); 130 131 blink(3 << 24, PORTA, 1 << 3); 132 } 133 134 135 136 /* Exception and interrupt handlers. */ 137 138 void exception_handler(void) 139 { 140 blink(3 << 12, PORTA, 1 << 3); 141 } 142 143 void interrupt_handler(void) 144 { 145 uint32_t ifs; 146 147 /* Check for a OC1 interrupt condition. */ 148 149 ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); 150 151 if (ifs) 152 { 153 vga_interrupt_handler(); 154 CLR_REG(OCIFS, ifs); 155 } 156 } 157 158 159 160 /* Enable the channels for the next line. */ 161 162 void start_visible(vga_display_t *vga_display) 163 { 164 dma_set_source(0, PHYSICAL((uint32_t) vga_display->linedata), 165 display_config.line_length / 2); 166 dma_set_source(1, PHYSICAL((uint32_t) vga_display->linedata + 167 display_config.line_length / 2), 168 display_config.line_length / 2); 169 dma_on(0); 170 dma_on(1); 171 } 172 173 /* Update the channels for the next line. */ 174 175 void update_visible(vga_display_t *vga_display) 176 { 177 dma_set_source(0, PHYSICAL((uint32_t) vga_display->linedata), 178 display_config.line_length / 2); 179 dma_set_source(1, PHYSICAL((uint32_t) vga_display->linedata + 180 display_config.line_length / 2), 181 display_config.line_length / 2); 182 } 183 184 /* Disable the channels for the next line. */ 185 186 void stop_visible(vga_display_t *vga_display) 187 { 188 dma_off(0); 189 dma_off(1); 190 } 191 192 /* Bring vsync low (single compare, output driven low) when the next line 193 starts. */ 194 195 void vsync_low(void) 196 { 197 oc_init(2, 0b010, 2); 198 oc_on(2); 199 } 200 201 /* Bring vsync high (single compare, output driven high) when the next line 202 starts. */ 203 204 void vsync_high(void) 205 { 206 oc_init(2, 0b001, 2); 207 oc_on(2); 208 } 209 210 211 212 /* Peripheral pin configuration. */ 213 214 void config_oc(void) 215 { 216 /* Map OC1 to RPA0. */ 217 218 REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */ 219 220 /* Map OC2 to RPA1. */ 221 222 REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */ 223 } 224 225 void config_uart(void) 226 { 227 /* Map U1RX to RPB13. */ 228 229 REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ 230 231 /* Map U1TX to RPB15. */ 232 233 REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ 234 235 /* Set RPB13 to input. */ 236 237 SET_REG(TRISB, 1 << 13); 238 }