Pages: [1]   Go Down
Author Topic: The DHG - Dynamic Hi resolution Graphics driver  (Read 1394 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
God Member
*****
Karma: 6
Posts: 512
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I got feed up with not having hires graphics with tvout on the ATmega328 so I came up with my own platform.

The DHG, dynamic hires graphics.
It gives a true 176x184 pixel resolution graphics on the 328 without having a 4k videomemory.

How?
It's RAMtiled at a resolution of 22x23 tiles.

That makes for the full resolution in 88x88pixels or 11x11tiles.
The thing is that not all of the pixels in a screen is with in this square.

My solution put's out tiles in a much larger screen just where they are needed.
Not in a square format.

It dynamically allocates tiles for text, graphics and sprites from a pool and put's them all over the larger screen.
This means you have the full resolution on the screen but only where it's needed.

At the moment it only has 128 tiles so if you try to fill the screen, it wont work.
But a reasonable amount of text, sprites and gfx works really great.

I'll show you more.
« Last Edit: February 07, 2014, 04:36:19 pm by janost » Logged

Brisbane, Australia
Online Online
Edison Member
*
Karma: 33
Posts: 1121
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'll show you more.
Please do. This sounds very interesting.  Are you using some kind of memory external to the 328 to cater for swapping out tiles or is 11x11 the theoretical limit?

Will this leave much RAM for the rest of your project, or is the idea to have a standalone 328 that just drives the display with data received from another Arduino/328 project?

Geoff
Logged

"There is no problem so bad you can't make it worse"
- retired astronaut Chris Hadfield

Offline Offline
God Member
*****
Karma: 6
Posts: 512
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

No external memory.

It uses 506bytes for videomemory and 1024bytes for tilememory.
It leaves 500bytes for the application.

It's kind of hard to explain but it only places tiles on the screen where there are pixels and blank in between.
The second picture shows a spaceship, missile and the player ship.

They use a total of 8+1+4=13 tiles but still in full 176x184 resolution on the screen.
You just specify where you want to draw them.

The tiles can be overlapping, it will just "or" the new pixels in to the tile on the screen.
So the screen will appear to be a hires bitmap.

The driver takes care of the allocating the tiles and can set points, draw lines, rectangles and circles and even sprites on the screen.

The example draws 2 rotating lines in full resolution on a 176x184 screen.

Code:
// DHG - Dynamic Hi resolution Graphics driver by Jan Ostman
// This driver allocates tileRAM for pixels dynamically

// This is not complete yet but only here for inspiration

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

//----------------- VideoBlaster definitions -----------------

#define DOTCLK 1   // Pixel clock (0 for 8MHz, 1 for 4MHz)
#define HSYNC 132  // Hsync frequency (divided from Fcpu)
#define LINES 261  // Lines per field -1 (261 for NTSC, 311 for PAL)
#define SYNCPIN 4  // Pin in PORTD that is connected for sync

volatile byte VBE=0;     // Video blanking status. If this is not zero you should sleep to keep the video smooth

#define WAIT_VBE while (VBE) sleep_cpu();
#define WAIT_NOTVBE while (!VBE) {};

unsigned int scanline=0; // Dont touch, not volatile
unsigned int videoptr=0; // Dont touch, not volatile
byte row;                // Dont touch, not volatile

#define TILEALLOC 1      // This reserves extra tiles for BG or chars on screen not used by DHG
                         // 1 tile is always reserved for blank screen tiles
                        
char videomem[528];
char DHGmem[1032];
uint8_t nextfreetile=TILEALLOC;

#define SYNCDELAY   // This makes up the sync pulse low time delay
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); \
    asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n");
    
const byte MSPIM_SCK = 4;  // This is needed for the hardware to work
const byte MSPIM_SS = 5;   // This is needed for the hardware to work

//--------------------------------------------------------------

