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