
Card B
Three individual video card designs, built out of 74-series derivative CMOS logic. No CPLDs or FPGAs! The cards are specifically intended to be used with old-school computer systems (Z80, 6502, et al.) and have 5V TTL/CMOS, 8-bit microprocessor-compatible control interfaces.
This page is currently under construction and serves as a scrapbook page as I develop, test and verify these designs. So far I have only fully completed card B, which is pictured here displaying the pretty Mandelbrot. The schematic diagram and the zipped Gerber files for this card can be downloaded from the links provided above.
I'm currently waiting on the boards for Card C to arrive from the PCB manufacturer. Card A is still in the PCB layout phase. Comprehensive documentation / technical details / applications information
for all three cards will eventually be produced/provided and collated into a single document.



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// CCS C Compiler
// Demonstration mathematical and graphics drawing program for VGA card.
// Initial prototype code
// 21/04/2021
// Card B - 640x480x64C
// www.glensstuff.com
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <16f877A.h>
#include <MATH.H> // For sin and cos
#FUSES HS,NOWDT,PUT,NOPROTECT,NOLVP,NOCPD,NOWRT,BROWNOUT
#use delay(clock=20000000)
#use fast_io(A)
#use fast_io(B)
#use fast_io(C)
#use fast_io(D)
#use fast_io(E)
#define xoffset 320; // Screen center x
#define yoffset 240; // Screen center y
int C, D, E; // Bytes for ClearRAM routine
int32 address; // Video memory address
int8 colour; // Video memory data
int8 start;
int16 iterations;
int16 x1, y1, x2, y2; // DrawLine and DrawFill coordinates
int16 xx, yy; // Working variables for line algorithm
int32 x, y; // "
float deltaX, deltaY, deltaError, error; // "
float xxx, yyy, zzz, dx, dy, dz, radians, r, n; // Working variables for trig. and integration
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void WriteRam() // Routine to write pixel data to video RAM
{
address = (x + 1) + (y * 1024); // Calculate RAM address
output_C(make8(address, 0)); // Update address bus
output_D(make8(address, 1)); // "
output_E(make8(address, 2)); // "
while (!input(PIN_A4)); while (input(PIN_A4)); // Wait for negative edge of !H_BLANK
output_B(colour); // Put pixel data on data bus
output_low(pin_A0); // Assert !ACCESS_RAM
output_low(pin_A1); output_high(pin_A1); // Strobe !WRITE
output_high(pin_A0); // Un-assert !ACCESS_RAM
}
void QuadrantA() // Bresenham's line algorithm for x2 >= x1 & y2 >= y1
{
deltaX = (x2 - x1); deltaY = (y2 - y1);
if (deltaX >= deltaY) {
deltaError = (deltaY / deltaX); error = 0; y = y1;
for (x = x1; x <= x2; x++) {
WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
y++; error = error - 1;
}
}
}
else
{
deltaError = (deltaX / deltaY); error = 0; x = x1;
for (y = y1; y <= y2; y++) {
WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
x++; error = error - 1;
}
}
}
}
void QuadrantB() // Bresenham's line algorithm for x2 < x1 & y2 >= y1
{
deltaX = (x1 - x2); deltaY = (y2 - y1);
if (deltaX >= deltaY) {
deltaError = (deltaY / deltaX); error = 0; y = y1;
for (xx = (x1 + 1); xx > x2; xx--) {
x = (xx - 1); WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
y++; error = error - 1;
}
}
}
else
{
deltaError = (deltaX / deltaY); error = 0; x = x1;
for (y = y1; y <= y2; y++) {
WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
x--; error = error - 1;
}
}
}
}
void QuadrantC() // Bresenham's line algorithm for x2 < x1 & y2 < y1
{
deltaX = (x1 - x2); deltaY = (y1 - y2);
if (deltaX >= deltaY) {
deltaError = (deltaY / deltaX); error = 0; y = y1;
for (xx = (x1 + 1); xx > x2; xx--) {
x = (xx - 1); WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
y--; error = error - 1;
}
}
}
else
{
deltaError = (deltaX / deltaY); error = 0; x = x1;
for (yy = (y1 + 1); yy > y2; yy--) {
y = (yy - 1); WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
x--; error = error - 1;
}
}
}
}
void QuadrantD() // Bresenham's line algorithm for x2 >= x1 & y2 < y1
{
deltaX = (x2 - x1); deltaY = (y1 - y2);
if (deltaX >= deltaY) {
deltaError = (deltaY / deltaX); error = 0; y = y1;
for (x = x1; x <= x2; x++) {
WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
y--; error = error - 1;
}
}
}
else
{
deltaError = (deltaX / deltaY); error = 0; x = x1;
for (yy = (y1 + 1); yy > y2; yy--) {
y = (yy - 1); WriteRAM(); error = error + deltaError;
if (error >= 0.5) {
x++; error = error - 1;
}
}
}
}
void DrawLine() // Routine for plotting lines from start point (x1, y1) to
{ // end point (x2, y2)
if (x2 >= x1) {
if (y2 >= y1)
QuadrantA();
if (y2 < y1)
QuadrantD();
}
if (x2 < x1) {
if (y2 >= y1)
QuadrantB();
if (y2 < y1)
QuadrantC();
}
x1 = x; y1 = y; // Set start point of the next line to the end point of line
} // just plotted if x1 and y1 not redefined on the next call
void DrawFill() // Routine for drawing a rectangular fill.
{ // (x1, y1) = upper left corner. (x2, y2) = lower left corner
for (y = y1; y <= y2; y++) {
for (x = x1; x <= x2; x++) {
WriteRAM();
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ClearRAM() // Fast routine for clearing video memory
{ // Does not wait for !H_SYNC
C = 0; D = 0; E = 0; output_C(C); output_D(D); output_E(E); // Zero address bus
output_B(0); // Data bus = 0x00 for black
output_low(pin_A0); // Assert !ACCESS_RAM
// Video is hardware blanked when !ACCESS_RAM low
while (E < 8) {
output_low(pin_A1); output_high(pin_A1); // Strobe !WRITE
C++; output_C(C); // Increment through and update address
if (C == 0) { // "
D++; output_D(D); // "
if (D == 0) { // "
E++; output_E(E); // "
}
}
}
output_high(pin_A0); // Un-assert !ACCESS_RAM
}
void DrawPolarRoses() // Iterative overlaying polar rose plots for the
{ // form r = a sin(n(angle)), stepping n
start = 0; colour = 53;
for (n = 1; n <= 6; n = n + 0.5) {
for (radians = 0; radians <= 6.2832; radians = radians + 0.05) { // Step angle, range 0 through 360 (2pi) degrees
r = 200 * sin(n * radians); // Calculate r for polar rose function
x2 = (sin(radians) * r) + xoffset; // Polar coordinates to Cartesian coordinates, x
y2 = (cos(radians) * r) + yoffset; // Polar coordinates to Cartesian coordinates, y
if (start == 0) {
start = 1; x1 = x2; y1 = y2; // Set initial starting coordinate for DrawLine
}
else
DrawLine();
}
colour++; // Plot next rose in next colour of palette
}
}
void DrawLinePattern()
{
colour = 0x0B; x2 = 0; y2 = 0;
while (x2 < 639) {
x1 = 0; y1 = 479; DrawLine(); x2 = x2 + 20;
}
colour = 0x0C;
While (y2 < 479) {
x1 = 0; y1 = 479; DrawLine(); y2 = y2 + 20;
}
x1 = 0; y1 = 479; y2 = 479; DrawLine();
colour = 0x3C; x2 = 639; y2 = 0; start = 0;
while (start <= 31) {
x1 = 639; y1 = 479; DrawLine(); x2 = x2 -20; start++;
}
colour = 0x0F; x2 = 0;
while (y2 <479) {
x1 = 639; y1 = 479; DrawLine(); y2 = y2 + 20;
}
x1 = 639; y1 = 479; y2 = 479; DrawLine();
}
void DrawCascadingSquares()
{
colour = 0; x1 = 0; y1 = 0;
while (colour <= 63) {
x2 = x1 + 72; y2 = y1 + 38; DrawFill();
x1 = x1 + 9; y1 = y1 + 7; colour++;
}
colour = 0; x1 = 567; y1 = 0;
while (colour <=63) {
x2 = x1 + 72; y2 = y1 + 38; DrawFill();
x1 = x1 - 9; y1 = y1 + 7; colour++;
}
}
void DrawChequerBoard()
{
colour = 1;
for (x1 = 120; x1 <= 480; x1 = x1 + 40) {
x2 = x1 + 39; colour = 1 - colour;
for (y1 = 40; y1 <= 400; y1 = y1 + 40) {
y2 = y1 + 39; DrawFill(); colour = 1 - colour;
}
}
}
void DrawSinXdivXfunction() // Iterative sin(x)/x plot with stepped variables in
{ // isometric projection
colour = 0x3F; start = 0; dy = 1; dz = 10; // Plotting colour = white and initial conditions
for (zzz = -100; zzz <= 100; zzz = zzz + 10) { // Step z
start = 0;
for (radians = -5; radians <= 5; radians = radians + 0.1) { // Step radians
xxx = (radians * 20); // Compute x coordinate
yyy = sin(radians * dy); // Compute y coordinate
yyy = -(dz * (yyy / (radians * dy))); // "
x2 = (xxx - zzz) + xoffset; // Isometric projection transform, x axis, 45 degrees
y2 = (yyy - (xxx + zzz)) + yoffset; // Isometric projection transform, y axis, 45 degrees
// Sin(45) = cos(45) so trig. omitted
if (start == 0) {
start = 1; x1 = x2; y1 = y2; // Set initial starting coordinate for DrawLine
}
else
DrawLine();
}
dy = dy + 0.2; // Linear step increase of ripple multiplier for next iteration
dz = dz * 1.12; // Exponential step increase of amplitude multiplier for next iteration
}
}
void DrawLandscape()
{
colour = 0x10; x1 = 0; y1 = 0; x2 = 639; y2 = 119; DrawFill(); // Draw blue and green screen fill
colour = 0x20; x1 = 0; y1 = 120; x2 = 639; y2 = 239; DrawFill(); // "
colour = 0x30; x1 = 0; y1 = 240; x2 = 639; y2 = 359; DrawFill(); // "
colour = 0x04; x1 = 0; y1 = 360; x2 = 639; y2 = 399; DrawFill(); // "
colour = 0x08; x1 = 0; y1 = 400; x2 = 639; y2 = 439; DrawFill(); // "
colour = 0x0C; x1 = 0; y1 = 439; x2 = 639; y2 = 479; DrawFill(); // "
colour = 0x03; // Draw red border
x1 = 0; y1 = 0; x2 = 639; y2 = 3; DrawFill(); // "
x1 = 636; y1 = 0; x2 = 639; y2 = 479; DrawFill(); // "
x1 = 0; y1 = 476; x2 = 639; y2 = 479; DrawFill(); // "
x1 = 0; y1 = 0; x2 = 3; y2 = 479; DrawFill(); // "
colour = 0x0F; // Draw sun
for (radians = 0; radians <= 6.28; radians = radians + 0.05) { // "
x1 = 100; y1 = 100; // "
x2 = 100 + (50 * sin(radians)); y2 = 100 + (50 * cos(radians)); // "
DrawLine(); // "
}
}
void ComputeAndPlotLorenzAttractor()
{
iterations = 0; start = 0; colour = 0x3F;
xxx = 1; yyy = 0; zzz = 20; // Initial conditions
while (iterations < 7000) {
dx = (-10 * xxx) + (10 * yyy); // Compute x
dy = (28 * xxx) - yyy - (xxx * zzz); // Compute y
dz = (-2.67 * zzz) + (xxx * yyy); // Compute z
xxx = xxx + (dx * 0.01); // Integration step x
yyy = yyy + (dy * 0.01); // Integration step y
zzz = zzz + (dz * 0.01); // Integration step z
x2 = (xxx * 12) + xoffset; // Line plotting coordinate x
y2 = (-zzz * 8) + 450; // Line plotting coordinate y
if (start == 0) {
start = 1; x1 = x2; y1 = y2; // Set initial starting coordinate for DrawLine
}
else {
DrawLine();
iterations++;
}
}
}
void WipeOut()
{
x1 = 0; y1 = 0; x2 = 639; y2 = 479; colour = 0x00;
DrawFill();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MAIN()
{
delay_ms(3000);
set_tris_A(0x10); set_tris_B(0xC0); set_tris_C(0x00); // Setup IO ports
set_tris_D(0x00); set_tris_E(0x00); // "
output_high(pin_A0); output_high(pin_A1); output_high(pin_A2); // !ACCESS_RAM, !WRITE & !READ = high
ClearRAM();
DrawPolarRoses();
DrawLinePattern();
DrawCascadingSquares();
DrawChequerBoard();
DrawSinXdivXfunction();
delay_ms(1000);
DrawLandscape();
ComputeAndPlotLorenzAttractor();
delay_ms(2000);
WipeOut();
}