void setupVideoBlaster() {
  pinMode (MSPIM_SS, OUTPUT);  //A must for MSMSPI VIDEO to work
  pinMode (MSPIM_SCK, OUTPUT); //A must for MSMSPI VIDEO to work
  pinMode(2, OUTPUT); //Set D2 as output for Sync. A must for MSMSPI VIDEO to work
  UBRR0 = 0;
  UCSR0A = _BV (TXC0);
  UCSR0C = _BV (UMSEL00) | _BV (UMSEL01);
  UCSR0B = _BV (TXEN0);
  UBRR0 = DOTCLK;
  cli();
  TCCR0A = 0;
  TCCR0B = 0;
  TCNT0  = 0;
  OCR0A = HSYNC;  
  TCCR0A |= (1 << WGM01);
  TCCR0B |= (1 << CS01) | (0 << CS00);  
  TIMSK0 |= (1 << OCIE0A);
  set_sleep_mode (SLEEP_MODE_IDLE);
  sei();

  pinMode(1, OUTPUT); //A must for MSMSPI VIDEO to work
  digitalWrite(1,LOW); //A must for MSMSPI VIDEO to work

  for (int i=0;i<528;i++) {
    videomem[i]=0;
  }
}

ISR(TIMER0_COMPA_vect){    //Video interrupt. This is called at every line in the frame.
  byte c=8;  //Back porch
  byte d=4;  //Left Blank
  byte p=22; //Chars per row
  PORTD=0;
    if ((scanline>2)&&(scanline<40)||(scanline>231)) {
    SYNCDELAY
    PORTD = SYNCPIN;
    VBE=0;
  }

 if (scanline<3) {
    SYNCDELAY  
    PORTD =0;
    videoptr=0;  
    row=0;    
  }  
  
  if ((scanline>39)&&(scanline<232)) {
    SYNCDELAY  
    PORTD = SYNCPIN;
    while (c--) {
     asm("nop\n");
     asm("nop\n");
    }  
    UCSR0B = _BV(TXEN0);
    while (d--) {
     while ((UCSR0A & _BV (UDRE0)) == 0) {}
     UDR0 = 0;
    }
    while (p--) {
     UDR0 = DHGmem[videomem[videoptr++]*8+(row&7)];
     //while ((UCSR0A & _BV (UDRE0)) == 0)
     // {}
     //videoptr++;
    }  
    while ((UCSR0A & _BV (UDRE0)) == 0)
      {}  
    UCSR0B = 0;
    row++;
    videoptr=(row>>3)*22;
    VBE=1;
    }
  
  scanline++;
  if (scanline>LINES) {
    scanline=0;
  }
}

void Dclr() {
  for (uint16_t z=22;z<528;z++) {
   if (videomem[z]) {
     uint16_t pixelptr=videomem[z]<<3;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     DHGmem[pixelptr++]=0;
     videomem[z]=0;
   }
  }
  nextfreetile=TILEALLOC;
}

void Dpset(uint8_t x,uint8_t y) {
  if (nextfreetile<129) {
  if ((x<176)&&(y<184)) {
  uint16_t tileptr;
  uint16_t pixeltile=22+(((y>>3)*22)+(x>>3));
  if (videomem[pixeltile]) {
   tileptr=videomem[pixeltile]<<3;
   DHGmem[tileptr+(y&7)]|= (128>>(x&7));
  }
  else {
   videomem[pixeltile]=nextfreetile;
   tileptr=nextfreetile<<3;
   DHGmem[tileptr+(y&7)]|= (128>>(x&7));
   nextfreetile++;
  }
}
}
}

void Ddrawline(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
  int e;
  signed int dx,dy,j, temp;
  signed char s1,s2, xchange;
  signed int x,y;
  x = x0;
  y = y0;
  if (x1 < x0) {
     dx = x0 - x1;
     s1 = -1;
  }
  else if (x1 == x0) {
     dx = 0;
     s1 = 0;
  }
  else {
     dx = x1 - x0;
     s1 = 1;
  }
  if (y1 < y0) {
     dy = y0 - y1;
     s2 = -1;
  }
  else if (y1 == y0) {
     dy = 0;
     s2 = 0;
  }
  else {
     dy = y1 - y0;
     s2 = 1;
  }
  xchange = 0;    
  if (dy>dx) {
     temp = dx;
     dx = dy;
     dy = temp;
     xchange = 1;
  }
  e = ((int)dy<<1) - dx;  
  for (j=0; j<=dx; j++) {
     Dpset(x,y);
     if (e>=0) {
        if (xchange==1) x = x + s1;
        else y = y + s2;
           e = e - ((int)dx<<1);
        }
        if (xchange==1)
           y = y + s2;
        else
           x = x + s1;
           e = e + ((int)dy<<1);
     }
}

