Commodore C64 how to detect PAL or NTSC

438 Views Asked by At

Background Information

I am currently developing a programming API for the Commodore C64 using KickC [in Beta] to allow me to more easily develop small programs, applications and possibly some games; it occurred to me that I may need a way to check if my code is running on PAL or NTSC machines, and in the latter case, which NTSC machine, as the old NTSC C64 has one fewer scanline than the newer C64 version.

After asking about for some help, Robin Harbron sent me a code snippet which works including with a CMD SuperCPU attached (my eventual target machine). As he sent it as assembly, I had to use most of it as it was, but use the ASM directive in KickC, as follows:

/**
 * This is the initialisation will
 * determine the machine type
 * by setting the getMachineType global
 * as follows:
 *  37 is PAL
 *  5 is NTSC (old)
 *  6 is NTSC (new)
 *  0 (or any other value) is unknown
 *
 * For safety, the initial value of 0xc000
 * is stored into the accumulator and
 * pushed onto the stack; it is then
 * restored after the getMachineType
 * global is set
 *
 * @author Robin Harbron
 */
void setMachineType() {
    asm {
        lda $c000
        pha
        sei
    __br1:
        lda $d011
        bmi __br1
    __br2:
        lda $d011
        bpl __br2
    __br3:
        lda $d012
        bit $d011
        bpl __ex1
        sta $c000
        bmi __br3
    __ex1:
        cli
    }
 
    getMachineType = peek(0xc000);
 
    asm {
        pla
        sta $c000
    }
}

At the top of my script, I have the getMachineType declared globally like this:

unsigned byte getMachineType;

and at the moment, my peek() function works like this:

/**
 * Returns a single byte from a specific
 * memory location
 */
unsigned char peek(unsigned short location) {
    unsigned char* memory = (char*)location;
    unsigned char returnByte = *memory;
 
    return returnByte;
}

So now I can determine the available number of scan lines on the host machine running my program, and therefore more easily create PAL and NTSC compatible executables.

KickC is available to download from CSDb.dk, and will build and assemble with Kick Assembler

1

There are 1 best solutions below

2
On BEST ANSWER

The assembly code you shared is using the SCROLY register ($D011) and the RASTER register ($D012) of the VIC-II chip.

The high bit of $D011 is the most significant bit of the current raster scan line, while $D012 contains the lower 8 bits.

NTSC systems have 262 (or 261?) scan lines, PAL have 312.

The assembler routine waits for the instant the high bit is set , i.e. scan line 256.

IF the high bit is set it loops until it is not set:

    __br1:
        lda $d011
        bmi __br1         // read $d011 again if high bit is set

Then it loops while the high bit is clear, exiting the loop as soon as it becomes set:

    __br2:
        lda $d011
        bpl __br2         // read $ d011 again if high bit is clear

Then it falls through to load the lower 8 bits of the RASTER scanline from $d012

This keeps storing the lower 8 bits of the scanline value in $c000 while testing the high bit until it clears again. If it has cleared, don't store the lower 8 bits, instead exit the loop through __ex1

    __br3:
        lda $d012
        bit $d011
        bpl __ex1
        sta $c000
        bmi __br3
    __ex1:
        cli

The result should be The number of the highest scanline observed - 256. For NTSC with 262 scanlines this is where you get the return value of 6 (or 5 for that other NTSC model), it's zero-based so 5 would be the value for the 262 scanline NTSC version. Zero based scanline 312 would be 311, minus 256 = 55, in hex $37 .. The comments there should really have specified that the 37 was hexadecimal.

You can find information about these registers in the excellent "Mapping the Commodore 64" book.

You could translate all of this assembler into C (with the exception of setting the interrupt disable bit and clearing it: sei, cli), just assign a char * pointer the value 0xD011, and another 0xD012 and do the same thing with C code.

char machineType = 0;
signed char * SCROLY = 0xd011;
char * RASTER = 0xd012;

asm {
  sei
}

while (*SCROLY < 0) ; // spin until scaline wraps
while (*SCROLY > 0) ; // spin until high bit set again
while (true) {
  char lines = *RASTER;
  if (*SCROLY > 0)
    break;
  machineType = lines;
}

asm {
  cli
}