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.
Unlike the vga example, but in common with the vga-dual example, this example employs two DMA channels for pixel data which are interleaved to investigate a potential remedy for the wide pixel effect. This seems to preserve consistent pixel widths only with a transfer cell size of 1: other cell sizes suffer from the wide pixel problem. Despite not offering the greater throughput of larger cell sizes, merely employing dual channels increases throughput for a cell size of 1, making the technique worth using.
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 | DMA1 | zerodata -> PORTB |
Timer3 | DMA0 | linedata -> PORTB |
Timer3 | DMA2 | linedata -> PORTB |
Timer3 | DMA3 | zerodata -> PORTB |
The real purpose of this channel (DMA1) is to capture the Timer2 interrupt condition and to enable the following channels (DMA0, DMA2) through channel chaining. Having been enabled, DMA0 and DMA2 are then able to conduct transfers at a tempo dictated by Timer3. Finally, DMA3 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:
The pin usage of this solution is documented below.
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/PGED2 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).
UART1 is exposed by the RB13 and RB15 pins.
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