1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/examples/vga/main.c Sat Oct 20 19:24:34 2018 +0200
1.3 @@ -0,0 +1,297 @@
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 +#include "main.h"
1.28 +#include "vga.h"
1.29 +
1.30 +
1.31 +
1.32 +/* Display state. */
1.33 +
1.34 +static void (*state_handler)(void);
1.35 +static uint32_t line;
1.36 +
1.37 +/* Pixel data. */
1.38 +
1.39 +static uint8_t linedata[LINE_LENGTH];
1.40 +static const uint8_t zerodata[ZERO_LENGTH] = {0};
1.41 +
1.42 +
1.43 +
1.44 +static void test_linedata(void)
1.45 +{
1.46 + int i;
1.47 +
1.48 + for (i = 0; i < LINE_LENGTH; i++)
1.49 + linedata[i] = (i % 2) ? 0xff : 0x00;
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 + int i;
1.58 +
1.59 + /* Clear outputs (LED). */
1.60 +
1.61 + CLR_REG(port, pins);
1.62 +
1.63 + while (1)
1.64 + {
1.65 + counter = delay;
1.66 +
1.67 + while (counter--) __asm__(""); /* retain loop */
1.68 +
1.69 + /* Invert outputs (LED). */
1.70 +
1.71 + INV_REG(port, pins);
1.72 + for (i = 0; i < LINE_LENGTH; i++)
1.73 + hex(linedata[i], 1);
1.74 + uart_write_nl();
1.75 + }
1.76 +}
1.77 +
1.78 +
1.79 +
1.80 +/* Main program. */
1.81 +
1.82 +void main(void)
1.83 +{
1.84 + line = 0;
1.85 + state_handler = vbp_active;
1.86 + test_linedata();
1.87 +
1.88 + init_memory();
1.89 + init_pins();
1.90 + init_outputs();
1.91 +
1.92 + unlock_config();
1.93 + config_oc();
1.94 + config_uart();
1.95 + lock_config();
1.96 +
1.97 + init_dma();
1.98 +
1.99 + /* Initiate DMA on the Timer2 interrupt transferring line data to the first
1.100 + byte of PORTB. Do not enable the channel for initiation until the visible
1.101 + region is about to start. */
1.102 +
1.103 + dma_init(0, 3);
1.104 + dma_set_auto_enable(0, 1);
1.105 + dma_set_interrupt(0, T2, 1);
1.106 + dma_set_transfer(0, PHYSICAL((uint32_t) linedata), LINE_LENGTH,
1.107 + HW_PHYSICAL(PORTB), 1,
1.108 + LINE_LENGTH);
1.109 + dma_init_interrupt(0, 0b00001000, 1, 3);
1.110 +
1.111 + /* Enable DMA on the preceding channel's completion, with this also
1.112 + initiating transfers. */
1.113 +
1.114 + dma_init(1, 3);
1.115 + dma_set_chaining(1, dma_chain_previous);
1.116 + dma_set_interrupt(1, DMA0, 1);
1.117 + dma_set_transfer(1, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH,
1.118 + HW_PHYSICAL(PORTB), 1,
1.119 + ZERO_LENGTH);
1.120 + dma_set_receive_events(1, 1);
1.121 +
1.122 + /* Configure a timer for the horizontal sync. The timer has no prescaling
1.123 + (0). */
1.124 +
1.125 + timer_init(2, 0, HFREQ_LIMIT);
1.126 + timer_on(2);
1.127 +
1.128 + /* Horizontal sync. */
1.129 +
1.130 + /* Configure output compare in dual compare (continuous output) mode using
1.131 + Timer2 as time base. The interrupt condition drives the first DMA channel
1.132 + but is not handled (having a lower priority than the CPU). */
1.133 +
1.134 + oc_init(1, 0b101, 2);
1.135 + oc_set_pulse(1, HSYNC_END);
1.136 + oc_set_pulse_end(1, HSYNC_START);
1.137 + oc_init_interrupt(1, 7, 3);
1.138 + oc_on(1);
1.139 +
1.140 + /* Vertical sync. */
1.141 +
1.142 + /* Configure output compare in single compare (output driven low) mode using
1.143 + Timer2 as time base. The unit is enabled later. It is only really used to
1.144 + achieve precisely-timed level transitions in hardware. */
1.145 +
1.146 + oc_init(2, 0b010, 2);
1.147 + oc_set_pulse(2, 0);
1.148 +
1.149 + uart_init(1, 115200);
1.150 + uart_on(1);
1.151 +
1.152 + interrupts_on();
1.153 +
1.154 + blink(3 << 24, PORTA, 1 << 3);
1.155 +}
1.156 +
1.157 +
1.158 +
1.159 +/* Exception and interrupt handlers. */
1.160 +
1.161 +void exception_handler(void)
1.162 +{
1.163 + blink(3 << 12, PORTA, 1 << 3);
1.164 +}
1.165 +
1.166 +void interrupt_handler(void)
1.167 +{
1.168 + uint32_t ifs;
1.169 +
1.170 + /* Check for a OC1 interrupt condition. */
1.171 +
1.172 + ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF);
1.173 +
1.174 + if (ifs)
1.175 + {
1.176 + line += 1;
1.177 + state_handler();
1.178 + CLR_REG(OCIFS, ifs);
1.179 + }
1.180 +}
1.181 +
1.182 +
1.183 +
1.184 +/* Vertical back porch region. */
1.185 +
1.186 +void vbp_active(void)
1.187 +{
1.188 + if (line < VISIBLE_START)
1.189 + return;
1.190 +
1.191 + /* Enter the visible region. */
1.192 +
1.193 + state_handler = visible_active;
1.194 +
1.195 + /* NOTE: Set the line address. */
1.196 +
1.197 + /* Enable the channel for the next line. */
1.198 +
1.199 + dma_on(0);
1.200 +}
1.201 +
1.202 +/* Visible region. */
1.203 +
1.204 +void visible_active(void)
1.205 +{
1.206 + uint32_t ifs;
1.207 +
1.208 + /* Remove any DMA interrupt condition (CHBCIF). */
1.209 +
1.210 + ifs = REG(DMAIFS) & DMA_INT_FLAGS(0, DCHxIF);
1.211 +
1.212 + if (ifs)
1.213 + {
1.214 + CLR_REG(DMA_REG(0, DCHxINT), 0b11111111);
1.215 + CLR_REG(DMAIFS, ifs);
1.216 + INV_REG(PORTA, 1 << 2);
1.217 + }
1.218 +
1.219 + if (line < VFP_START)
1.220 + {
1.221 + /* NOTE: Update the line address and handle wraparound. */
1.222 +
1.223 + return;
1.224 + }
1.225 +
1.226 + /* End the visible region. */
1.227 +
1.228 + state_handler = vfp_active;
1.229 +
1.230 + /* Disable the channel for the next line. */
1.231 +
1.232 + dma_off(0);
1.233 +}
1.234 +
1.235 +/* Vertical front porch region. */
1.236 +
1.237 +void vfp_active(void)
1.238 +{
1.239 + if (line < VSYNC_START)
1.240 + return;
1.241 +
1.242 + /* Enter the vertical sync region. */
1.243 +
1.244 + state_handler = vsync_active;
1.245 +
1.246 + /* Bring vsync low (single compare, output driven low) when the next line
1.247 + starts. */
1.248 +
1.249 + oc_init(2, 0b010, 2);
1.250 + oc_on(2);
1.251 +}
1.252 +
1.253 +/* Vertical sync region. */
1.254 +
1.255 +void vsync_active(void)
1.256 +{
1.257 + if (line < VSYNC_END)
1.258 + return;
1.259 +
1.260 + /* Start again at the top of the display. */
1.261 +
1.262 + line = 0;
1.263 + state_handler = vbp_active;
1.264 +
1.265 + /* Bring vsync high (single compare, output driven high) when the next line
1.266 + starts. */
1.267 +
1.268 + oc_init(2, 0b001, 2);
1.269 + oc_on(2);
1.270 +}
1.271 +
1.272 +
1.273 +
1.274 +/* Peripheral pin configuration. */
1.275 +
1.276 +void config_oc(void)
1.277 +{
1.278 + /* Map OC1 to RPA0. */
1.279 +
1.280 + REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */
1.281 +
1.282 + /* Map OC2 to RPA1. */
1.283 +
1.284 + REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */
1.285 +}
1.286 +
1.287 +void config_uart(void)
1.288 +{
1.289 + /* Map U1RX to RPB13. */
1.290 +
1.291 + REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */
1.292 +
1.293 + /* Map U1TX to RPB15. */
1.294 +
1.295 + REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */
1.296 +
1.297 + /* Set RPB13 to input. */
1.298 +
1.299 + SET_REG(TRISB, 1 << 13);
1.300 +}