Arduino/ATmega328 C64 Emulator

Some code in this line makes a wait longer than 16 cycles before loading the first byte in the shifter.
That's what is making the White bar.

UDR0 = pgm_read_byte (linePtr + (* messagePtr++));

I use this batch file to disassemble programs and look at the compiled code.

Save it as "disassemble.bat" (or whatever...)

rem This is where you installed the Arduino IDE
set avr="c:\Program files\arduino-1.0.5\hardware\tools\avr\bin\avr-"

rem This is "build.path" from your preferences file
set build="h:\temp\arduino\build\"

rem This is the name of your sketch
set target=blink

rem Disassemble the program
%avr%objdump.exe -S -z %build%%target%.cpp.elf >disassembly_elf.txt

rem Show memory usage
%avr%nm.exe -S %build%%target%.cpp.elf >nm_out.txt

This code appears to output successfully every 16 cycles:

const byte pixelPin = 1;     // <------- Pixel data
const byte MSPIM_SCK = 4;    // <--  clock
const byte vSyncPin = 10;    // <------- VSYNC

const byte enable = 7;

void setup()
  {
  // Set up USART in SPI mode (MSPIM)
  
  // baud rate must be zero before enabling the transmitter
  UBRR0 = 0;  // USART Baud Rate Register
  pinMode (MSPIM_SCK, OUTPUT);   // set XCK pin as output to enable master mode
  UCSR0B = 0; 
  UCSR0C = bit (UMSEL00) | bit (UMSEL01) | bit (UCPHA0) | bit (UCPOL0);  // Master SPI mode

  digitalWrite (enable, HIGH);
  pinMode (enable, OUTPUT);

}  // end of setup

#define nop asm volatile ("nop")

void loop() 
  {
  int i = 255;
  digitalWrite (enable, LOW);
  
  // turn transmitter on 
  UCSR0B = bit (TXEN0);  // transmit enable (starts transmitting white)
  
  while (i--)
    {
    UDR0 = i;
    nop; nop;
    nop; nop;
    nop; nop;
    nop; nop;
    }

  // wait till done    
  while (!(UCSR0A & bit(TXC0))) 
    {}
  
  // disable transmit
  UCSR0B = 0;   // drop back to black
 
  digitalWrite (enable, HIGH);

 }  // end of loop

Eyeballing the results in the logic analyzer it looks like the data is OK.

 114:   01 97           sbiw    r24, 0x01       ; 1   (2)
 116:   80 93 c6 00     sts     0x00C6, r24   (2)
 11a:   00 00           nop   (1)
 11c:   00 00           nop   (1)
 11e:   00 00           nop   (1)
 120:   00 00           nop   (1)
 122:   00 00           nop   (1)
 124:   00 00           nop   (1)
 126:   00 00           nop   (1)
 128:   00 00           nop   (1)
 12a:   00 97           sbiw    r24, 0x00       ; 0   (2)
 12c:   99 f7           brne    .-26            ; 0x114 <loop+0x14>   (1/2)

Some code in this line makes a wait longer than 16 cycles before loading the first byte in the shifter.

Do you mean for the first character or every character?

OMG! It works!
Managed to get Nick's shiftloader to work by shuffling around line statments in the code.

Its not optimal and uses both plaster and glue but the size is a fraction of the TVout library but with a text resolution of 40x25 and a gfx resolution of 160x100.

The 5" LCD screen does by no way like an 8MHz pixelclock but this is so cool :slight_smile:

Now its time to optimize :slight_smile:

40x25 CBMvideo4.jpg

Man, this is looking great! Glad somebody is using my 6502 emulator. I'm really looking forward to seeing how far you can take this.

No. It was the first char on every scanline.
Somehow the shifter was not in sync as there are no wait for transmitter ready so it delayed the load of the first char.

I moved preload of pointers and load of variables around and it works.

miker00lz:
Man, this is looking great! Glad somebody is using my 6502 emulator. I'm really looking forward to seeing how far you can take this.

I'm already running your emulator on a Nano with both the VIC-20 and C64.
This is for giving it a screen :slight_smile:

It runs with a character mapped textmode of 40x25.
With blockgraphics it does a real 160x100 graphicsmode.

That outperforms the TVout library and still has 30K progmem and 1K RAM free :slight_smile:

janost:
It runs with a character mapped textmode of 40x25.
With blockgraphics it does a real 160x100 graphicsmode.

That outperforms the TVout library and still has 30K progmem and 1K RAM free :slight_smile:

It would be cool to put the character ROM in RAM so we can redefine the charset for games. There's obviously not enough RAM for a complete charset, but it might work with 96/64 chars. The first 32 ASCII chars are non-visible anyway.

Maybe even the ability to select ROM/RAM charset as the screen draws (user sets a flag in their program). That way you can draw the score at the top with ASCII chars then have your RAM-based charset underneath.

If you add raster information (increment a byte on every screen line for us to look at) then we could modify the RAM charset as the screen scans and get full hires graphics (sounds nasty but the ATARI VCS was much worse than that).

Nick, I enabled sleepmode but only on the lines containing pixels (200).
Thats because I need the rest of the time for performing other tasks.

It works, the picture is rocksolid.

However my 32" flatscreen TV would not show anything and says "No Signal".
I know they are a bit kinky about the signal and I dont have a true Vsync with pre and post syncs.

janost:
OMG! It works!
Managed to get Nick's shiftloader to work by shuffling around line statments in the code.

That's the problem with compilers - the timing comes and goes.

janost:
Now its time to optimize :slight_smile:

Assembly language!

fungus:

janost:
It runs with a character mapped textmode of 40x25.
With blockgraphics it does a real 160x100 graphicsmode.

That outperforms the TVout library and still has 30K progmem and 1K RAM free :slight_smile:

It would be cool to put the character ROM in RAM so we can redefine the charset for games. There's obviously not enough RAM for a complete charset, but it might work with 96/64 chars. The first 32 ASCII chars are non-visible anyway.

Maybe even the ability to select ROM/RAM charset as the screen draws (user sets a flag in their program). That way you can draw the score at the top with ASCII chars then have your RAM-based charset underneath.

If you add raster information (increment a byte on every screen line for us to look at) then we could modify the RAM charset as the screen scans and get full hires graphics (sounds nasty but the ATARI VCS was much worse than that).

The character rom is just an Array with defs.
For a game you would redefine it anyway with your own chars for those not used for text in the game.
And you dont need chardefs for chars you never display so it does not have to be 2K in size.
The true graphicsmode does however need the full 2K charfont unless we can XOR.

It can run a Copper telling it if the line is char or gfx on any line.
It can even run sprites on the odd frames since its NonInterlaced.

2 separate linescan routines for odd/even lines.

Here is the code for the screenshot in the picture.
Remember it is VERY sensitive to changes in the code, even the order of the linesstatements.

I will make this into the Videoblaster library.

is it ok to use IFDEF for settings like PAL/NTSC, 40x25/22x23 and others or does it need to be runtime configurable?

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

const unsigned char charROM [8] [128] PROGMEM = { 0x1C , 0x18 , 0x7C , 0x1C , 0x78 , 0x7E , 0x7E , 0x1C  };

const byte MSPIM_SCK = 4;
const byte MSPIM_SS = 5;

unsigned int scanline=0;
unsigned int videoptr=0;
byte row;
byte charcode;
byte border=0x00;
char videomem[1040];

char str1[] = "THIS WORKS BUT IS NOT OPTIMAL";
char str2[] = " SAY HI TO THE VIDEOBLASTER  ";
char str3[] = "      NO MORE  TVOUT  :)     ";
char str4[] = "   40X25 TEXT 160X100 GFX    ";

void setup() {
  pinMode (MSPIM_SS, OUTPUT);   // SS
  // must be zero before enabling the transmitter
  UBRR0 = 0;
  UCSR0A = _BV (TXC0);  // any old transmit now complete
  pinMode (MSPIM_SCK, OUTPUT);   // set XCK pin as output to enable master mode
  UCSR0C = _BV (UMSEL00) | _BV (UMSEL01);  // Master SPI mode
  UCSR0B = _BV (TXEN0);  // transmit enable
  // must be done last, see page 206
  UBRR0 = 0;  // 4 Mhz clock rate
  pinMode(2, OUTPUT); //Set D2 as output for Sync
  cli();
  //set timer0 interrupt at 15625Hz
  TCCR0A = 0;// set entire TCCR0A register to 0
  TCCR0B = 0;// same for TCCR0B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 15625hz increments
  OCR0A = 133;// = (16*10^6) / (15625*8) - 1 (must be <256)
  //PAL OCR0A = 134;// = (16*10^6) / (15625*8) - 1 (must be <256)
  
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS01 and CS00 bits for 9 prescaler
  TCCR0B |= (1 << CS01) | (0 << CS00);   
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);
  set_sleep_mode (SLEEP_MODE_IDLE);
  sei();
  for (int x=0; x <1040; x++){
    videomem[x]=32;
  }
byte z;  
for (int x=0; x <29; x++){
z=str1[x];
if (z>63) z=z-64;
videomem[x+45]=z;
}
for (int x=0; x <29; x++){
z=str2[x];
if (z>63) z=z-64;
videomem[x+285]=z;
}
for (int x=0; x <29; x++){
z=str3[x];
if (z>63) z=z-64;
videomem[x+525]=z;
}
for (int x=0; x <29; x++){
z=str4[x];
if (z>63) z=z-64;
videomem[x+645]=z;
}


}

void loop() {
if ((scanline>55)&&(scanline<263)) sleep_mode (); 

}  
  
