Close

Explanation of approach for porting XORLib to TFT-display

A project log for Xorya TFT

Attempt to port XORLib to high-end PIC32 devices and TFT 320x240 displays with on-board controllers

shaosSHAOS 11/09/2018 at 05:460 Comments

If you remember my 2015 project Xoryahttps://hackaday.io/project/5507-xorya-extremely-low-cost-game-console-on-pic32 ) then you should know that it was 640x200 monochrome (black and white) or 160x200 15-color modes (burst NTSC colors like in Apple II or CGA composite). So how could it be ported to 320x240 full-color TFT-display? At that time I started writing a software library XORLib (open sourced under MIT license) with some future graphics modes reservations:

/* NTSC compatible video modes - 320 bits per line */
#define XOMODE_320x200_MONO    0 /* Black and white 320x200 */
#define XOMODE_160x100_GRAY5   1 /* Pseudo mode on top of mode 0 with 5 shades of gray */
/* NTSC compatible video modes - 640 bits per line */
#define XOMODE_640x200_MONO    2 /* Black and white 640x200 */
#define XOMODE_213x200_GRAY4   3 /* Pseudo mode on top of mode 2 with 4 shades of gray */
#define XOMODE_160x200_COL15   4 /* Color burst mode over 640x200 with 15 unique colors (4 pallets) */
#define XOMODE_160x100_COL120  5 /* Pseudo mode on top of mode 4 with about 120 unique dithered colors */
/* NTSC compatible video modes with additional hardware - 640 bits per line */
#define XOMODE_320x200_COL4    6 /* CGA-like mode with configurable 4-color palette */
#define XOMODE_160x200_COL16   7 /* Color mode with standard 16-color palette */
/* NTSC compatible video modes with additional hardware - 1280 bits per line */
#define XOMODE_320x200_COL16   8 /* EGA-like mode with standard 16-color palette */
#define XOMODE_160x200_COL256  9 /* Predefined 256-color RGB-palette (including 64 shades of gray) */
/* NTSC compatible video modes with additional hardware - 2560 bits per line */
#define XOMODE_640x200_COL16  10 /* EGA-like mode with standard 16-color palette */
#define XOMODE_320x200_COL256 11 /* Predefined 256-color RGB-palette (including 64 shades of gray) */
/* VGA compatible video modes (just a placeholder for future) */
#define XOMODE_640x350_COL16  12 /* EGA-like mode with standard 16-color palette */
#define XOMODE_640x480_COL16  13 /* VGA-like mode with standard 16-color palette */
#define XOMODE_800x600_COL16  14 /* SVGA-like mode with standard 16-color palette */
#define XOMODE_ENHANCED_VGA   15 /* Placeholder for other VGA modes */

Having full-color TFT 320x240 is actually helping to cover all 320x200 modes (up to 256 colors) and now I'll explain how. If you have ever looked into XORLib examples then you know, that most of the time it works with videomemory directly:

#include "xorlib.h"

int main()
{
 int y;
 unsigned char *p;

 xoinit(XOMODE_160x200_COL15); /* gray colors 5 and 10 are identical */

 for(y=0;y<200;y++)
 {
   p = xodirectline(y);
   /* do something with pixels of current line (2 pixels per byte) */
 }

 return 0;
}

Even though XORLib is also allowing to use graphics primitives as xopixel and xoline (but obviously it is a little slower), so this port should support direct videomem access as well. So I decided to have low-res low-color videomemory in the PIC32 RAM - in case of 160x200_COL15 mode it is 160x200/2=16000 bytes (in actual XORLib it is a little bigger because of required color burst in the beginning of every line). And how is content of this videomem in PIC32 RAM transferred to videomem of TFT-display? Easiest way to do that is when next xodirectline function is called:

int xorlib_curline = -1;

inline unsigned char* xodirectline(short y)
{
   xcopy();
   xorlib_curline = y;
   return &xorlib_screen_buffer[y*80];
}

where xcopy is a function that does actual copying of programmatically modified line of pixels to TFT:

union{unsigned long bbytes; unsigned char bbyte[4];}aa;

void xcopy(void)
{
    int i;
    if(xorlib_curline >= 0)
    {
        tft_set_write_area(0,xorlib_curline,320,1);
        TFT_24_7789_Write_Command(0x2C);
        unsigned char *p = &xorlib_screen_buffer[xorlib_curline*80];
        for(i=0;i<80;i++)
        {
            aa.bbytes = actual[(*p)>>4];
            TFT_24_7789_Write_Data3(aa.bbyte[0],aa.bbyte[1],aa.bbyte[2]);   
            TFT_24_7789_Write_Data3(aa.bbyte[0],aa.bbyte[1],aa.bbyte[2]);   
            aa.bbytes = actual[(*p)&15];
            TFT_24_7789_Write_Data3(aa.bbyte[0],aa.bbyte[1],aa.bbyte[2]);   
            TFT_24_7789_Write_Data3(aa.bbyte[0],aa.bbyte[1],aa.bbyte[2]);
            p++;
        }
    }
}

where actual is an array that converts 4-bit color identifier from Xorya pallette to RGB value: 

#define RGB(r,g,b) (r|(g<<8)|(b<<16))

const unsigned long actual[16] = {
 RGB(0  , 0  , 0  ), /* #000000 H=0   S=0   V=0   0000 */
 RGB(0  , 81 , 169), /* #0051A9 H=211 S=100 V=66  0001 */
 RGB(123, 6  , 199), /* #7B06C7 H=276 S=97  V=78  0010 */
 RGB(110, 88 , 255), /* #6E58FF H=248 S=65  V=100 0011 */
 RGB(140, 45 , 0  ), /* #8C2D00 H=19  S=100 V=55  0100 */
 RGB(127, 127, 127), /* #7F7F7F H=0   S=0   V=50  0101 */
 RGB(255, 52 , 157), /* #FF349D H=329 S=80  V=100 0110 */
 RGB(250, 134, 255), /* #FA86FF H=298 S=47  V=100 0111 */
 RGB(4  , 120, 0  ), /* #047800 H=118 S=100 V=47  1000 */
 RGB(0  , 202, 97 ), /* #00CA61 H=149 S=100 V=79  1001 */
 RGB(127, 127, 127), /* #7F7F7F H=0   S=0   V=50  1010 */
 RGB(114, 209, 255), /* #72D1FF H=200 S=55  V=100 1011 */
 RGB(144, 166, 0  ), /* #90A600 H=68  S=100 V=65  1100 */
 RGB(131, 248, 55 ), /* #83F837 H=96  S=78  V=97  1101 */
 RGB(255, 173, 85 ), /* #FFAD55 H=31  S=67  V=100 1110 */
 RGB(255, 255, 255)  /* #FFFFFF H=0   S=0   V=100 1111 */
};

 and this approach is working pretty well (but slower than DMA+SPI hardware chain in original Xorya). 

40 lines at the bottom of the display could be used for debugging purposes (as right now I'm printing there 2 lines of text that helped me to debug the port).

P.S. Future optimizations of this port may be switching to RGB565 during copying (instead of RGB888) and even DMA usage...

Discussions