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