Search code examples
c64commodore

Commodore C64 how to detect PAL or NTSC


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


Solution

  • 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
    }