1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/examples/vga-dual/main.c Tue Oct 23 21:34:54 2018 +0200
1.3 @@ -0,0 +1,311 @@
1.4 +/*
1.5 + * Generate a VGA signal using a PIC32 microcontroller.
1.6 + *
1.7 + * Copyright (C) 2017, 2018 Paul Boddie <paul@boddie.org.uk>
1.8 + *
1.9 + * This program is free software: you can redistribute it and/or modify
1.10 + * it under the terms of the GNU General Public License as published by
1.11 + * the Free Software Foundation, either version 3 of the License, or
1.12 + * (at your option) any later version.
1.13 + *
1.14 + * This program is distributed in the hope that it will be useful,
1.15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.17 + * GNU General Public License for more details.
1.18 + *
1.19 + * You should have received a copy of the GNU General Public License
1.20 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.21 + */
1.22 +
1.23 +
1.24 +#include "pic32_c.h"
1.25 +#include "init.h"
1.26 +#include "debug.h"
1.27 +
1.28 +/* Specific functionality. */
1.29 +
1.30 +#include "main.h"
1.31 +#include "vga.h"
1.32 +#include "display.h"
1.33 +
1.34 +
1.35 +
1.36 +/* Display state. */
1.37 +
1.38 +static void (*state_handler)(void);
1.39 +static uint32_t line;
1.40 +
1.41 +/* Pointers to pixel lines. */
1.42 +
1.43 +static uint8_t *linedata, *linedatalimit, *screenstart;
1.44 +
1.45 +/* Pixel data. */
1.46 +
1.47 +static const uint8_t zerodata[ZERO_LENGTH] = {0};
1.48 +static uint8_t framebuffer[SCREEN_SIZE];
1.49 +
1.50 +
1.51 +
1.52 +/* Blink an attached LED with delays implemented using a loop. */
1.53 +
1.54 +static void blink(uint32_t delay, uint32_t port, uint32_t pins)
1.55 +{
1.56 + uint32_t counter;
1.57 +
1.58 + /* Clear outputs (LED). */
1.59 +
1.60 + CLR_REG(port, pins);
1.61 +
1.62 + while (1)
1.63 + {
1.64 + counter = delay;
1.65 +
1.66 + while (counter--) __asm__(""); /* retain loop */
1.67 +
1.68 + /* Invert outputs (LED). */
1.69 +
1.70 + INV_REG(port, pins);
1.71 + }
1.72 +}
1.73 +
1.74 +
1.75 +
1.76 +/* Main program. */
1.77 +
1.78 +void main(void)
1.79 +{
1.80 + line = 0;
1.81 + state_handler = vbp_active;
1.82 + test_linedata(framebuffer);
1.83 +
1.84 + /* Initialise the current display line pointer and display limit. */
1.85 +
1.86 + linedatalimit = framebuffer + SCREEN_SIZE;
1.87 + screenstart = framebuffer;
1.88 +
1.89 + init_memory();
1.90 + init_pins();
1.91 + init_outputs();
1.92 +
1.93 + unlock_config();
1.94 + config_oc();
1.95 + config_uart();
1.96 + lock_config();
1.97 +
1.98 + init_dma();
1.99 +
1.100 + /* Initiate DMA on the Timer2 interrupt transferring line data to the first
1.101 + byte of PORTB. Do not enable the channels for initiation until the visible
1.102 + region is about to start.
1.103 +
1.104 + Here, two DMA channels are interleaved. This appears to provide a more
1.105 + stable picture. */
1.106 +
1.107 + dma_init(0, 3);
1.108 + dma_set_auto_enable(0, 1);
1.109 + dma_set_interrupt(0, T2, 1);
1.110 + dma_set_transfer(0, PHYSICAL((uint32_t) screenstart), LINE_LENGTH / 2,
1.111 + HW_PHYSICAL(PORTB), 1,
1.112 + TRANSFER_CELL_SIZE);
1.113 +
1.114 + dma_init(1, 3);
1.115 + dma_set_auto_enable(1, 1);
1.116 + dma_set_interrupt(1, T2, 1);
1.117 + dma_set_transfer(1, PHYSICAL((uint32_t) screenstart + LINE_LENGTH / 2), LINE_LENGTH / 2,
1.118 + HW_PHYSICAL(PORTB), 1,
1.119 + TRANSFER_CELL_SIZE);
1.120 +
1.121 + /* Enable DMA on the preceding channel's completion, with the timer event
1.122 + initiating the transfer. */
1.123 +
1.124 + dma_init(2, 3);
1.125 + dma_set_chaining(2, dma_chain_previous);
1.126 + dma_set_interrupt(2, T2, 1);
1.127 + dma_set_transfer(2, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH,
1.128 + HW_PHYSICAL(PORTB), 1,
1.129 + ZERO_LENGTH);
1.130 + dma_set_receive_events(2, 1);
1.131 +
1.132 + /* Configure a timer for the horizontal sync. The timer has no prescaling
1.133 + (0). */
1.134 +
1.135 + timer_init(2, 0, HFREQ_LIMIT);
1.136 + timer_on(2);
1.137 +
1.138 + /* Horizontal sync. */
1.139 +
1.140 + /* Configure output compare in dual compare (continuous output) mode using
1.141 + Timer2 as time base. The interrupt condition drives the first DMA channel
1.142 + and is handled to drive the display state machine. */
1.143 +
1.144 + oc_init(1, 0b101, 2);
1.145 + oc_set_pulse(1, HSYNC_END);
1.146 + oc_set_pulse_end(1, HSYNC_START);
1.147 + oc_init_interrupt(1, 7, 3);
1.148 + oc_on(1);
1.149 +
1.150 + /* Vertical sync. */
1.151 +
1.152 + /* Configure output compare in single compare (output driven low) mode using
1.153 + Timer2 as time base. The unit is enabled later. It is only really used to
1.154 + achieve precisely-timed level transitions in hardware. */
1.155 +
1.156 + oc_init(2, 0b010, 2);
1.157 + oc_set_pulse(2, 0);
1.158 +
1.159 + uart_init(1, 115200);
1.160 + uart_on(1);
1.161 +
1.162 + interrupts_on();
1.163 +
1.164 + blink(3 << 24, PORTA, 1 << 3);
1.165 +}
1.166 +
1.167 +
1.168 +
1.169 +/* Exception and interrupt handlers. */
1.170 +
1.171 +void exception_handler(void)
1.172 +{
1.173 + blink(3 << 12, PORTA, 1 << 3);
1.174 +}
1.175 +
1.176 +void interrupt_handler(void)
1.177 +{
1.178 + uint32_t ifs;
1.179 +
1.180 + /* Check for a OC1 interrupt condition. */
1.181 +
1.182 + ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF);
1.183 +
1.184 + if (ifs)
1.185 + {
1.186 + line += 1;
1.187 + state_handler();
1.188 + CLR_REG(OCIFS, ifs);
1.189 + }
1.190 +}
1.191 +
1.192 +
1.193 +
1.194 +/* Vertical back porch region. */
1.195 +
1.196 +void vbp_active(void)
1.197 +{
1.198 + if (line < VISIBLE_START)
1.199 + return;
1.200 +
1.201 + /* Enter the visible region. */
1.202 +
1.203 + state_handler = visible_active;
1.204 +
1.205 + /* Set the line address. */
1.206 +
1.207 + linedata = screenstart;
1.208 + dma_set_source(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH / 2);
1.209 + dma_set_source(1, PHYSICAL((uint32_t) linedata + LINE_LENGTH / 2), LINE_LENGTH / 2);
1.210 +
1.211 + /* Enable the channels for the next line. */
1.212 +
1.213 + dma_on(0);
1.214 + dma_on(1);
1.215 +}
1.216 +
1.217 +/* Visible region. */
1.218 +
1.219 +void visible_active(void)
1.220 +{
1.221 + INV_REG(PORTA, 1 << 2);
1.222 +
1.223 + if (line < VFP_START)
1.224 + {
1.225 + /* Update the line address and handle wraparound. */
1.226 +
1.227 + if (!(line % LINE_MULTIPLIER))
1.228 + {
1.229 + linedata += LINE_LENGTH;
1.230 + if (linedata >= linedatalimit)
1.231 + linedata -= SCREEN_SIZE;
1.232 + }
1.233 +
1.234 + dma_set_source(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH / 2);
1.235 + dma_set_source(1, PHYSICAL((uint32_t) linedata + LINE_LENGTH / 2), LINE_LENGTH / 2);
1.236 + return;
1.237 + }
1.238 +
1.239 + /* End the visible region. */
1.240 +
1.241 + state_handler = vfp_active;
1.242 +
1.243 + /* Disable the channels for the next line. */
1.244 +
1.245 + dma_off(0);
1.246 + dma_off(1);
1.247 +}
1.248 +
1.249 +/* Vertical front porch region. */
1.250 +
1.251 +void vfp_active(void)
1.252 +{
1.253 + if (line < VSYNC_START)
1.254 + return;
1.255 +
1.256 + /* Enter the vertical sync region. */
1.257 +
1.258 + state_handler = vsync_active;
1.259 +
1.260 + /* Bring vsync low (single compare, output driven low) when the next line
1.261 + starts. */
1.262 +
1.263 + oc_init(2, 0b010, 2);
1.264 + oc_on(2);
1.265 +}
1.266 +
1.267 +/* Vertical sync region. */
1.268 +
1.269 +void vsync_active(void)
1.270 +{
1.271 + if (line < VSYNC_END)
1.272 + return;
1.273 +
1.274 + /* Start again at the top of the display. */
1.275 +
1.276 + line = 0;
1.277 + state_handler = vbp_active;
1.278 +
1.279 + /* Bring vsync high (single compare, output driven high) when the next line
1.280 + starts. */
1.281 +
1.282 + oc_init(2, 0b001, 2);
1.283 + oc_on(2);
1.284 +}
1.285 +
1.286 +
1.287 +
1.288 +/* Peripheral pin configuration. */
1.289 +
1.290 +void config_oc(void)
1.291 +{
1.292 + /* Map OC1 to RPA0. */
1.293 +
1.294 + REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */
1.295 +
1.296 + /* Map OC2 to RPA1. */
1.297 +
1.298 + REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */
1.299 +}
1.300 +
1.301 +void config_uart(void)
1.302 +{
1.303 + /* Map U1RX to RPB13. */
1.304 +
1.305 + REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */
1.306 +
1.307 + /* Map U1TX to RPB15. */
1.308 +
1.309 + REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */
1.310 +
1.311 + /* Set RPB13 to input. */
1.312 +
1.313 + SET_REG(TRISB, 1 << 13);
1.314 +}