void Ddrawrect(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h) {
Ddrawline(x0,y0,x0+w,y0);
Ddrawline(x0,y0,x0,y0+h);
Ddrawline(x0+w,y0,x0+w,y0+h);
Ddrawline(x0,y0+h,x0+w,y0+h);
}

uint8_t BX=0;
uint8_t BY=10;
int8_t DX=1;
int8_t DY=-1;

uint8_t BX2=38;
uint8_t BY2=90;
int8_t DX2=1;
int8_t DY2=1;

uint8_t BX3=15;
uint8_t BY3=9;
int8_t DX3=1;
int8_t DY3=1;

uint8_t BX4=130;
uint8_t BY4=50;
int8_t DX4=1;
int8_t DY4=1;

void setup () {
setupVideoBlaster();
}

void loop () {
  WAIT_VBE
  Dclr();  // call this to clear the screen before drawing anything
  BX+=DX;
  BY+=DY;
  BX2+=DX2;
  BY2+=DY2;
  BX3+=DX3;
  BY3+=DY3;
  BX4+=DX4;
  BY4+=DY4;
  if ((BX>145)||(BX<1)) DX=-DX;
  if ((BY>153)||(BY<1)) DY=-DY;
  if ((BX2>145)||(BX2<1)) DX2=-DX2;
  if ((BY2>153)||(BY2<1)) DY2=-DY2;
  if ((BX3>145)||(BX3<1)) DX3=-DX3;
  if ((BY3>153)||(BY3<1)) DY3=-DY3;
  if ((BX4>145)||(BX4<1)) DX4=-DX4;
  if ((BY4>153)||(BY4<1)) DY4=-DY4;
  Ddrawline(BX,BY,BX2,BY2);
  Ddrawline(BX3,BY3,BX4,BY4);
  WAIT_NOTVBE
}


* DHG1.jpg (174.63 KB, 800x576 - viewed 59 times.)

* DHG2.jpg (37.31 KB, 382x304 - viewed 64 times.)
« Last Edit: February 07, 2014, 07:23:25 pm by janost » Logged

Des Moines, Iowa, USA
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

This is a great compromise! I suppose you could think of it as a sprite engine, in a way. (I saw another project that did something similar, and acted like the character mapped display in the old VIC-20 I had.) Very cool.
Logged

Embedded Software Engineer
UNO | Leonardo | Due | Teensy | BASIC Stamp
http://www.subethasoftware.com

West Yorkshire, UK
Offline Offline
Edison Member
*
Karma: 41
Posts: 1228
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Excellent! Reminds me of the way the graphics worked on the Sinclair ZX80/81. They only had 1K RAM and a 4MHz Z80 CPU, and no graphics processor, so the CPU did the graphics as well. There was a "FAST" mode you could use which would use the full speed of the CPU to execute the BASIC program, but the screen went crazy (no line/frame sync signals being produced) until you changed back to "SLOW" mode.

Paul
Logged

Des Moines, Iowa, USA
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

At the moment it only has 128 tiles so if you try to fill the screen, it wont work.
But a reasonable amount of text, sprites and gfx works really great.

I plan to try out your code soon. If the tiles are not being used, to they consume memory? I see great potential in a "Sprite Graphics" type system like this, for games without tons of background graphics. For instance, if a program was required to only use bitmap sprite data, stored in PROGMEM (too slow to use directly?), and the engine just kept an array of how many things it was displaying and where they were supposed to be drawn, it would free up a ton of memory.

I suspect PROGMEM might be too slow, so RAM would have to be allocated for the sprites that were in use. I might have eight bitmaps for an Asteroids ship, and display frame 1 at X,Y, using up only the RAM/overhead for that bitmap.

