1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/lib/vga_display_cpu.c Fri Nov 02 02:15:09 2018 +0100
1.3 @@ -0,0 +1,242 @@
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 +#include "pic32_c.h"
1.24 +#include "init.h"
1.25 +#include "vga_display_cpu.h"
1.26 +
1.27 +
1.28 +
1.29 +/* Display state. */
1.30 +
1.31 +vga_display_t vga_display;
1.32 +
1.33 +
1.34 +
1.35 +/* Initialise the state machine. */
1.36 +
1.37 +void init_vga(display_config_t *display_config, int line_timer)
1.38 +{
1.39 + /* Display parameters. */
1.40 +
1.41 + vga_display.display_config = display_config;
1.42 +
1.43 + /* Initial state. */
1.44 +
1.45 + vga_display.state_handler = vbp_active;
1.46 + vga_display.line = 0;
1.47 +
1.48 + /* Configure a general display timer to start line data transfer and for the
1.49 + horizontal sync. */
1.50 +
1.51 + vga_display.line_timer = line_timer;
1.52 +}
1.53 +
1.54 +/* Initialise a separate transfer timer if different from the general display
1.55 + timer. */
1.56 +
1.57 +void init_vga_with_timer(display_config_t *display_config, int line_timer)
1.58 +{
1.59 + /* Initialise the basic properties of the display. */
1.60 +
1.61 + init_vga(display_config, line_timer);
1.62 +
1.63 + /* Configure a line timer for horizontal sync and line data transfers. The
1.64 + interrupt is handled to produce line data and to drive the display state
1.65 + machine. */
1.66 +
1.67 + /* The timer has no prescaling (0). */
1.68 +
1.69 + timer_init(line_timer, 0, display_config->hfreq_limit);
1.70 + timer_init_interrupt(line_timer, 7, 3);
1.71 + timer_on(line_timer);
1.72 +}
1.73 +
1.74 +
1.75 +
1.76 +/* Configure the output port. */
1.77 +
1.78 +void vga_configure_transfer(uint32_t output)
1.79 +{
1.80 + vga_display.output = output;
1.81 +}
1.82 +
1.83 +/* Configure output compare units for horizontal and vertical sync. */
1.84 +
1.85 +void vga_configure_sync(int hsync_unit, int vsync_unit)
1.86 +{
1.87 + /* Record the peripherals in use. */
1.88 +
1.89 + vga_display.hsync_unit = hsync_unit;
1.90 + vga_display.vsync_unit = vsync_unit;
1.91 +
1.92 + /* Horizontal sync. */
1.93 +
1.94 + /* Configure output compare in dual compare (continuous output) mode using
1.95 + the timer as time base. */
1.96 +
1.97 + oc_init(hsync_unit, 0b101, vga_display.line_timer);
1.98 + oc_set_pulse(hsync_unit, vga_display.display_config->hsync_end);
1.99 + oc_set_pulse_end(hsync_unit, vga_display.display_config->hsync_start);
1.100 + oc_init_interrupt(hsync_unit, 7, 3);
1.101 + oc_on(hsync_unit);
1.102 +
1.103 + /* Vertical sync. */
1.104 +
1.105 + /* Configure output compare in single compare (output driven low) mode using
1.106 + the timer as time base. The unit is enabled later. It is only really used
1.107 + to achieve precisely-timed level transitions in hardware. */
1.108 +
1.109 + oc_init(vsync_unit, 0b010, vga_display.line_timer);
1.110 + oc_set_pulse(vsync_unit, 0);
1.111 +}
1.112 +
1.113 +
1.114 +
1.115 +/* Interrupt handlers. */
1.116 +
1.117 +void vga_interrupt_handler(void)
1.118 +{
1.119 + vga_display.line += 1;
1.120 + vga_display.state_handler();
1.121 +}
1.122 +
1.123 +/* Visible region pixel output handler. */
1.124 +
1.125 +void vga_transfer_interrupt_handler(void)
1.126 +{
1.127 + display_config_t *cfg = vga_display.display_config;
1.128 + uint8_t *current, *end, *output;
1.129 +
1.130 + if (vga_display.state_handler != visible_active)
1.131 + return;
1.132 +
1.133 + /* Generate the pixel signal. */
1.134 +
1.135 + output = (uint8_t *) vga_display.output;
1.136 + end = vga_display.linedata + cfg->line_length;
1.137 +
1.138 + /* This is potentially not as efficient as loading words and shifting bytes
1.139 + but it appears difficult to implement that approach without experiencing
1.140 + data load exceptions. */
1.141 +
1.142 + for (current = vga_display.linedata; current < end; current++)
1.143 + REG(output) = *current;
1.144 +
1.145 + /* Reset the signal level. */
1.146 +
1.147 + REG(output) = 0;
1.148 +}
1.149 +
1.150 +
1.151 +
1.152 +/* Vertical back porch region. */
1.153 +
1.154 +void vbp_active(void)
1.155 +{
1.156 + if (vga_display.line < vga_display.display_config->visible_start)
1.157 + return;
1.158 +
1.159 + /* Enter the visible region. */
1.160 +
1.161 + vga_display.state_handler = visible_active;
1.162 +
1.163 + /* Set the line address. */
1.164 +
1.165 + vga_display.linedata = vga_display.display_config->screen_start;
1.166 +}
1.167 +
1.168 +/* Visible region. */
1.169 +
1.170 +void visible_active(void)
1.171 +{
1.172 + display_config_t *cfg = vga_display.display_config;
1.173 +
1.174 + if (vga_display.line < cfg->vfp_start)
1.175 + {
1.176 + /* Update the line address and handle wraparound. */
1.177 +
1.178 + if (!(vga_display.line % cfg->line_multiplier))
1.179 + {
1.180 + vga_display.linedata += cfg->line_length;
1.181 +
1.182 + if (vga_display.linedata >= cfg->screen_limit)
1.183 + vga_display.linedata -= cfg->screen_size;
1.184 + }
1.185 +
1.186 + return;
1.187 + }
1.188 +
1.189 + /* End the visible region. */
1.190 +
1.191 + vga_display.state_handler = vfp_active;
1.192 +}
1.193 +
1.194 +/* Vertical front porch region. */
1.195 +
1.196 +void vfp_active(void)
1.197 +{
1.198 + if (vga_display.line < vga_display.display_config->vsync_start)
1.199 + return;
1.200 +
1.201 + /* Enter the vertical sync region. */
1.202 +
1.203 + vga_display.state_handler = vsync_active;
1.204 +
1.205 + /* Bring vsync low when the next line starts. */
1.206 +
1.207 + vsync_low();
1.208 +}
1.209 +
1.210 +/* Vertical sync region. */
1.211 +
1.212 +void vsync_active(void)
1.213 +{
1.214 + if (vga_display.line < vga_display.display_config->vsync_end)
1.215 + return;
1.216 +
1.217 + /* Start again at the top of the display. */
1.218 +
1.219 + vga_display.line = 0;
1.220 + vga_display.state_handler = vbp_active;
1.221 +
1.222 + /* Bring vsync high when the next line starts. */
1.223 +
1.224 + vsync_high();
1.225 +}
1.226 +
1.227 +
1.228 +
1.229 +/* Bring vsync low (single compare, output driven low) when the next line
1.230 + starts. */
1.231 +
1.232 +void vsync_low(void)
1.233 +{
1.234 + oc_init(vga_display.vsync_unit, 0b010, vga_display.line_timer);
1.235 + oc_on(vga_display.vsync_unit);
1.236 +}
1.237 +
1.238 +/* Bring vsync high (single compare, output driven high) when the next line
1.239 + starts. */
1.240 +
1.241 +void vsync_high(void)
1.242 +{
1.243 + oc_init(vga_display.vsync_unit, 0b001, vga_display.line_timer);
1.244 + oc_on(vga_display.vsync_unit);
1.245 +}