ISR(TIMER0_COMPA_vect){//timer0 interrupt 
  byte c=3;
  byte p=40;
  // NTSC if ((scanline>3)&&(scanline<40)||(scanline>239)) {
    if ((scanline>3)&&(scanline<56)||(scanline>263)) {
    UCSR0B = _BV(TXEN0);
    PORTD = 0; //Hsync
    UDR0 = 0x00; //Load first byte
    while (c--); {  
     // wait for transmitter ready
     while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
    // send pixelbyte
    UDR0 = 0x00;
    }
    while ((UCSR0A & _BV (UDRE0)) == 0)
      {}  
    if (border==0) UCSR0B = 0;
    PORTD =4;       
  }

 if (scanline<4) {
    c=3;
    UCSR0B = _BV(TXEN0);
    PORTD = 0; //Vsync
    UDR0 = 0x00;
    while (c--); {
    while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
    UDR0 = 0x00;
    }
    PORTD =0;
    UCSR0B = 0;
    videoptr=0;
    row=0;    
  }  
  
  //NTSC if ((scanline>39)&&(scanline<240)) {
  if ((scanline>55)&&(scanline<263)) {
    asm("nop\n");
    asm("nop\n");
    asm("nop\n");
    UCSR0B = _BV(TXEN0);
    PORTD = 0; //Hsync
    UDR0 = 0x00; //Load first byte
    c=3;
    while (c--) {
     // wait for transmitter ready
     while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
    // send pixelbyte
    UDR0 = 0x00;
    }
  
    while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
     //send colorburst
     UDR0 = B10101010;
     PORTD =0;
     while ((UCSR0A & _BV (UDRE0)) == 0)
      {}
    const register byte * linePtr = &charROM [ row & 0x07 ] [0];
    register byte * messagePtr = (byte *) & videomem [videoptr] ;
     PORTD =4;

    UCSR0B = _BV(TXEN0); 
    
    c=3;
    while (c--) {
    //for (byte x=0; x < 3; x++){
     // wait for transmitter ready
     while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
     // send pixelbyte
     UDR0 = 0x00;
    }
    c=7;
    while (c--) {
     // wait for transmitter ready
     while ((UCSR0A & _BV (UDRE0)) == 0)
      {} 
    // send pixelbyte
    UDR0 = 0;
    }
    
    UDR0 = pgm_read_byte (linePtr + 32);
  
    while (p--) {  
    UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
    }
  
    while ((UCSR0A & _BV (UDRE0)) == 0)
      {}
    UDR0 = border; //Front porch
    
    if (border==0) UCSR0B = 0;
    
    row++;
    videoptr=(row>>3)*40;
  }
  
  scanline++;
  if (scanline>311) scanline=0;
  
  
  
}

janost:
However my 32" flatscreen TV would not show anything and says "No Signal".
I know they are a bit kinky about the signal and I dont have a true Vsync with pre and post syncs.

I must admit that when I started tweaking my own code this would happen to me too. The exact timing between pixels and vsync (and hsync) was apparently fairly important.

I think even starting the pixels a bit early (and changing nothing else) would do it, as if the monitor was saying "nuh, uh, that can't be video data!".

is it ok to use IFDEF for settings like PAL/NTSC, 40x25/22x23 and others or does it need to be runtime configurable?

I doubt it. You only connect to one monitor, right? Most people would either have PAL or NTSC (depending on country).

janost:

fungus:
It would be cool to put the character ROM in RAM so we can redefine the charset for games.

For a game you would redefine it anyway with your own chars for those not used for text in the game.

We used to change the character sets dynamically, that's why it's good to have it in RAM.

eg. To do sprites on a background we would reserve some chars at the end of the charset to combine sprite+background together. Horizontal/vertical starfields would also be done by moving a dot inside a single char then move the char across the screen as the dot wraps around.

fungus:

janost:

fungus:
It would be cool to put the character ROM in RAM so we can redefine the charset for games.

For a game you would redefine it anyway with your own chars for those not used for text in the game.

We used to change the character sets dynamically, that's why it's good to have it in RAM.

eg. To do sprites on a background we would reserve some chars at the end of the charset to combine sprite+background together. Horizontal/vertical starfields would also be done by moving a dot inside a single char then move the char across the screen as the dot wraps around.

yes, I know. Redefine chararacters ingame.
The tight inner scanloop has to be changed to fetch from RAM.

That can only be selected on a textline basis and can be done with a 25byte copperlist that tells the scanning if a line is ROM/RAM.

The variables scanline, row and videoptr are global at can be read to know where the rasterbeam is at the moment.

janost:
That can only be selected on a textline basis and can be done with a 25byte copperlist that tells the scanning if a line is ROM/RAM.

Don't really need a copperlist (which wastes RAM). Just let me know which screen line I'm on and give me access to the video control variables. That way I can select charsets, do smooth scrolling in a window, etc. :slight_smile:

I tried to run my videoblaster with the emulatorcode but the Wire library doesnt work with my high interrupt rate.

Is there some softwire library that bitbangs the i2c?

I did set it up yesterday for blockgraphic hiresmode and the resolution is 80x100.
There isnt enough fontentrys to make it 160x100 but it can run at 160x50 to.

janost:
I did set it up yesterday for blockgraphic hiresmode and the resolution is 80x100.
There isnt enough fontentrys to make it 160x100 but it can run at 160x50 to.

Is that with a 4x2 matrix of pixels inside each char?