# HG changeset patch # User Paul Boddie # Date 1540228353 -7200 # Node ID 2500eefe10f979838fb85b1838bdb1aa312c84ca # Parent d2d95a8a5dfe30bbef60dc94b2882ae3b80be739 Added a VGA example variant employing timer-scheduled single-pixel transfers. diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/Makefile Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,33 @@ +# Makefile - Build the PIC32 deployment payload +# +# Copyright (C) 2015, 2017, 2018 Paul Boddie +# Copyright (C) Xiangfu Liu +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +include ../../mk/common.mk + +TARGET = vga.elf +DUMP = $(TARGET:.elf=.dump) +MAP = $(TARGET:.elf=.map) + +HEX = $(TARGET:.elf=.hex) +SREC = $(TARGET:.elf=.srec) + +# Ordering of objects is important and cannot be left to replacement rules. + +SRC = $(START_SRC) main.c $(COMMON_SRC) +OBJ = $(START_OBJ) main.o $(COMMON_OBJ) + +include ../../mk/rules.mk diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/README.txt Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,135 @@ +Introduction +------------ + +This example demonstrates the generation of an analogue VGA signal from a +PIC32 microcontroller using general output pins. Unlike the vga and vga-pmp +examples, it employs a regular interrupt condition to schedule single-byte +(single-pixel) DMA transfers instead of a single whole-line transfer. + +The principal advantage of this method over the whole-line transfer method is +its production of pixels with consistent widths. The principal disadvantage is +the significant loss of horizontal resolution due to the latencies involved in +propagating interrupt conditions to the DMA controller and thereby initiating +each transfer. + +Employing a peripheral clock that has half the frequency of the system clock +should ensure the stability of the picture, since the lower frequency may make +transfers easier to schedule. The peripheral clock should provide a more +forgiving deadline for each transfer, permitting late transfers to complete on +time. + +Meanwhile, matching the system and peripheral clock frequencies appears to +leave the scheduling of transfers open to uncertainty, with transfers being +more readily delayed by other activity in the system, and with instability of +the picture being the result. + +In contrast to the vga and vga-pmp examples, a special DMA channel is employed +to initiate the pixel transfer process without actually transferring any pixel +data itself. The channel arrangement is as follows: + + Transfer Initiator DMA Channel Transfer Activity + ------------------ ----------- ----------------- + Timer2 DMA0 zerodata -> PORTB + Timer3 DMA1 linedata -> PORTB + DMA1 (completion) DMA2 zerodata -> PORTB + +The real purpose of this channel (DMA0) is to capture the Timer2 interrupt +condition and to enable the following channel (DMA1) through channel chaining. +Having been enabled, DMA1 is then able to conduct transfers at a tempo +dictated by Timer3. Finally, DMA2 acts as the "reset" or "zero" channel to +ensure that the pixel level is set to black at the end of each display line. + +In principle, other initiating conditions can be used instead of Timer3, which +is configured to produce such conditions as frequently as possible: + + * A persistent interrupt condition can be employed instead. For example, + configuring UART2 and setting the UART2 transfer interrupt, employing this + interrupt condition for DMA1, produces the same effect. + + * An external interrupt such as INT2 can be configured, and the peripheral + clock can be routed through the CLKO pin and back into the microcontroller + via an appropriate pin. With INT2 being employed as the interrupt + condition for DMA1, the same effect is produced. + +Hardware Details +================ + +The pin usage of this solution is documented below. + +PIC32MX270F256B-50I/SP Pin Assignments +-------------------------------------- + +MCLR# 1 \/ 28 +HSYNC/OC1/RA0 2 27 +VSYNC/OC2/RA1 3 26 RB15/U1TX + D0/RB0 4 25 RB14 + D1/RB1 5 24 RB13/U1RX + D2/RB2 6 23 + D3/RB3 7 22 RB11/PGEC2 + 8 21 RB10/PGEC3 + RA2 9 20 + RA3 10 19 + D4/RB4 11 18 RB9 + 12 17 RB8 + 13 16 RB7/D7 + D5/RB5 14 15 + +Note that RB6 is not available on pin 15 on this device (it is needed for VBUS +unlike the MX170 variant). + +UART Connections +---------------- + +UART1 is exposed by the RB13 and RB15 pins. + +Data Signal Routing +------------------- + +For one bit of intensity, two bits per colour channel: + +D7 -> 2200R -> I + +I -> diode -> R +I -> diode -> G +I -> diode -> B + +D6 (not connected) + +D5 -> 470R -> R +D4 -> 1000R -> R +D3 -> 470R -> G +D2 -> 1000R -> G +D1 -> 470R -> B +D0 -> 1000R -> B + +HSYNC -> HS +VSYNC -> VS + +Output Socket Pinout +-------------------- + + 5 (GND) 4 (NC) 3 (B) 2 (G) 1 (R) + + 10 (GND) 9 (NC) 8 (GND) 7 (GND) 6 (GND) + + 15 (NC) 14 (VS) 13 (HS) 12 (NC) 11 (NC) + +Output Cable Pinout +------------------- + + 1 (R) 2 (G) 3 (B) 4 (NC) 5 (GND) + + 6 (GND) 7 (GND) 8 (GND) 9 (NC) 10 (GND) + + 11 (NC) 12 (NC) 13 (HS) 14 (VS) 15 (NC) + +References +---------- + +https://en.wikipedia.org/wiki/VGA_connector + +http://papilio.cc/index.php?n=Papilio.VGAWing + +http://lucidscience.com/pro-vga%20video%20generator-2.aspx + +https://sites.google.com/site/h2obsession/CBM/C128/rgbi-to-vga diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/devconfig.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/devconfig.h Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,63 @@ +/* + * Device configuration. + * + * Copyright (C) 2018 Paul Boddie + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include "pic32.h" + +/* +Set the oscillator to be the FRC oscillator with PLL, with peripheral clock +divided by 2 (FPBDIV), and FRCDIV+PLL selected (FNOSC). + +The watchdog timer (FWDTEN) is also disabled. + +The secondary oscillator pin (FSOSCEN) is disabled to avoid pin conflicts with +RPB4. +*/ + +#define DEVCFG1_CONFIG (DEVCFG1_FWDTEN_OFF | DEVCFG1_FPBDIV_2 | \ + DEVCFG1_OSCIOFNC_OFF | DEVCFG1_FSOSCEN_OFF | \ + DEVCFG1_FNOSC_FRCDIV_PLL) + +/* +Set the FRC oscillator PLL function with an input division of 2, an output +division of 2, a multiplication of 24, yielding a multiplication of 6. + +The FRC is apparently at 8MHz but enforces input division of 2 to produce a +frequency in the acceptable range from 4MHz to 5MHz for the PLL: + +8MHz / 2 = 4MHz + +Multiplication and output division should produce a system clock of 48MHz: + +4MHz * 24 / 2 = 48MHz +*/ + +#define DEVCFG2_CONFIG (DEVCFG2_FPLLODIV_2 | DEVCFG2_FPLLMUL_24 | \ + DEVCFG2_FPLLIDIV_2) + +/* +The peripheral clock frequency (FPB) will be 24MHz given the above DEVCFG1 and +DEVCFG2 settings. +*/ + +#define FPB 24000000 + +#endif /* __CONFIG_H__ */ diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/main.c Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,319 @@ +/* + * Generate a VGA signal using a PIC32 microcontroller. + * + * Copyright (C) 2017, 2018 Paul Boddie + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "pic32_c.h" +#include "init.h" +#include "debug.h" +#include "main.h" +#include "vga.h" + + + +/* Display state. */ + +static void (*state_handler)(void); +static uint32_t line; + +/* Pixel data. */ + +static uint8_t linedata[LINE_LENGTH]; +static const uint8_t zerodata[ZERO_LENGTH] = {0}; + + + +static void test_linedata(void) +{ + int i; + + for (i = 0; i < LINE_LENGTH; i++) + linedata[i] = (i % 2) ? 0xff : 0x00; +} + +/* Blink an attached LED with delays implemented using a loop. */ + +static void blink(uint32_t delay, uint32_t port, uint32_t pins) +{ + uint32_t counter; + + /* Clear outputs (LED). */ + + CLR_REG(port, pins); + + while (1) + { + counter = delay; + + while (counter--) __asm__(""); /* retain loop */ + + /* Invert outputs (LED). */ + + INV_REG(port, pins); + } +} + + + +/* Main program. */ + +void main(void) +{ + line = 0; + state_handler = vbp_active; + test_linedata(); + + init_memory(); + init_pins(); + init_outputs(); + + unlock_config(); + config_oc(); + config_uart(); + lock_config(); + + init_dma(); + + /* Peripheral relationships: + + Timer2 -> OC1 + -> OC2 (vertical sync region) + -> DMA0: zerodata -> PORTB (visible region) + | + Timer3 -> DMA1: linedata -> PORTB + | + DMA1 -> DMA2: zerodata -> PORTB + */ + + /* Initiate DMA on the Timer2 interrupt condition, transferring line data to + the first byte of PORTB. Do not enable the channel for initiation until + the visible region is about to start. */ + + dma_init(0, 3); + dma_set_auto_enable(0, 1); + dma_set_interrupt(0, T2, 1); + dma_set_transfer(0, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, + HW_PHYSICAL(PORTB), 1, + ZERO_LENGTH); + + /* Enable DMA on the preceding channel's completion, with the Timer3 + interrupt condition initiating transfers. */ + + dma_init(1, 3); + dma_set_chaining(1, dma_chain_previous); + dma_set_interrupt(1, T3, 1); + dma_set_transfer(1, PHYSICAL((uint32_t) linedata), LINE_LENGTH, + HW_PHYSICAL(PORTB), 1, + 1); + dma_init_interrupt(1, 0b00001000, 1, 3); + + /* Enable DMA on the preceding channel's completion, with this also + initiating transfers. */ + + dma_init(2, 3); + dma_set_chaining(2, dma_chain_previous); + dma_set_interrupt(2, DMA1, 1); + dma_set_transfer(2, PHYSICAL((uint32_t) zerodata), ZERO_LENGTH, + HW_PHYSICAL(PORTB), 1, + ZERO_LENGTH); + dma_set_receive_events(2, 1); + + /* Configure a timer for the horizontal sync. The timer has no prescaling + (0). */ + + timer_init(2, 0, HFREQ_LIMIT); + timer_on(2); + + /* Configure a timer for line data transfers. */ + + timer_init(3, 0, 1); + timer_on(3); + + /* Horizontal sync. */ + + /* Configure output compare in dual compare (continuous output) mode using + Timer2 as time base. The interrupt condition drives the first DMA channel + and is handled to drive the display state machine. */ + + oc_init(1, 0b101, 2); + oc_set_pulse(1, HSYNC_END); + oc_set_pulse_end(1, HSYNC_START); + oc_init_interrupt(1, 7, 3); + oc_on(1); + + /* Vertical sync. */ + + /* Configure output compare in single compare (output driven low) mode using + Timer2 as time base. The unit is enabled later. It is only really used to + achieve precisely-timed level transitions in hardware. */ + + oc_init(2, 0b010, 2); + oc_set_pulse(2, 0); + + uart_init(1, 115200); + uart_on(1); + + interrupts_on(); + + blink(3 << 24, PORTA, 1 << 3); +} + + + +/* Exception and interrupt handlers. */ + +void exception_handler(void) +{ + blink(3 << 12, PORTA, 1 << 3); +} + +void interrupt_handler(void) +{ + uint32_t ifs; + + /* Check for a OC1 interrupt condition. */ + + ifs = REG(OCIFS) & OC_INT_FLAGS(1, OCxIF); + + if (ifs) + { + line += 1; + state_handler(); + CLR_REG(OCIFS, ifs); + } +} + + + +/* Vertical back porch region. */ + +void vbp_active(void) +{ + if (line < VISIBLE_START) + return; + + /* Enter the visible region. */ + + state_handler = visible_active; + + /* NOTE: Set the line address. */ + + /* Enable the channel for the next line. */ + + dma_on(0); +} + +/* Visible region. */ + +void visible_active(void) +{ + uint32_t ifs; + + /* Remove any DMA interrupt condition (CHBCIF). */ + + ifs = REG(DMAIFS) & DMA_INT_FLAGS(1, DCHxIF); + + if (ifs) + { + CLR_REG(DMA_REG(1, DCHxINT), 0b11111111); + CLR_REG(DMAIFS, ifs); + INV_REG(PORTA, 1 << 2); + } + + if (line < VFP_START) + { + /* NOTE: Update the line address and handle wraparound. */ + + return; + } + + /* End the visible region. */ + + state_handler = vfp_active; + + /* Disable the channel for the next line. */ + + dma_off(0); +} + +/* Vertical front porch region. */ + +void vfp_active(void) +{ + if (line < VSYNC_START) + return; + + /* Enter the vertical sync region. */ + + state_handler = vsync_active; + + /* Bring vsync low (single compare, output driven low) when the next line + starts. */ + + oc_init(2, 0b010, 2); + oc_on(2); +} + +/* Vertical sync region. */ + +void vsync_active(void) +{ + if (line < VSYNC_END) + return; + + /* Start again at the top of the display. */ + + line = 0; + state_handler = vbp_active; + + /* Bring vsync high (single compare, output driven high) when the next line + starts. */ + + oc_init(2, 0b001, 2); + oc_on(2); +} + + + +/* Peripheral pin configuration. */ + +void config_oc(void) +{ + /* Map OC1 to RPA0. */ + + REG(RPA0R) = 0b0101; /* RPA0R<3:0> = 0101 (OC1) */ + + /* Map OC2 to RPA1. */ + + REG(RPA1R) = 0b0101; /* RPA1R<3:0> = 0101 (OC2) */ +} + +void config_uart(void) +{ + /* Map U1RX to RPB13. */ + + REG(U1RXR) = 0b0011; /* U1RXR<3:0> = 0011 (RPB13) */ + + /* Map U1TX to RPB15. */ + + REG(RPB15R) = 0b0001; /* RPB15R<3:0> = 0001 (U1TX) */ + + /* Set RPB13 to input. */ + + SET_REG(TRISB, 1 << 13); +} diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/main.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/main.h Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,35 @@ +/* + * Generate a VGA signal using a PIC32 microcontroller. + * + * Copyright (C) 2017, 2018 Paul Boddie + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __MAIN_H__ +#define __MAIN_H__ + +/* Peripheral pin configuration. */ + +void config_oc(void); +void config_uart(void); + +/* Display state handlers. */ + +void vbp_active(void); +void visible_active(void); +void vfp_active(void); +void vsync_active(void); + +#endif /* __MAIN_H__ */ diff -r d2d95a8a5dfe -r 2500eefe10f9 examples/vga-timer/vga.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/vga-timer/vga.h Mon Oct 22 19:12:33 2018 +0200 @@ -0,0 +1,55 @@ +/* + * Generate a VGA signal using a PIC32 microcontroller. + * + * Copyright (C) 2017, 2018 Paul Boddie + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __VGA_H__ +#define __VGA_H__ + +#define LINE_LENGTH 80 /* pixels */ +#define LINE_COUNT 256 /* distinct display lines */ + +#define ZERO_LENGTH 1 /* pixels */ + +/* 24MHz cycle measurements. */ + +#define HFREQ_LIMIT 643 +#define HSYNC_START 523 +#define HSYNC_LIMIT 40 +#define HSYNC_END (HSYNC_START + HSYNC_LIMIT) + +/* Horizontal lines, back porch end. */ + +#define VISIBLE_START 70 +#define VFP_START (VISIBLE_START + 2 * LINE_COUNT) + +/* Horizontal lines, front porch end. */ + +#define VSYNC_START 620 + +/* Horizontal lines, back porch start. */ + +#define VSYNC_END 622 + +#define SCREEN_BASE 256 +#define SCREEN_SIZE (40 * 1024) +#define SCREEN_LIMIT (SCREEN_BASE + SCREEN_SIZE) + +#define SCREEN_BASE_KSEG0 (KSEG0_BASE + SCREEN_BASE) +#define SCREEN_LIMIT_KSEG0 (KSEG0_BASE + SCREEN_LIMIT) + +#endif /* __VGA_H__ */