Graphics using C and Kye's video driver

This is a quick video demo of Catalina running Kye's video driver http://www.youtube.com/watch?v=Vw3E38Zna8U
A couple of pictures of our recent family expedition to the our local open park zoo http://www.zoossa.com.au/monarto-zoo
This is demonstrating:
1) Running a C on the propeller using external memory (programs up to 512k)
2) Stopping the Catalina video driver cogs
3) Loading in Kye's video driver into a cog from an sd card into one of these cogs and starting it
4) Drawing some pixels and boxes on the screen
5) Loading some pictures off the sd card and displaying them
I believe there is a lot of potential for C to create a GUI on the propeller. I am not sure Spin can do this because by the time you write an sd driver (1/3 of the hub ram) and a video buffer (19200 bytes) there is hardly any ram left for actual code.
So I think an answer is to move the program out into external memory, which Catalina C can do. This frees up virtually all the hub for a video buffer.
Coupled with this is the ability to load 'cogjects' - standalone 2k code that can be loaded into cogs and run. We have cogjects now for keyboard, serial, multiple text video, mouse and now graphics video and these can all be loaded and unloaded under software control.
Next step is to try to speed up the sd reads as this is a lot slower than Kye's sd code (which is able to read in frames at about 20 per second). Maybe Ross H might have some suggestions there?
A couple of pictures of our recent family expedition to the our local open park zoo http://www.zoossa.com.au/monarto-zoo
This is demonstrating:
1) Running a C on the propeller using external memory (programs up to 512k)
2) Stopping the Catalina video driver cogs
3) Loading in Kye's video driver into a cog from an sd card into one of these cogs and starting it
4) Drawing some pixels and boxes on the screen
5) Loading some pictures off the sd card and displaying them
I believe there is a lot of potential for C to create a GUI on the propeller. I am not sure Spin can do this because by the time you write an sd driver (1/3 of the hub ram) and a video buffer (19200 bytes) there is hardly any ram left for actual code.
So I think an answer is to move the program out into external memory, which Catalina C can do. This frees up virtually all the hub for a video buffer.
Coupled with this is the ability to load 'cogjects' - standalone 2k code that can be loaded into cogs and run. We have cogjects now for keyboard, serial, multiple text video, mouse and now graphics video and these can all be loaded and unloaded under software control.
Next step is to try to speed up the sd reads as this is a lot slower than Kye's sd code (which is able to read in frames at about 20 per second). Maybe Ross H might have some suggestions there?
/* PASM cogject demonstration, see also cogject example in spin*/
#include <stdio.h>
unsigned long cogarray[511]; // external memory common cog array
// start of C functions
void clearscreen() // white text on dark blue background
{
int i;
for (i=0;i<40;i++)
{
t_setpos(0,0,i); // move cursor to next line
t_color(0,0x08FC); // RRGGBBxx eg dark blue background 00001000 white text 11111100
}
}
void sleep(int milliseconds) // sleep function
{
_waitcnt(_cnt()+(milliseconds*(_clockfreq()/1000))-4296);
}
char peek(int address) // function implementation of peek
{
return *((char *)address);
}
void poke(int address, char value) // function implementation of poke
{
*((char *)address) = value;
}
void external_memory_cog_load(int cognumber, unsigned long cogdata[], unsigned long parameters_array[]) // load a cog from external memory
{
unsigned long hubcog[511]; // create a local array, this is in hub ram, not external ram
int i;
for(i=0;i<512;i++)
{
hubcog[i]=cogdata[i]; // move from external memory to a local array in hub
}
_coginit((int)parameters_array>>2, (int)hubcog>>2, cognumber); // load the cog
}
int EoF (FILE* stream)
{
register int c, status = ((c = fgetc(stream)) == EOF);
ungetc(c,stream);
return status;
}
void readcog(char *filename,unsigned long external_cog[]) // read in a .cog file into external memory array
{
int i;
FILE *FP1;
i = 0;
if((FP1=fopen(filename,"rb"))==0) // open the file
{
fprintf(stderr,"Can't open file %s\n",filename);
exit(1);
}
fseek(FP1,0,0);
for(i=0;i<24;i++)
{
getc(FP1); // read in the first 24 bytes and discard
}
i = 0;
while(!EoF(FP1) & (i<505)) // run until end of file or 511-6
{
external_cog[i] = getc(FP1) | (getc(FP1)<<8) | (getc(FP1)<<16) | (getc(FP1)<<24); // get the long
i+=1;
}
if(FP1)
{
fclose(FP1); // close the file
FP1=NULL;
}
printf("external array cog first long = 0x%x \n",external_cog[0]); // hex value
}
void pix_clearscreen(unsigned long screen[])
{
int i;
for (i=0;i<4800;i++)
{
screen[i] = 0x00000000; // fill with black, use longs rather than bytes so 4 pixels per loop
}
}
void pix_pixel(unsigned long screen[], int x, int y, char color)
{
poke((y*160+x+(unsigned long)&screen[0]),color);
}
void pix_readscreen(char *filename, unsigned long screen[]) // read a full screen 19200 byte file into the screen
{
int i;
FILE *FP1;
i = 0;
FP1=fopen(filename,"rb"); // open the file
fseek(FP1,0,0);
for(i=0;i<=4800;i++)
{
screen[i] = getc(FP1) | (getc(FP1)<<8) | (getc(FP1)<<16) | (getc(FP1)<<24); // get the long
}
fclose(FP1); // close the file
FP1=NULL;
}
char pix_color(char red,char green,char blue) // pass red,green,blue 0-3, returns a combined value
{
return ((red & 0x3) << 6 ) | ((green & 0x3) << 4) | ((blue & 0x3) << 2);
}
void pixenginestart(int cognumber, unsigned long cogarray[], unsigned long screen[], unsigned long pingroup, char kye_graphics_parameters[])
{
/*
pinGroup := ((pinGroup <# 3) #> 0) ' pingroup only used in this pub
long[screen][2] := ($FF << (8 * pinGroup))
long[screen][3] := ($30_00_00_FF | (pinGroup << 9))
pinGroup := constant((25_175_000 + 1_600) / 4) ' pingroup recycled as a new var
long[screen][4] := 1
repeat 32
pinGroup <<= 1
long[screen][4] <-= 1
if(pinGroup => clkfreq)
pinGroup -= clkfreq
long[screen][4] +=1
long[screen][0] := @displayindicator
long[screen][1] := @syncindicator
'cogNumber := cognew(@initialization, screen) ' and displayindicatoraddress/syncindicatoraddress are cog variables
cognumber := cognew(cogarray,screen) ' start cog
waitcnt((((clkfreq / 1000 * 1) - 4296) #> 381) + cnt) ' Wait for designated time in ms eg 1000*1 is 1ms
' need a short delay otherwise the variables don't get passed to the cog
*/
// no need for first pingroup test, always will be 0,1,2,3
int i;
char *ptr;
screen[2] = (0xff << (8*pingroup));
//printf("value is %x \n",screen[2]);
screen[3] = (0x300000ff | (pingroup << 9));
//printf("value is %x \n",screen[3]);
pingroup = ((25175000 + 1600) / 4);
//printf("value is %x \n",pingroup);
screen[4] = 1;
for(i=0;i<32;i++)
{
pingroup = pingroup << 1;
screen[4] = (screen[4] << 1) | (screen[4] >> 31);
//printf("value is %x \n",screen[4]);
if (pingroup >= _clockfreq())
{
pingroup -= _clockfreq();
screen[4] += 1;
}
}
kye_graphics_parameters[0] = 1; // displayindicator
kye_graphics_parameters[1] = 0; // syncindicator
//printf("pointers \n");
ptr = &kye_graphics_parameters[0];
screen[0] = (unsigned long) ptr;
//printf("value is %u \n",screen[0]);
ptr = &kye_graphics_parameters[1];
screen[1] = (unsigned long) ptr;
//printf("value is %u \n",screen[1]);
//_cogstop(0); // not video
_cogstop(1); // video shut down this driver
// cog 2 is catalina
_cogstop(3); // video shut down this driver
//_cogstop(4); // not sure what this does, hangs prop when add this
//_cogstop(5); // not video
//_cogstop(6); // not video
external_memory_cog_load(7,cogarray,screen); // load from external ram, pass some values in screen[]
pix_clearscreen(screen); // clear the screen to black
}
void pix_colorbar(unsigned long screen[])
{
int x;
int c;
x = 0;
for (c=0;c<64;c++)
{
pix_pixel(screen,x+10,6,(c<<2)); // print all the colors in a bar
x++;
}
}
void pix_box_unfilled(unsigned long screen[], int startx, int starty, int endx, int endy, char color)
{
int row;
int col;
for(col=startx;col <= endx ;col++)
{
pix_pixel(screen,col,starty,color); // horizontal lines
pix_pixel(screen,col,endy,color);
}
for(row=starty;row <= endy;row++)
{
pix_pixel(screen,startx,row,color);
pix_pixel(screen,endx,row,color);
}
}
void pix_box_fill(unsigned long screen[], int startx, int starty, int endx, int endy, char color)
{
int row;
int col;
for(row =starty;row <= endy; row++)
{
for(col=startx;col <=endx; col++)
{
pix_pixel(screen,col,row,color); // horizontal line
}
}
}
void kyegraphicsdemo(unsigned long screen[],char kye_graphics_parameters[]) // kye graphics demo
{
clearscreen(); // white on blue vga
printf("Clock speed %u \n",_clockfreq()); // see page 28 of the propeller manual for other useful commands
printf("Catalina running in cog number %i \n",_cogid()); // integer
printf("Kye graphics demonstration \n");
sleep(2000); // 2 sec delay
readcog("vgagraph.cog",cogarray); // read in kye's graphics driver
pixenginestart(7,cogarray,screen,2,kye_graphics_parameters); // start the driver
pix_pixel(screen,3,3,0x44);
pix_pixel(screen,4,4,0xFF);
pix_pixel(screen,5,5,pix_color(1,2,3)); // rgb each 0,1,2 or 3
pix_box_unfilled(screen,0,0,159,119,pix_color(3,0,0)); // red border for screen
pix_colorbar(screen); // a color bar
pix_box_unfilled(screen,10,10,30,30,pix_color(3,3,0)); // a yellow box
pix_box_fill(screen,10,50,40,60,pix_color(3,0,3)); // magenta filled rectangle
sleep(2000);
pix_readscreen("wallaby.vga",screen); // display whole screen image
pix_readscreen("Giraffe.vga",screen); // display whole screen image
pix_readscreen("prop160.vga",screen); // display whole screen image
}
void main ()
{
char kye_graphics_parameters[1]; // displayindicator and syncindicater are permanent hub variables
unsigned long screen[160*120/4]; // graphics and text video buffer in hub - eg 19200 bytes
kyegraphicsdemo(screen,kye_graphics_parameters);
while (1); // endless loop as prop reboots on exit from main()
}
Comments
Yes, I know the SD card driver I use is quite slow. It was my own adaption. When I get time I'll have a look at speeding it up.
Ross.
P.S. With release 3.0, it is much easier to identiy what cogs are doing what - see the new demo program test_plugin_names.c
I've got it down to about half a second by storing the picture in external memory. Got a tiny bit more speed using memcpy
memcpy(destination,source,19200); // a strings.h function
I've added a button, text box and radio box - see attached photo. Next step is to capture a font. Catalina with external memory is great because things like fonts can be stored in external ram.
/* PASM cogject demonstration, see also cogject example in spin*/ #include <stdio.h> #include <string.h> unsigned long cogarray[512]; // external memory common cog array unsigned long screen_external[4800] ; // external memory for a screen buffer // start of C functions void clearscreen() // white text on dark blue background { int i; for (i=0;i<40;i++) { t_setpos(0,0,i); // move cursor to next line t_color(0,0x08FC); // RRGGBBxx eg dark blue background 00001000 white text 11111100 } } void sleep(int milliseconds) // sleep function { _waitcnt(_cnt()+(milliseconds*(_clockfreq()/1000))-4296); } char peek(int address) // function implementation of peek { return *((char *)address); } void poke(int address, char value) // function implementation of poke { *((char *)address) = value; } void external_memory_cog_load(int cognumber, unsigned long cogdata[], unsigned long parameters_array[]) // load a cog from external memory { unsigned long hubcog[511]; // create a local array, this is in hub ram, not external ram int i; for(i=0;i<512;i++) { hubcog[i]=cogdata[i]; // move from external memory to a local array in hub } _coginit((int)parameters_array>>2, (int)hubcog>>2, cognumber); // load the cog } int EoF (FILE* stream) { register int c, status = ((c = fgetc(stream)) == EOF); ungetc(c,stream); return status; } void readcog(char *filename,unsigned long external_cog[]) // read in a .cog file into external memory array { int i; FILE *FP1; i = 0; if((FP1=fopen(filename,"rb"))==0) // open the file { fprintf(stderr,"Can't open file %s\n",filename); exit(1); } fseek(FP1,0,0); for(i=0;i<24;i++) { getc(FP1); // read in the first 24 bytes and discard } i = 0; while(!EoF(FP1) & (i<505)) // run until end of file or 511-6 { external_cog[i] = getc(FP1) | (getc(FP1)<<8) | (getc(FP1)<<16) | (getc(FP1)<<24); // get the long i+=1; } if(FP1) { fclose(FP1); // close the file FP1=NULL; } printf("external array cog first long = 0x%x \n",external_cog[0]); // hex value } void pix_clearscreen(unsigned long screen[]) { int i; for (i=0;i<4800;i++) { screen[i] = 0x00000000; // fill with black, use longs rather than bytes so 4 pixels per loop } } void pix_pixel(unsigned long screen[], int x, int y, char color) { poke((y*160+x+(unsigned long)&screen[0]),color); } void pix_readscreen(char *filename, unsigned long screen[]) // read a full screen 19200 byte file into the screen { int i; FILE *FP1; i = 0; FP1=fopen(filename,"rb"); // open the file fseek(FP1,0,0); for(i=0;i<=4800;i++) { screen[i] = getc(FP1) | (getc(FP1)<<8) | (getc(FP1)<<16) | (getc(FP1)<<24); // get the long } fclose(FP1); // close the file FP1=NULL; } char pix_color(char red,char green,char blue) // pass red,green,blue 0-3, returns a combined value { return ((red & 0x3) << 6 ) | ((green & 0x3) << 4) | ((blue & 0x3) << 2); } void pixenginestart(int cognumber, unsigned long cogarray[], unsigned long screen[], unsigned long pingroup, char kye_graphics_parameters[]) { /* pinGroup := ((pinGroup <# 3) #> 0) ' pingroup only used in this pub long[screen][2] := ($FF << (8 * pinGroup)) long[screen][3] := ($30_00_00_FF | (pinGroup << 9)) pinGroup := constant((25_175_000 + 1_600) / 4) ' pingroup recycled as a new var long[screen][4] := 1 repeat 32 pinGroup <<= 1 long[screen][4] <-= 1 if(pinGroup => clkfreq) pinGroup -= clkfreq long[screen][4] +=1 long[screen][0] := @displayindicator long[screen][1] := @syncindicator 'cogNumber := cognew(@initialization, screen) ' and displayindicatoraddress/syncindicatoraddress are cog variables cognumber := cognew(cogarray,screen) ' start cog waitcnt((((clkfreq / 1000 * 1) - 4296) #> 381) + cnt) ' Wait for designated time in ms eg 1000*1 is 1ms ' need a short delay otherwise the variables don't get passed to the cog */ // no need for first pingroup test, always will be 0,1,2,3 int i; char *ptr; screen[2] = (0xff << (8*pingroup)); //printf("value is %x \n",screen[2]); screen[3] = (0x300000ff | (pingroup << 9)); //printf("value is %x \n",screen[3]); pingroup = ((25175000 + 1600) / 4); //printf("value is %x \n",pingroup); screen[4] = 1; for(i=0;i<32;i++) { pingroup = pingroup << 1; screen[4] = (screen[4] << 1) | (screen[4] >> 31); //printf("value is %x \n",screen[4]); if (pingroup >= _clockfreq()) { pingroup -= _clockfreq(); screen[4] += 1; } } kye_graphics_parameters[0] = 1; // displayindicator kye_graphics_parameters[1] = 0; // syncindicator //printf("pointers \n"); ptr = &kye_graphics_parameters[0]; screen[0] = (unsigned long) ptr; //printf("value is %u \n",screen[0]); ptr = &kye_graphics_parameters[1]; screen[1] = (unsigned long) ptr; //printf("value is %u \n",screen[1]); //_cogstop(0); // not video _cogstop(1); // video shut down this driver // cog 2 is catalina _cogstop(3); // video shut down this driver //_cogstop(4); // not sure what this does, hangs prop when add this //_cogstop(5); // not video //_cogstop(6); // not video external_memory_cog_load(7,cogarray,screen); // load from external ram, pass some values in screen[] pix_clearscreen(screen); // clear the screen to black } void pix_colorbar(unsigned long screen[]) { int x; int c; x = 0; for (c=0;c<64;c++) { pix_pixel(screen,x+10,6,(c<<2)); // print all the colors in a bar x++; } } void pix_line(unsigned long screen[], int startx, int starty, int endx, int endy, char color) { int row; // also does an unfilled box int col; for(col=startx;col <= endx ;col++) { pix_pixel(screen,col,starty,color); // horizontal lines pix_pixel(screen,col,endy,color); } for(row=starty;row <= endy;row++) { pix_pixel(screen,startx,row,color); pix_pixel(screen,endx,row,color); } } void pix_box_fill(unsigned long screen[], int startx, int starty, int endx, int endy, char color) { int row; int col; for(row =starty;row <= endy; row++) { for(col=startx;col <=endx; col++) { pix_pixel(screen,col,row,color); // horizontal line } } } void pix_button(unsigned long screen[],int startx, int starty, int sizex, int sizey, char *lineoftext) { pix_line(screen, startx, starty, startx, starty+sizey, pix_color(3,3,3)); // white vertical line pix_line(screen, startx, starty, startx+sizex, starty, pix_color(3,3,3)); // white horizontal line pix_line(screen, startx+sizex, starty+1, startx+sizex, starty+sizey-1, pix_color(1,1,1)); // vertical dark gray line pix_line(screen, startx+1, starty+sizey, startx+sizex, starty+sizey, pix_color(1,1,1)); // horizontal dark gray line pix_line(screen, startx+sizex+1, starty, startx+sizex+1, starty+sizey+1, pix_color(0,0,0)); // vertical black line pix_line(screen, startx, starty+sizey+1, startx+sizex+1, starty+sizey+1, pix_color(0,0,0)); // horizontal black line // add in the text } void pix_text(unsigned long screen[], int startx, int starty, int sizex, int sizey, char *lineoftext) { pix_box_fill(screen, startx+1, starty+1, startx+sizex-1, starty+sizey-1,pix_color(3,3,3)); // white centre pix_line(screen, startx, starty, startx, starty+sizey-1, pix_color(0,0,0)); // black vertical line pix_line(screen, startx, starty, startx+sizex-1, starty, pix_color(0,0,0)); // black horizontal line pix_line(screen, startx+sizex, starty, startx+sizex, starty+sizey, pix_color(2,2,2)); // vertical light gray line pix_line(screen, startx, starty+sizey, startx+sizex, starty+sizey, pix_color(2,2,2)); // horizontal light gray line pix_line(screen, startx-1, starty-1, startx-1, starty+sizey+1, pix_color(1,1,1)); // dark gray vertical line pix_line(screen, startx-1, starty-1, startx+sizex, starty-1, pix_color(1,1,1)); // dark gray horizontal line pix_line(screen, startx+sizex+1, starty-1, startx+sizex+1, starty+sizey+1, pix_color(3,3,3)); // vertical whiteline pix_line(screen, startx-1, starty+sizey+1, startx+sizex, starty+sizey+1, pix_color(3,3,3)); // horizontal whiteline } void pix_radio(unsigned long screen[], int startx, int starty, char check) { char radio[144] = { // black = 0x00, dark gray = 0x54, light gray = A8, white = FC 0xa8,0xa8,0xa8,0xa8,0x54,0x54,0x54,0x54,0xa8,0xa8,0xa8,0xa8, // a radio button 0xa8,0xa8,0x54,0x54,0x00,0x00,0x00,0x00,0x54,0x54,0xa8,0xa8, 0xa8,0x54,0x00,0x00,0xfc,0xfc,0xfc,0xfc,0x00,0x00,0xfc,0xa8, 0xa8,0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc,0xa8, 0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc, 0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc, 0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc, 0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc, 0xa8,0x54,0x00,0xfc,0xfc,0xfc,0xfc,0xfc,0xfc,0xa8,0xfc,0xa8, 0xa8,0x54,0xa8,0xa8,0xfc,0xfc,0xfc,0xfc,0xa8,0xa8,0xfc,0xa8, 0xa8,0xa8,0xfc,0xfc,0xa8,0xa8,0xa8,0xa8,0xfc,0xfc,0xa8,0xa8, 0xa8,0xa8,0xa8,0xa8,0xfc,0xfc,0xfc,0xfc,0xa8,0xa8,0xa8,0xa8 }; int x; int y; int i = 0; if(check !=0) { radio[53]=radio[54]=0x00; radio[64]=radio[65]=radio[66]=radio[67]=0x00; // draw the circle in the middle radio[76]=radio[77]=radio[78]=radio[79]=0x00; radio[89]=radio[90]=0x00; } for(y=0;y<12;y++) { for(x=0;x<12;x++) { pix_pixel(screen,startx+x, starty+y, radio[i]); // draw a radio button, white centre i++; } } } void copyscreen(unsigned long source[],unsigned long destination[]) { memcpy(destination,source,19200); // a strings.h function } void pix_screengray(unsigned long screen[]) // whole screen gray { int i; for(i=0;i<4800;i++) { screen[i] = 0xa8a8a8a8; // same as pix_color(2,2,2) } } void kyegraphicsdemo(unsigned long screen[],char kye_graphics_parameters[]) // kye graphics demo { clearscreen(); // white on blue vga printf("Clock speed %u \n",_clockfreq()); // see page 28 of the propeller manual for other useful commands printf("Catalina running in cog number %i \n",_cogid()); // integer printf("Kye graphics demonstration \n"); //sleep(2000); // 2 sec delay readcog("vgagraph.cog",cogarray); // read in kye's graphics driver pixenginestart(7,cogarray,screen,2,kye_graphics_parameters); // start the driver pix_pixel(screen,3,3,0x44); pix_pixel(screen,4,4,0xFF); pix_pixel(screen,5,5,pix_color(1,2,3)); // rgb each 0,1,2 or 3 pix_line(screen,0,0,159,119,pix_color(3,0,0)); // red border for screen pix_colorbar(screen); // a color bar pix_line(screen,10,10,30,30,pix_color(3,3,0)); // a yellow box pix_box_fill(screen,10,50,40,60,pix_color(3,0,3)); // magenta filled rectangle //pix_readscreen("wallaby.vga",screen); // display whole screen image //copyscreen(screen,screen_external); // copy to external ram //pix_readscreen("Giraffe.vga",screen); // display whole screen image //copyscreen(screen_external,screen); // copy back to hub //pix_readscreen("prop160.vga",screen); // display whole screen image //pix_box_fill(screen, 0, 0, 159,119,pix_color(2,2,2)); // whole screen gray pix_screengray(screen); // faster whole screen gray pix_button(screen, 50,50,40,20,"Button1"); // a button pix_text(screen, 50,80, 40, 20, "Text1"); // a text box pix_radio(screen, 10,10,1); // a checked radio button pix_radio(screen, 10,30,0); // an unchecked radio button } void main () { char kye_graphics_parameters[1]; // displayindicator and syncindicater are permanent hub variables unsigned long screen[160*120/4]; // graphics and text video buffer in hub - eg 19200 bytes kyegraphicsdemo(screen,kye_graphics_parameters); while (1); // endless loop as prop reboots on exit from main() }
I've just realised though that if you only want buttons, text boxes, radio buttons etc, and no picture boxes, then a gray scale works fine. That means the color depth can go from 64 down to 4 (black, dark gray, light gray and white) and in theory, that means we could increase the number of pixels.
So - off to the obex to rummage around, and lo and behold, I managed to find one written by Kye. The demo he has was written at 160x120 and has a blue 3D thing bouncing around the screen. But this does not quite show off the full features of this driver.
I have tweaked this a little so it is 320x240 and with a light gray background and with some grayscale lines. Attached is a little demo - this will run on all the standard propeller boards with vga on pin group 2. Next step is to turn this into a cogject and port it into C.
I think Kye's driver can also do 640x480 in two colors - that may come in handy too.
The nice thing about cogjects and C is you can flip between all these video modes, and text video drivers all within the one program.
640x480 @ 1bpp = 38,400 bytes ... so it does not fit in the prop
640x240 @ 1bpp = 19,200 bytes
I would think there is likely to be some open source gui c code. That Prop Computer is getting closer
As for the graphics, perhaps a mix of tiles and text is possibly what we require, with these being combined by cogs. Not quite sure how to go about this (maybe a discussion point at the next UPEOZSA conference)
Ross: I presume the slow SD code is caused by the SD low level access. Perhaps Kye's pasm code could be adapted here??
@Cluso, I'm writing the buttons and radio boxes from scratch - doing a screen capture on standard windows buttons, blowing them up and then turning them into arrays. A little tedious, but most of them are pretty easy to do. I've got an automated font capture almost done - I'm going to try fixed width and variable width fonts.
I've got a bit stuck though with Kye's video driver. In general terms, when turning an obex into a cogject, there are no common variables between spin and pasm. The only link is via "par" so the first step is to comment out any DAT sections and see where all the errors are. Then pass variables in a list.
In Kye's 160 video driver I simply passed all the variables via the screen buffer, then in the cog code did a whole lot of rdlongs into the cog variables.
Kye's 320 video driver is very similar looking but it is not working. I've got the code written but I've had to come back a few steps to test things out one thing at a time. I've narrowed down the problem to a few lines of code:
PUB BMPEngineStart(pinGroup, colorMode, horizontalResolution, verticalResolution, newDisplayPointer) '' 11 Stack Longs ... screenPointer := newDisplayPointer ... cogNumber := cognew(@initialization, @screenpointer)
This is different to the 160 driver as we have this new screenpointer variable.
To me this is quite confusing, as you start by passing from the 'main' program a pointer to the screen buffer. So that might pass a value like 2000. Then in this routine, you make a pointer equal to that value. Then you pass a pointer to a pointer??
I need to understand why he is doing this, because if you make one simple change, it garbles the screen:
I would have thought this would pass the same value as above?
As far as I can see, "newdisplaypointer" does not get changed in the startup routine. Is there something else clever going on, some other variable that is next to "newdisplaypointer" that changes it as well?
Any help here would be most appreciated.
I fixed this with the previous video driver by adding a delay just after the cognew
waitcnt((((clkfreq / 1000 * 1) - 4296) #> 381) + cnt) ' 1ms delay
but this is not working for this video driver.
This is most confusing. Say I start my main with a declaration of a screen buffer
long screen[160*120/4]
and then start the method with this
gray320.bmpenginestart(2, 2, 320,240,@screen) ' pin group 2, color mode 2
and in the method isPUB BMPEngineStart(pinGroup, colorMode, horizontalResolution, verticalResolution, newDisplayPointer)
and then there is a line and then I want to fill up the screen buffer. Will this line below work?long[screenpointer][0] := displayindicator ' test passing variable this way
or is this pointing to something different?Actually, what I am really confused about is where the screen buffer is. In the previous video driver we pass the value as above, and we have
PUB PIXEngineStart(cogarray, screen, pinGroup) '' 7 Stack Longs
and we start the cog withcognumber := cognew(cogarray,screen) ' start cog waitcnt((((clkfreq / 1000 * 1) - 4296) #> 381) + cnt) ' 1ms delay to start the cog
in other words, we start with "screen", not with "@screen"What I don't understand is why this new driver starts with an @ symbol. The value being passed to the method is a number which already has an @ in the main routine, so I can't grasp why there is a pointer to a pointer?
And I suspect because of this I'm not filling up the screen buffer, but some other random location in ram (and probably overwriting things).
I'm still confused about pointers. There are three of these in a row and I'm not sure why Kye does it that way.
I'll try a mailbox and see if I can get it working.
I'll start by putting everything into a mailbox (there are 14 longs in total that are passed), but some of these are stored in cog variables so maybe the mailbox can be made smaller once it is working.
Addit: Some working code. The only way to do this is to recompile after changing each line. There are too many variables otherwise. Also every time a variable is changed, you have to do a F3 and search for all other places it appears. Cogject programming is a very different style!
Thanks again to Kuroneko for finding that par rdlong in the main loop. I've changed the loop now so it stores the screen location in a new variable.
{{ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // VGA64 Bitmap Engine // // Author: Kwabena W. Agyeman // Updated: 7/15/2010 // Designed For: P8X32A // Version: 1.1 // // Copyright (c) 2010 Kwabena W. Agyeman // See end of file for terms of use. // // Update History: // // v1.0 - Original release - 9/28/2009. // v1.1 - Merged and rewrote code and added more features - 7/15/2010. // // For each included copy of this object only one spin interpreter should access it at a time. // // Nyamekye, // modifications J Moxham April 2011 so can run from a cogject. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }} DAT ' //////////////////////Variable Arrary//////////////////////////////////////////////////////////////////////////////////////// 'screenPointer long 0 ' Screen pointer. pixelColors long 0 ' Screen colors. displayIndicator byte 1 ' Video output control. syncIndicator byte 0 ' Video update control. cogNumber byte 0 ' Cog ID. bitsPerPixel byte 0 ' Bits ID. PUB plotPixel(pixelValue, xPixel, yPixel, displayBase) '' 7 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Plots a 1x1 pixel on screen. '' // '' // PixelValue - The pixel to plot on screen. Between %%0 and %%1 or between %%0, %%1, %%2, and %%3 depending on color mode. '' // XPixel - The X cartesian pixel coordinate, will be forced to be multiple of 1. Y will be forced to be a multiple of 1. '' // YPixel - The Y cartesian pixel coordinate. Note that this axis is inverted like on all other graphics drivers. '' // DisplayBase - The address of the display buffer to draw to. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// xPixel := ((xPixel <# (320 - 1)) #> 0) ' 320 = horizontal pixels displayBase += (((20 * ((yPixel <# (240 - 1)) #> 0)) + (xPixel >> (5 - bitsPerPixel))) << 2) ' 20 is horizontal longs, 240 = vertical pixels xPixel := ((xPixel & ($1F >> bitsPerPixel)) << bitsPerPixel) yPixel := (!((1 + (bitsPerPixel << 1)) << xPixel)) long[displayBase] := ((long[displayBase] & yPixel) | (((pixelValue <# (1 + (bitsPerPixel << 1))) #> 0) << xPixel)) PUB displayState(state) '' 4 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Enables or disables the BMP Driver's video output - turning the monitor off or putting it into standby mode. '' // '' // State - True for active and false for inactive. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// displayIndicator := state PUB displayRate(rate) '' 4 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Returns true or false depending on the time elasped according to a specified rate. '' // '' // Rate - A display rate to return at. 0=0.234375Hz, 1=0.46875Hz, 2=0.9375Hz, 3=1.875Hz, 4=3.75Hz, 5=7.5Hz, 6=15Hz, 7=30Hz. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// result or= (($80 >> ((rate <# 7) #> 0)) & syncIndicator) PUB displayWait(frames) '' 4 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Waits for the display vertical refresh. '' // '' // The best time to draw on screen for flicker free operation is right after this function returns. '' // '' // Frames - Number of vertical refresh frames to wait for. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// repeat (frames #> 0) result := syncIndicator repeat until(result <> syncIndicator) PUB displayColor(pixelNumber, newColor) '' 5 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Changes a pixel color for the whole screen. '' // '' // PixelNumber - The pixel number to change for the whole screen. Between 0 and 1 or 0 to 3. '' // NewColor - A color byte (%RR_GG_BB_xx) describing the pixel's new color for the whole screen. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// pixelColors.byte[(pixelNumber <# (1 + (bitsPerPixel << 1))) #> 0] := newColor PUB displayClear(patternValue, displayBase) '' 5 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Clears the whole screen. '' // '' // PatternValue - The pattern to plot on screen. '' // DisplayBase - The address of the display buffer to draw to. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// longfill(displayBase, patternValue, (20 * 240)) ' 20 is horizontal longs, 240 is vertical pixels PUB BMPEngineStart(pinGroup, colorMode, horizontalResolution, verticalResolution, screen) '' 11 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Starts up the BMP driver running on a cog. '' // '' // Returns true on success and false on failure. '' // '' // PinGroup - Pin group to use to drive the video circuit. Between 0 and 3. '' // ColorMode - Color mode to use for the whole screen. Between 1 bit per pixel or 2 bits per pixel. '' // HorizontalResolution - The driver will force this value to be a factor of 640 and divisible by 16 or 32. 16/32 to 640. '' // VerticalResolution - The driver will force this value to be a factor of 480. 1 to 480. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// long[screen][0] := screen ' pointer to screen buffer long[screen][1] := displayindicator 'pass variables to cog via screen buffer long[screen][2] := syncindicator pinGroup := ((pinGroup <# 3) #> 0) bitsPerPixel := ((colorMode <# 2) #> 1) long[screen][3] := ($FF << (8 * pinGroup)) ' directionstate long[screen][4] := ($20_00_00_FF | (pinGroup << 9) | ((--bitsPerPixel) << 28)) ' videostate pinGroup := constant((25_175_000 + 1_600) / 4) long[screen][5] := 1 ' frequencystate repeat 32 pinGroup <<= 1 long[screen][5] <-= 1 if(pinGroup => clkfreq) pinGroup -= clkfreq long[screen][5] +=1 horizontalResolution := ((horizontalResolution <# 640) #> (32 >> bitsPerPixel)) repeat while((640 // horizontalResolution) or ((horizontalResolution--) // (32 >> bitsPerPixel))) long[screen][6] := (640 / (++horizontalResolution)) ' 6 = horizontalscaling verticalResolution := ((verticalResolution <# 480) #> 1) repeat while(480 // verticalResolution--) long[screen][7] := (480 / (++verticalResolution)) ' 7 = verticalscaling long[screen][8] := (640 / long[screen][6]) ' 8 = horizontalpixels long[screen][9] := (480 / long[screen][7]) ' 9 = verticalpixels long[screen][10] := ((long[screen][6] << 12) + ((constant(640 * 32) >> bitsPerPixel) / long[screen][8])) ' 10 is visible scale long[screen][11] := (((8 << bitsPerPixel) << 12) + 160) ' 11 is invisible scale long[screen][12] := (long[screen][8] / (32 >> bitsPerPixel)) ' 12 is horizontal longs (equals 20 for 320x240) long[screen][13] := (long[screen][12] * 4) ' 13 is horizontalloops long[screen][14] := @pixelColors ' 14 is pixel colors cogNumber := cognew(@initialization, screen) waitcnt((((clkfreq / 1000 * 1) - 4296) #> 381) + cnt) ' 1msec delay to read in values to cog result or= ++cogNumber PUB BMPEngineStop '' 3 Stack Longs '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// '' // Shuts down the BMP driver running on a cog. '' //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if(cogNumber) cogstop(-1 + cogNumber~) DAT ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ' BMP Driver ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// org 0 ' //////////////////////Initialization///////////////////////////////////////////////////////////////////////////////////////// initialization mov i, par ' video buffer rdlong screenorg, i add i,#4 rdlong displayIndicatorAddress,i ' read in ongs from video buffer add i,#4 rdlong syncIndicatorAddress,i add i,#4 rdlong directionstate,i add i,#4 rdlong videostate,i add i,#4 rdlong frequencystate,i add i,#4 rdlong horizontalScaling,i add i,#4 rdlong verticalScaling,i add i,#4 rdlong horizontalPixels,i add i,#4 rdlong verticalPixels,i add i,#4 rdlong visibleScale,i add i,#4 rdlong invisibleScale,i add i,#4 rdlong horizontalLongs,i add i,#4 rdlong horizontalLoops,i add i,#4 rdlong pixelcolorsaddress,i mov vcfg, videoState ' Setup video hardware. mov frqa, frequencyState ' movi ctra, #%0_00001_101 ' ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ' Active Video ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// loop mov buffer, screenorg ' get the screen origin mov tilesCounter, verticalPixels ' tilesDisplay mov tileCounter, verticalScaling ' Set/Reset tile fill counter. tileDisplay mov vscl, visibleScale ' Set/Reset the video scale. mov counter, horizontalLongs ' ' //////////////////////Visible Video////////////////////////////////////////////////////////////////////////////////////////// videoLoop rdlong screenPixels, buffer ' Download new pixels. add buffer, #4 ' waitvid screenColors, screenPixels ' Update display scanline. djnz counter, #videoLoop ' Repeat. ' //////////////////////Invisible Video//////////////////////////////////////////////////////////////////////////////////////// mov vscl, invisibleScale ' Set/Reset the video scale. waitvid HSyncColors, syncPixels ' Horizontal Sync. ' //////////////////////Repeat///////////////////////////////////////////////////////////////////////////////////////////////// sub buffer, horizontalLoops ' Repeat. djnz tileCounter, #tileDisplay ' add buffer, horizontalLoops ' Repeat. djnz tilesCounter, #tilesDisplay ' ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ' Inactive Video ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// rdlong screenColors, pixelColorsAddress ' Get new screen colors. or screenColors, HVSyncColors ' ' //////////////////////Update Indicator/////////////////////////////////////////////////////////////////////////////////////// add refreshCounter, #1 ' Update sync indicator. wrbyte refreshCounter, syncIndicatorAddress ' ' //////////////////////Front Porch//////////////////////////////////////////////////////////////////////////////////////////// mov counter, #11 ' Set loop counter. frontPorch mov vscl, blankPixels ' Invisible lines. waitvid HSyncColors, #0 ' mov vscl, invisibleScale ' Horizontal Sync. waitvid HSyncColors, syncPixels ' djnz counter, #frontPorch ' Repeat # times. ' //////////////////////Vertical Sync////////////////////////////////////////////////////////////////////////////////////////// mov counter, #(2 + 2) ' Set loop counter. verticalSync mov vscl, blankPixels ' Invisible lines. waitvid VSyncColors, #0 ' mov vscl, invisibleScale ' Vertical Sync. waitvid VSyncColors, syncPixels ' djnz counter, #verticalSync ' Repeat # times. ' //////////////////////Back Porch///////////////////////////////////////////////////////////////////////////////////////////// mov counter, #31 ' Set loop counter. backPorch mov vscl, blankPixels ' Invisible lines. waitvid HSyncColors, #0 ' mov vscl, invisibleScale ' Horizontal Sync. waitvid HSyncColors, syncPixels ' djnz counter, #backPorch ' Repeat # times. ' //////////////////////Update Display Settings//////////////////////////////////////////////////////////////////////////////// rdbyte buffer, displayIndicatorAddress wz ' Update display settings. muxnz dira, directionState ' ' //////////////////////Loop/////////////////////////////////////////////////////////////////////////////////////////////////// jmp #loop ' Loop. ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ' Data ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// blankPixels long 640 ' Blank scanline pixel length. syncPixels long $00_00_3F_FC ' F-porch, h-sync, and b-porch. HSyncColors long $01_03_01_03 ' Horizontal sync color mask. VSyncColors long $00_02_00_02 ' Vertical sync color mask. HVSyncColors long $03_03_03_03 ' Horizontal and vertical sync colors. ' //////////////////////Configuration Settings///////////////////////////////////////////////////////////////////////////////// directionState long 0 videoState long 0 frequencyState long 0 horizontalScaling long 0 verticalScaling long 0 horizontalPixels long 0 verticalPixels long 0 visibleScale long 0 invisibleScale long 0 horizontalLongs long 0 horizontalLoops long 0 screenorg long 0 ' added instead of reading it from par each loop ' //////////////////////Addresses////////////////////////////////////////////////////////////////////////////////////////////// pixelColorsAddress long 0 displayIndicatorAddress long 0 syncIndicatorAddress long 0 ' //////////////////////Run Time Variables///////////////////////////////////////////////////////////////////////////////////// counter res 1 buffer res 1 tileCounter res 1 tilesCounter res 1 screenPixels res 1 screenColors res 1 refreshCounter res 1 displayCounter res 1 i res 1 ' general purpose variable ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// fit 496 ' ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// {{ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // TERMS OF USE: MIT License /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, // modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the // Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }}