Main Site Documentation

My Gameo attempt


#1

I thought I would give this a try and it performs fairly well. It seems to be capable of slightly above a VIC-II chip without expanding the memory. I settled on a resolution of 192x128 which has enough ram left for 16 measures of 3ch music, 5x7 font and 8 sprites up to 16x16. There are 7 static sprites that are quickly drawn using a tile map then 1 sprite is reused multiple times a frame for the different animations.


#2

What is FEZ exactly doing and how are you communication between the 2 micros? Are you reusing any of the old project? Can we see some code? You have TV out or VGA? I still want to revive this project :slight_smile:


#3

I’ll post all the full code in a couple days when I finish.

It’s just using a TV right now because I’m using the bare chip and didn’t take time to wire VGA yet. The FEZ is doing everything, the Propeller is just constantly waiting for the next command. All data is loaded from the FEZ and the FEZ tells the Propeller what to do with it.

The connection is parallel but it was originally using serial and I programmed the parallel driver on the Propeller to act the same as serial so it’s interchangeable by swapping objects. The main reason originally wasn’t for speed although parallel is faster, it was because I could program parallel in a few lines of assembly that takes 1/4 the ram. The other connections are for the write enable, busy flag from the Propeller and a reset. The sound effects are coming from the FEZ and run through a passive mixer using a couple resistors.

This is the driver so far, it just needs some final adjustments to account for 16x16 tiles now that I know the Propeller has enough ram left. The Propeller code I’m using is mostly what was included with the SPIN IDE but with non essentials stripped out and a few changes.

Some commands use the lower nibble for extra data which reduces the total byte transfer count.


using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.IO;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Native;
using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.System;
using System.Collections;
using System.Runtime.InteropServices;
using Utility;

namespace PropTV {
    public sealed class GraphicsDevice {
        private enum Command {
            NOP = 0x00,
            ClearBuffer = 0x10,
            ClearRegion = 0x11,
            DrawBuffer = 0x12,

            SetColorMapAddr = 0x20,
            SetScreenTile = 0x21,

            DrawLine = 0x30,
            DrawPoint = 0x40,
            DrawQuad = 0x50,
            DrawSprite = 0x60,

            DefineTile = 0xA0,
            DrawTile = 0xB0,
            DrawTileMap = 0xC0,

            MusicPlay = 0xE0,
            MusicStop = 0xE1,
            MusicSet = 0xE2,

            DrawText = 0xF0,
        };

        #region Definition

        const int MaxMusicIndex = 512;

        public int MaxWidth {
            get {
                return MAX_WIDTH;
            }
        }
        private int MAX_WIDTH = 160;

        public int MaxHeight {
            get {
                return MAX_HEIGHT;
            }
        }
        private int MAX_HEIGHT = 96;

        public int Mode {
            get {
                return mode;
            }
        }
        private int mode = 0;

        public int TileSize {
            get {
                return TILE_SIZE;
            }
        }
        private int TILE_SIZE = 8;

        RLP.Procedure InitRLP;
        RLP.Procedure SetMatrixRLP;
        RLP.Procedure DrawQuadMeshRLP;
        RLP.Procedure DrawQuadBatchRLP;

        private float zoom = 80.0f;
        private float centerX;
        private float centerY;
        private float zNear = 5.0f;
        private float zFar = 2000.0f;
        private bool matrixDefined = false;//a null matrix in RLP will cause hard crash

        private ParallelPort InterfacePort;
        private InputPort BusyPin;
        private OutputPort ResetPin;

        private byte[] commandBuffer;

        #endregion

        #region Low level control and init

        public GraphicsDevice(int mode) {
            this.mode = mode;
            if (mode == 0) {
                //7 different tiles
                MAX_WIDTH = 160;
                MAX_HEIGHT = 96;
                TILE_SIZE = 8;
            } else if (mode == 1) {
                //7 different tiles
                MAX_WIDTH = 192;
                MAX_HEIGHT = 128;
                TILE_SIZE = 8;
            } else if (mode == 2) {
                //7 different tiles
                MAX_WIDTH = 256;
                MAX_HEIGHT = 192;
                TILE_SIZE = 8;
            } else if (mode == 3) {
                //no tilemap
                //single buffer
                MAX_WIDTH = 320;
                MAX_HEIGHT = 192;
                TILE_SIZE = 8;
            }

            RLP.Enable();
            RLP.Unlock(/*unlock*/);
            byte[] elf_file = Resources.GetBytes(Resources.BinaryResources.PropTV);
            RLP.LoadELF(elf_file);
            RLP.InitializeBSSRegion(elf_file);
            InitRLP = RLP.GetProcedure(elf_file, "Init");
            SetMatrixRLP = RLP.GetProcedure(elf_file, "SetMatrix");
            DrawQuadMeshRLP = RLP.GetProcedure(elf_file, "DrawQuadMesh");
            DrawQuadBatchRLP = RLP.GetProcedure(elf_file, "DrawQuadBatch");

            Debug.Print("elfSize = " + elf_file.Length.ToString());
            elf_file = null;
            Debug.GC(true);

            centerX = MAX_WIDTH / 2.0f;
            centerY = MAX_HEIGHT / 2.0f;

            Cpu.Pin[] pins = {
                        (Cpu.Pin)FEZ_Pin.Digital.Di10,
                        (Cpu.Pin)FEZ_Pin.Digital.Di3,
                        (Cpu.Pin)FEZ_Pin.Digital.Di4,
                        (Cpu.Pin)FEZ_Pin.Digital.Di5,
                        (Cpu.Pin)FEZ_Pin.Digital.Di6,
                        (Cpu.Pin)FEZ_Pin.Digital.Di7,
                        (Cpu.Pin)FEZ_Pin.Digital.Di8,
                        (Cpu.Pin)FEZ_Pin.Digital.Di9,
                             };

            byte[] RLPpins = new byte[10];
            for(int i=0;i<8;i++){
                RLPpins[i] = (byte)pins[i];
            }
            RLPpins[8] = (byte)FEZ_Pin.Digital.UEXT6;
            RLPpins[9] = (byte)FEZ_Pin.Digital.UEXT5;

            InitRLP.InvokeEx(RLPpins, MAX_WIDTH, MAX_HEIGHT);

            BusyPin = new InputPort((Cpu.Pin)FEZ_Pin.Digital.UEXT5, false, Port.ResistorMode.PullUp);
            ResetPin = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.An7, false);
            InterfacePort = new ParallelPort(pins, (Cpu.Pin)FEZ_Pin.Digital.UEXT6, Cpu.Pin.GPIO_NONE);

            RLPpins = null;
            pins = null;
            Debug.GC(true);

            commandBuffer = new byte[32];
            Array.Clear(commandBuffer, 0, 32);

            Reset();
            Clear();
        }

        //use Color4bit.GetBytes to set color bytes, address range is 0-63
        public void SetColorMapAddr(int Addr, byte Color0, byte Color1, byte Color2, byte Color3) {
            commandBuffer[0] = (byte)Command.SetColorMapAddr;
            commandBuffer[1] = (byte)(Addr & 0x3F);
            commandBuffer[2] = Color0;
            commandBuffer[3] = Color1;
            commandBuffer[4] = Color2;
            commandBuffer[5] = Color3;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 6);
            Thread.Sleep(1);
        }

        //assign color address 0-63 to screen tile @ 16x16 pixels
        //multiple tiles can use the same color address
        public bool SetScreenTile(int x, int y, int colorMapAddr) {
            if (x < 0 || y < 0 || x >= (MAX_WIDTH >> 4) || y >= (MAX_HEIGHT >> 4)) {
                return false;
            }
            commandBuffer[0] = (byte)Command.SetScreenTile;
            commandBuffer[1] = (byte)x;
            commandBuffer[2] = (byte)y;
            commandBuffer[3] = (byte)(colorMapAddr & 0x3F);
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 4);
            Thread.Sleep(1);

            return true;
        }

        //flood buffer with NOP to clear
        //any unrecognized command will pull the busy pin and flush the buffer
        public void Reset() {
            ResetPin.Write(true);
            Thread.Sleep(4000);
        }

        #endregion

        #region High level control

        //clear the draw buffer
        public void Clear() {
            commandBuffer[0] = (byte)Command.ClearBuffer;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 1);
        }

        //clear region by filling with background color
        public void ClearRegion(int minX, int minY, int maxX, int maxY) {
            commandBuffer[0] = (byte)Command.ClearRegion;
            commandBuffer[1] = (byte)minX;
            commandBuffer[2] = (byte)minY;
            commandBuffer[3] = (byte)maxX;
            commandBuffer[4] = (byte)maxY;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 5);
        }

        //copy draw buffer to screen
        public void DrawBuffer() {
            commandBuffer[0] = (byte)Command.DrawBuffer;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 1);
        }
        
        public void DrawPoint(int x, int y, int color, int size) {
            int[] point = new int[2];
            point[0] = x;
            point[1] = y;
            DrawPointMulti(point, color, size);
        }
        
        // draw multiple points using the same size and color
        public void DrawPointMulti(Vector2[] points, int color, int size) {
            commandBuffer[0] = (byte)Command.DrawPoint;
            commandBuffer[1] = (byte)color;
            commandBuffer[2] = (byte)size;
            commandBuffer[3] = (byte)points.Length;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 4);

            for (int i = 0; i < points.Length; i++) {
                commandBuffer[0] = (byte)points[i].X;
                commandBuffer[1] = (byte)points[i].Y;
                InterfacePort.Write(commandBuffer, 0, 2);
            }
        }

        // draw multiple points using the same size and color
        public void DrawPointMulti(int[] points, int color, int size) {
            commandBuffer[0] = (byte)Command.DrawPoint;
            commandBuffer[1] = (byte)color;
            commandBuffer[2] = (byte)size;
            commandBuffer[3] = (byte)(points.Length >> 1);
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 4);

            for (int i = 0; i < points.Length; i += 2) {
                commandBuffer[0] = (byte)points[i];
                commandBuffer[1] = (byte)points[i + 1];
                InterfacePort.Write(commandBuffer, 0, 2);
            }
        }

        //draw single line
        public void DrawLine(int x1, int y1, int x2, int y2, int color, int width) {
            int[] points = new int[4];
            points[0] = x1;
            points[1] = y1;
            points[2] = x2;
            points[3] = y2;
            DrawLineMulti(points, color, width, false);
        }

        //draw continuous line
        public void DrawLineMulti(Vector2[] points, int color, int width, bool closed) {
            commandBuffer[0] = (byte)Command.DrawLine;
            commandBuffer[1] = (byte)color;
            commandBuffer[2] = (byte)width;
            commandBuffer[3] = (byte)points.Length;
            if (closed) {
                commandBuffer[4] = (byte)1;
            } else {
                commandBuffer[4] = (byte)0;
            }
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 5);

            for (int i = 0; i < points.Length; i++) {
                commandBuffer[0] = (byte)points[i].X;
                commandBuffer[1] = (byte)points[i].Y;
                InterfacePort.Write(commandBuffer, 0, 2);
            }
        }

        //draw continuous line
        public void DrawLineMulti(int[] points, int color, int width, bool closed) {
            commandBuffer[0] = (byte)Command.DrawLine;
            commandBuffer[1] = (byte)color;
            commandBuffer[2] = (byte)width;
            commandBuffer[3] = (byte)(points.Length >> 1);
            if (closed) {
                commandBuffer[4] = (byte)1;
            } else {
                commandBuffer[4] = (byte)0;
            }
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 5);

            for (int i = 0; i < points.Length; i += 2) {
                commandBuffer[0] = (byte)points[i];
                commandBuffer[1] = (byte)points[i + 1];
                InterfacePort.Write(commandBuffer, 0, 2);
            }
        }

        //draw a filled quad and outline with line color
        //color > 3 will skip fill or line drawing
        public void DrawQuad(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4, int fillColor, int lineColor) {
            commandBuffer[0] = (byte)Command.DrawQuad;
            commandBuffer[1] = (byte)x1;
            commandBuffer[2] = (byte)y1;
            commandBuffer[3] = (byte)x2;
            commandBuffer[4] = (byte)y2;
            commandBuffer[5] = (byte)x3;
            commandBuffer[6] = (byte)y3;
            commandBuffer[7] = (byte)x4;
            commandBuffer[8] = (byte)y4;
            commandBuffer[9] = (byte)fillColor;
            commandBuffer[10] = (byte)lineColor;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 11);
        }
        
        //this is for animated sprites, may be used multiple times per frame for different sprites
        //rotation > 3 will flip horizontal
        public void DrawSprite(byte[] spriteData, int x, int y, int rot) {
            int[] point = new int[2];
            point[0] = x;
            point[1] = y;
            DrawSpriteMulti(spriteData, point, rot, false);
        }

        public void DrawSpriteMulti(byte[] spriteData, int[] points, int rot, bool clearFirst) {
            if (spriteData.Length > 16) {
                commandBuffer[0] = (byte)Command.DrawSprite | 0x01;
            } else {
                commandBuffer[0] = (byte)Command.DrawSprite;
            }
            commandBuffer[1] = (byte)(points.Length >> 1);
            commandBuffer[2] = (byte)rot;
            if (clearFirst) {
                commandBuffer[3] = (byte)1;
            } else {
                commandBuffer[3] = (byte)0;
            }

            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 4);
            InterfacePort.Write(spriteData, 0, 16);

            if (spriteData.Length > 16) {
                Thread.Sleep(1);
                InterfacePort.Write(spriteData, 16, 16);
                Thread.Sleep(1);
                InterfacePort.Write(spriteData, 32, 16);
                Thread.Sleep(1);
                InterfacePort.Write(spriteData, 48, 16);
            }

            for (int i = 0; i < (points.Length >> 1); i++) {
                int x = points[(i * 2)];
                int y = points[(i * 2) + 1];

                if (x >= 0) {
                    commandBuffer[0] = (byte)x;
                    commandBuffer[2] = 0;
                } else {
                    commandBuffer[0] = 0;
                    commandBuffer[2] = (byte)(x * -1);
                }

                if (y >= 0) {
                    commandBuffer[1] = (byte)y;
                    commandBuffer[3] = 0;
                } else {
                    commandBuffer[1] = 0;
                    commandBuffer[3] = (byte)(y * -1);
                }
                InterfacePort.Write(commandBuffer, 0, 4);
            }
            Thread.Sleep(1);
        }

        //set position and draw string, limit to MAX_WIDTH / 6
        //the null is automatically added at the end
        public void Print(int posX, int posY, int color, string _string, bool clearFirst) {
            if (clearFirst) {
                int clearsize = _string.Length * 6;
                ClearRegion(posX, posX, clearsize, posX + 8);
            }
            if (_string.Length > 40) {
                return;
            }
            commandBuffer[0] = (byte)Command.DrawText;
            commandBuffer[1] = (byte)posX;
            commandBuffer[2] = (byte)posY;
            commandBuffer[3] = (byte)color;

            InterfacePort.Write(commandBuffer, 0, 4);

            for (int i = 0; i < _string.Length; i++) {
                commandBuffer[0] = (byte)_string[i];
                InterfacePort.Write(commandBuffer, 0, 1);
            }

            commandBuffer[0] = (byte)0;
            InterfacePort.Write(commandBuffer, 0, 1);
        }

        #endregion

        #region Tiles
        //define tile to use in tile map 0=tile1, 0-6
        public void DefineTile(string fileName, int tileSize, int tileNum) {
            DefineTile(FileManager.LoadBinary(fileName, tileSize), tileNum);
        }

        public void DefineTile(byte[] tileData, int tileNum) {
            commandBuffer[0] = (byte)((byte)Command.DefineTile | (tileNum & 0x0F));
            if (tileData.Length > 16) {
                commandBuffer[1] = 1;
            } else {
                commandBuffer[1] = 0;
            }
            Array.Copy(tileData, 0, commandBuffer, 2, 16);
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 18);

            if (tileData.Length > 16) {
                Thread.Sleep(1);
                InterfacePort.Write(tileData, 16, 16);
                Thread.Sleep(1);
                InterfacePort.Write(tileData, 32, 16);
                Thread.Sleep(1);
                InterfacePort.Write(tileData, 48, 16);
            }
        }

        public void DrawTile(int x, int y, int tileNum) {
            commandBuffer[0] = (byte)((byte)Command.DrawTile | (byte)tileNum);
            if (x >= 0) {
                commandBuffer[1] = (byte)x;
                commandBuffer[3] = 0;
            } else {
                commandBuffer[1] = 0;
                commandBuffer[3] = (byte)(x * -1);
            }

            if (y >= 0) {
                commandBuffer[2] = (byte)y;
                commandBuffer[4] = 0;
            } else {
                commandBuffer[2] = 0;
                commandBuffer[4] = (byte)(y * -1);
            }

            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 5);
        }

        //clear and draw tile map region
        public void DrawTileMap(byte[] tileMap, int mapTotalWidth, Rectangle updateRect, int offsetX, bool clearFirst) {
            DrawTileMap(tileMap, mapTotalWidth, updateRect.Left, updateRect.Bottom, updateRect.Right, updateRect.Top, offsetX, clearFirst);
        }

        //mapTotalWidth = total horizontal tiles in tileMap
        //min-max values are in tile count
        //offsetX is always positive as pixels
        //clearFirst is used for partial redraws
        public void DrawTileMap(byte[] tileMap, int mapTotalWidth, int minX, int minY, int maxX, int maxY, int offsetX, bool clearFirst) {
            int maxOffset = (mapTotalWidth << 3) - MAX_WIDTH + 8;
            if (offsetX > maxOffset) {
                offsetX = maxOffset;
            }

            if (offsetX < 0) {
                offsetX = 0;
            }

            //must be positive for bit rounding
            if (minX < 0) minX = 0;
            if (minY < 0) minY = 0;
            if (maxX < 0) maxX = 0;
            if (maxY < 0) maxY = 0;

            //round to 8x8 grid
            minX = (minX & 0xFFF8);
            minY = (minY & 0xFFF8);
            maxX = (maxX & 0xFFF8);
            maxY = (maxY & 0xFFF8);

            //clamp to maximum
            if (minX >= MAX_WIDTH) minX = MAX_WIDTH - 1;
            if (minY >= MAX_HEIGHT) minY = MAX_HEIGHT - 1;
            if (maxX >= MAX_WIDTH) maxX = MAX_WIDTH - 1;
            if (maxY >= MAX_HEIGHT) maxY = MAX_HEIGHT - 1;

            if (maxX > minX && maxY > minY) {
                if (clearFirst) {
                    commandBuffer[0] = (byte)Command.DrawTileMap | 0x01;
                } else {
                    commandBuffer[0] = (byte)Command.DrawTileMap;
                }
                commandBuffer[5] = (byte)(offsetX % 8);
                if (commandBuffer[5] > 0) {
                    maxX += 8;
                }
                commandBuffer[1] = (byte)minX;
                commandBuffer[2] = (byte)minY;
                commandBuffer[3] = (byte)maxX;
                commandBuffer[4] = (byte)maxY;
                while (!BusyPin.Read()) ;
                InterfacePort.Write(commandBuffer, 0, 6);

                int width = maxX - minX;
                width = (width >> 3) + 1;
                minX = (minX >> 3) + (offsetX >> 3);
                minY = (minY >> 3);
                maxY = (maxY >> 3);
                for (int y = minY; y <= maxY; y++) {
                    while (!BusyPin.Read()) ;
                    InterfacePort.Write(tileMap, (y * mapTotalWidth) + minX, width);
                }

                Thread.Sleep(1);
            }
        }
        #endregion

        #region 3D

        public void DrawLine3D(float p1X, float p1Y, float p1Z, float p2X, float p2Y, float p2Z, ref Matrix4 mat, byte color) {
            Vector3 p1 = new Vector3(p1X, p1Y, p1Z);
            Vector3 p2 = new Vector3(p2X, p2Y, p2Z);
            p1.TransformWithMatrix(mat);
            p2.TransformWithMatrix(mat);
            if (p1.Z > zNear && p2.Z > zNear && p1.Z < zFar && p2.Z < zFar) {
                float _z1 = 1.0f / p1.Z * zoom;
                float _z2 = 1.0f / p2.Z * zoom;
                float x3 = (p1.X * _z1) + centerX;
                float y3 = (p1.Y * _z1) + centerY;
                float x4 = (p2.X * _z2) + centerX;
                float y4 = (p2.Y * _z2) + centerY;
                DrawLine((int)x3, (int)y3, (int)x4, (int)y4, color, 0);
            }
        }

        //set matrix pointer to RLP before using 3D functions
        public void SetMatrixPointer(ref Matrix4 localMat) {
            SetMatrixRLP.InvokeEx(localMat.buffer);
            if (localMat != null) {
                matrixDefined = true;
            }
        }

        //Add an entire mesh to the quad batch but don't draw or sort
        public void DrawQuadMesh(float[] points, byte fillColor, byte lineColor) {
            if (!matrixDefined) {
                Debug.Print("Define matrix before using 3D");
            } else {
                DrawQuadMeshRLP.InvokeEx(points, points.Length, fillColor, lineColor, zNear, zFar, zoom, centerX, centerY);
            }
        }

        //sort all quads added with DrawQuadMesh by Z and draw
        public void DrawQuadBatch() {
            if (!matrixDefined) {
                Debug.Print("Define matrix before using 3D");
            } else {
                DrawQuadBatchRLP.Invoke();
            }
        }

        #endregion

        #region Music
        public void MusicPlay(byte rate, bool loop) {
            commandBuffer[0] = (byte)Command.MusicPlay;
            commandBuffer[1] = rate;
            if (loop) {
                commandBuffer[2] = 1;
            } else {
                commandBuffer[2] = 0;
            }
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 3);
        }

        public void MusicStop() {
            commandBuffer[0] = (byte)Command.MusicStop;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 1);
        }

        public void MusicSet(int index, byte ch1, byte ch2, byte ch3) {
            if (index > MaxMusicIndex) {
                return;
            }
            commandBuffer[0] = (byte)Command.MusicSet;
            commandBuffer[1] = (byte)((index >> 8) & 0xFF);
            commandBuffer[2] = (byte)(index & 0xFF);
            commandBuffer[3] = ch1;
            commandBuffer[4] = ch2;
            commandBuffer[5] = ch3;
            while (!BusyPin.Read()) ;
            InterfacePort.Write(commandBuffer, 0, 6);
            Thread.Sleep(1);
        }

        #endregion

    }


}

The main SPIN code is just a simple loop that waits for a command and knows how many bytes to expect. I know this screwball SPIN probably doesn’t format but this is the rough idea. Everything is inlined as much as possible because SPIN subs take way too long.


PUB Listen | i,j,k,l,x,y
  repeat
    dira[BUSYPIN]~~
    outa[BUSYPIN]~~
    Command := Ser.rx
    SubCommand := Command & $0F
    Command &= $F0
    
    case Command
      BUFFER_GROUP:
        case SubCommand
          CLEAR_BUFFER:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            gr.clear
           
          CLEAR_REGION:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            i := Ser.rx ''minX
            j := Ser.rx ''minY
            x := Ser.rx ''maxX
            y := Ser.rx ''maxY
            gr.finish
            gr.color(0)
            gr.quad(i, j, x, j, x, y, i, y)
           
          DRAW_BUFFER:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            gr.finish
            gr.copy(display_base)
            
      SCREEN_GROUP:
        case SubCommand
          SET_COLOR_MAP_ADDR:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            repeat i from 0 to 4
              commandBuffer[i] := Ser.rx
            colors[commandBuffer[0]] := commandBuffer[1] | (commandBuffer[2] << 8) | (commandBuffer[3] << 16) | (commandBuffer[4] << 24)
               
          SET_SCREEN_TILE:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            repeat i from 0 to 2
              commandBuffer[i] := Ser.rx
            if commandBuffer[0] < x_tiles and commandBuffer[1] < y_tiles
              screen[commandBuffer[0] + (commandBuffer[1] * tv_hc)] := (commandBuffer[2] << 10) + (display_base >> 6) + commandBuffer[0] * tv_vc + commandBuffer[1]

      DRAW_LINE:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        repeat i from 0 to 5
          commandBuffer[i] := Ser.rx
        gr.color(commandBuffer[0])
        gr.width(commandBuffer[1])
         
        gr.plot(commandBuffer[4],commandBuffer[5])
        repeat (commandBuffer[2] - 1)
          commandBuffer[6] := Ser.rx
          commandBuffer[7] := Ser.rx
          gr.line(commandBuffer[6], commandBuffer[7])
        if commandBuffer[3] == 1
          gr.line(commandBuffer[4],commandBuffer[5])
         
      DRAW_POINT:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        repeat i from 0 to 2
          commandBuffer[i] := Ser.rx
        gr.color(commandBuffer[0])
        gr.width(commandBuffer[1])
        repeat commandBuffer[2]
          i := Ser.rx
          j := Ser.rx
          if i => 0 and j => 0 and i =< SCREEN_WIDTH and j =< SCREEN_HEIGHT
            gr.plot(i, j)
         
      DRAW_QUAD:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        repeat i from 0 to 9
          commandBuffer[i] := Ser.rx
        if commandBuffer[8] < 4
          gr.color(commandBuffer[8])
          gr.quad(commandBuffer[0],commandBuffer[1],commandBuffer[2],commandBuffer[3],commandBuffer[4],commandBuffer[5],commandBuffer[6],commandBuffer[7])
           
        if commandBuffer[9] < 4
          gr.color(commandBuffer[9])
          gr.width(0)
          gr.plot(commandBuffer[0], commandBuffer[1])
          repeat j from 2 to 6 step 2
            gr.line(commandBuffer[j], commandBuffer[j+1])
          gr.line(commandBuffer[0], commandBuffer[1])
       
      DRAW_SPRITE:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~

        commandBuffer[0] := Ser.rx
        commandBuffer[1] := Ser.rx
        commandBuffer[2] := Ser.rx

        if SubCommand == 0
          j := 9
          k := 8
          pixdef[0] := $801
        else
          j := 33
          k := 16
          pixdef[0] := $1002
        
        repeat i from 2 to j
          pixdef[i] := Ser.rx
          pixdef[i] <<= 8
          pixdef[i] |= Ser.rx
          
        gr.width(0)
        gr.color(0)
       
        repeat commandBuffer[0]
          x := Ser.rx
          y := Ser.rx
          commandBuffer[3] := Ser.rx
          commandBuffer[4] := Ser.rx

          x -= commandBuffer[3]
          y -= commandBuffer[4]
          
          if commandBuffer[2] == 1
            gr.quad(x,y,x,y+k,x+k,y+k,x+k,y)
            
          gr.pix(x,y,commandBuffer[1],@ pixdef)
       
      DEFINE_TILE:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~

        commandBuffer[0] := Ser.rx
        
        if commandBuffer[0] == 0
          j := 9
          k := $801
          tileSize := 8
        else
          j := 33
          k := $1002
          tileSize := 16
        
        repeat i from 2 to j
          Case SubCommand
            0: tile1[i] := Ser.rx
               tile1[i] <<= 8
               tile1[i] |= Ser.rx
            1: tile2[i] := Ser.rx
               tile2[i] <<= 8
               tile2[i] |= Ser.rx
            2: tile3[i] := Ser.rx
               tile3[i] <<= 8
               tile3[i] |= Ser.rx
            3: tile4[i] := Ser.rx
               tile4[i] <<= 8
               tile4[i] |= Ser.rx
            4: tile5[i] := Ser.rx
               tile5[i] <<= 8
               tile5[i] |= Ser.rx
            5: tile6[i] := Ser.rx
               tile6[i] <<= 8
               tile6[i] |= Ser.rx
            other: tile7[i] := Ser.rx
               tile7[i] <<= 8
               tile7[i] |= Ser.rx
               
        Case SubCommand
          0: tile1[0] := k
          1: tile2[0] := k
          2: tile3[0] := k
          3: tile4[0] := k
          4: tile5[0] := k
          5: tile6[0] := k
          other: tile7[0] := k

      DRAW_TILEMAP:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        commandBuffer[0] := Ser.rx ''minX
        commandBuffer[1] := Ser.rx ''minY
        commandBuffer[2] := Ser.rx ''maxX
        commandBuffer[3] := Ser.rx ''maxY
        commandBuffer[4] := Ser.rx ''offsetX
         
        gr.width(0)
         
        if SubCommand == 1
          gr.color(0)
          gr.quad(commandBuffer[0], commandBuffer[1], commandBuffer[2], commandBuffer[1], commandBuffer[2], commandBuffer[3], commandBuffer[0], commandBuffer[3])
         
        repeat y from commandBuffer[1] to commandBuffer[3] step tileSize
          dira[BUSYPIN]~~
          outa[BUSYPIN]~~
          i := Ser.rx
          outa[BUSYPIN]~
         
          repeat l from commandBuffer[0] to commandBuffer[2] step tileSize
            if l > commandBuffer[0]
              i := Ser.rx
              
            if i > 0
              if i > 7
                j := 4
                x := l + 7 - commandBuffer[4]
                i -= 7
              else
                j := 0
                x := l - commandBuffer[4]
         
              case i
                1: gr.pix(x,y,j,@ tile1)
                2: gr.pix(x,y,j,@ tile2)
                3: gr.pix(x,y,j,@ tile3)
                4: gr.pix(x,y,j,@ tile4)
                5: gr.pix(x,y,j,@ tile5)
                6: gr.pix(x,y,j,@ tile6)
                7: gr.pix(x,y,j,@ tile7)
         
           
      DRAW_TILE:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        i := SubCommand
        x := Ser.rx
        y := Ser.rx
        commandBuffer[0] := Ser.rx
        commandBuffer[1] := Ser.rx
        
        if i > 0
          if i > 7
            j := 4
            x += tileSize - 1
            i -= 7
          else
            j := 0

          x -= commandBuffer[0]
          y -= commandBuffer[1]
            
          gr.width(0)
          case i
            1: gr.pix(x,y,j,@ tile1)
            2: gr.pix(x,y,j,@ tile2)
            3: gr.pix(x,y,j,@ tile3)
            4: gr.pix(x,y,j,@ tile4)
            5: gr.pix(x,y,j,@ tile5)
            6: gr.pix(x,y,j,@ tile6)
            7: gr.pix(x,y,j,@ tile7)
                     
      DRAW_TEXT:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        x := Ser.rx
        y := Ser.rx
        j := Ser.rx
                
        gr.color(j)
        gr.width(0)

        j := x
        i := Ser.rx
        repeat while i  > 0
          DrawChar(x,y,i)
          x+=6
          if (x - j) > SCREEN_WIDTH
            i := 0
            Ser.rxflush
          else      
            i := Ser.rx

      MUSIC_GROUP:
        case SubCommand
          PLAY:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            i := Ser.rx
            j := Ser.rx
            mp.Set(1, 1, i, j)
          
          STOP:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            mp.Set(0, 1, 21, 0)
            
          SET:
            dira[BUSYPIN]~~
            outa[BUSYPIN]~
            musicIndex := Ser.rx
            musicIndex <<= 8
            musicIndex |= Ser.rx
            
            repeat i from 0 to 2
              commandBuffer[i] := Ser.rx
                           
            mp.SetMusic(musicIndex, commandBuffer[0], commandBuffer[1], commandBuffer[2])
            
      OTHER:
        dira[BUSYPIN]~~
        outa[BUSYPIN]~
        Ser.rxflush

PUB DrawChar(cursorX, cursorY, value) | i,j,charIndex
  if value < 128 and value > 32
    value -= 33 
    repeat i from 0 to 4
      charIndex := (value * 5) + i
      repeat j from 0 to 7
        if ((font5x7[charIndex] >> (7-j)) & $01) == 1
          gr.plot(cursorX, cursorY + j)
      cursorX++    



#4

This is cool!


#5

Very impressive. All on a FEZ Mini, too. You could make it into a custom PCB pretty easily…


#6

I uploaded all the source code so far. It currently uses the parallel on the Propeller 16-23 because I still want to test an SPI version and don’t want to rewire everything yet. Deleting 1 line of assembly in the parallel driver will put it on 0-7 and doesn’t effect anything else. It’s just NTSC TV right now but there is an allowance for any option and automatically load the correct firmware.

http://code.tinyclr.com/project/353/the-fez-propeller-connection/


#7

Great! How experienced are you with propeller SPIN and assembly language?


#8

I have about an hours experience, enough to skim through the manual to get it to do what I need and test the capability. It would probably take a couple days to master based on what I know so far.


#9

So you are probably using one of the many available drivers for video and audio not your own?


#10

It’s using the included video drivers right now but with the extra graphics functions stripped out and the sound is from the object exchange. Now that I know it works I can go back and rewrite everything to use less ram.

I would like to get 4bit color at 192x128 and 8bit color at 128x96. There’s a 6bit low res VGA version so I know it’s possible because most of those drivers assume the Propeller needs space left to do other work. It’s a little easier knowing I can pack the Propeller completely since it doesn’t have to do anything else.


#11

I have always wanted to see more for FEZ Gameo project. I will jump in and work with you once I am less busy. For now, I emailed you a $100 coupon so you can buy more FEZes for your prototypes and enjoyment :slight_smile:

… long live FEZ Gameo!!


#12

Excellent, I used that immediately for a Domino and Panda II.

This is definitely something I want to continue working on because it has a lot of potential beyond games. I just have to drudge through a couple days fully learning the Propeller.


#13

There are some really weak OSD display products fro security systems that sell for $$$ where you can make much better product with NETMF and propeller.


#14

I found a starting point for OSD with source and schematics.
http://www.igniteautomation.com/hc-osd.html

It looks like the only extra part needed is the EL1883 sync separator because everything else is redundant. The only problem with that solution is that it’s just B&W overlay because it can’t mix the color burst. The graphics are using a modified version of what’s included with the SPIN IDE so I know it has all the same graphics functions that I’m using now, just not color.

There’s also ram wasted on NTSC and PAL options which aren’t needed since the FEZ can program it adhoc.


#15

I updated the code so the Propeller is now an SPI slave and moved everything to the bottom to free the pins normally used for VGA. It’s only tested to 1Mhz right now because I still have to work on the assembly timing. Even at that speed it’s only slightly slower than parallel.


#16

For netmf, spi is better than parallel but even better is serial since it has built in buffers for tx and for rx. Especially good to send streams like audio to playback.

Speaking of audio, what format are you going to support? I had the code support some format that seem very common. I forgot what it is called!! See it in code please.

My goal was to use spi for graphics commands and serial for audio streams.


#17

On the Propeller side I’m using a 128byte buffer for SPI with a 1byte return loop that can hold some sort of status register. It still has ram left to run SPI and serial together along with audio streaming.

The audio was originally using the SN76489 emulator then I saw your code was using the AY3891 so I switched to that because it seems to have a lot more options. Right now the music is just a 32 beat up to 16 measure track that loops.

The streaming files use the YM format that writes the registers direct with only a couple lines of code so that would be easy to add.


#18

I found a free tool that exports the YM format so I made a test with a couple random songs. The FEZ controls the play/stop through SPI command then the raw frames are streamed directly from SD using serial.
http://www.julien-nevo.com/arkos/tools.html


#19

Stop teasing me! I am about to start soldering my own!


#20

I think I have the capabilities sorted out without adding ram. The last thing to work out is the sound because it should be possible to get a 2nd audio stream running at the same time. Right now the sfx just use an indexed YM stream.