Interesting concept. I just started playing with Arduino video output last weekend, and have much to learn. (I am documenting my attempts to make a Pac-Man clone at www.subethasoftware.com)
Logged

Embedded Software Engineer
UNO | Leonardo | Due | Teensy | BASIC Stamp
http://www.subethasoftware.com

Des Moines, Iowa, USA
Offline Offline
Jr. Member
**
Karma: 0
Posts: 52
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

IWould it be possible for the user to only allocate memory for tiles that are needed? Something like this:

The code would define the bitmap data for the sprites...

// 8x8 spaceship... A box because I am lazy.
#define SPACEBOXW 8
#define SPACEBOXW 8
PROGMEM const uint8_t spacebox[] = {
  0b11111111,
  0b10000001,
  0b10000001,
  0b10000001,
  0b10000001,
  0b10000001,
  0b10000001,
  0b11111111
};

Then, assuming doing PROGMEM reads might be too slow for video, RAM would be allocated for that tile/sprite. Maybe a structure like this that could contain info on where the tile is supposed to be drawn, and information about the tile size:

typedef struct {
  uint8_t x, y;
  uint8_t w, h;
  uint8_t *dataPtr;
} TILE;

Then something like this (maybe hidden in a function to make it easier):

TILE spacebox;
// RAM to hold the copy of the sprite data
uint8_t spaceboxRam[sizeof(spacebox)];
// Copy PROGMEM data to RAM buffer
memcpy_PF(spaceboxRam, spacebox, sizeof(spacebox));
// Update tile object to know where RAM buffer is.
spacebox.dataPtr = spaceBoxRam;
spacebox.x = 255; // Maybe 255 means "do not display"
spacebox.y = 255;

Then some interface to add/remove it to the stuff the video engine is drawing on the screen.

addTile(&spacebox);

Then in the code loop, the engine would go through all the tiles that have been added and display them. The use would do stuff to move them just by modifying the object:

spacebox.x = 50;
spacebox.y = 50;

For a game, such as Space Invaders, where there are five rows of eleven types of aliens, there would be RAM for five tiles buffers allocated, and there would be 55 (11x5) TILE structures to track each invader.

For animation, the tile would just have the pointer changed to point to the next frame. (A clever function could be written to let the user pass in an array of frames and have it actually handle the animation directly.)

spacebox.dataPtr = spaceboxRam2;

(I would be using an array of frames, most likely, and be setting them to something like spaceboxRam[framNum]);

NOTE: This would have to be done between interrupts so it didn't mess anything up, I suspect?

Just some quick thoughts. Displaying full screens of text this would wouldn't necessarily be practical, but it might be great for games. I have about 220 bytes free for my Pac-Man demo, using the low resolution of TVout. It would be fun to see what could be done in an environment with more RAM to play with, and higher resolution to boot!
Logged

Embedded Software Engineer
UNO | Leonardo | Due | Teensy | BASIC Stamp
http://www.subethasoftware.com

Offline Offline
God Member
*****
Karma: 6
Posts: 512
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

No. PROGMEM is not slow but it kind of defeats the purpose of dynamic allocation.
With progmem it becomes ROM tiled or what is in everyday called a char-rom smiley
My VideoBlaster platform runs with ROMtiles or text chars.

Your are in charge of your own program so if it doesn't require all 128 tiles then just lower the amount.
Change the size of DHGmem and lower the test for maxtiles in the Dpset function and it should work.

The thing with sprites (like the DHG takes care of) is that, say an 8x8 sprite, takes a single tile when at a position multiple of 8.
Once its moved a single pixel to the left or down it takes 2 or 4 tiles because of the boundary.

There isn't much time in the pixelshifting for or-ing pixels so the data really needs to be prepared on a linebasis.
And then there isn't time to prepare the next line.

For your space invaders example, you poke out a number of fixed tilespointers in videomem.
Then you animate them by changing data directly in the DHGmem.
« Last Edit: February 14, 2014, 12:56:39 pm by janost » Logged

Pages: [1]   Go Up
Jump to: