Contact me | Login | Search | Sitemap | Site Notice

Automatic Hardware Detection

While working on several Apple ][ projects I was pointed to a relevant problem by one of my fellow Apple ][ enthusiasts regarding hardware prerequisites in order to run different demo programs. Since the Apple ][ series consists of quite a few different hardware setups it would be nice if a program could detect parts of the available hardware in the system itself in order to determine if the hardware configuration to run the program or in case that it is not sufficient to terminate itself with an appropriate error message.

Apart from the Apple IIgs especially in the older models possibilities to do so are rather limited. However, with some simple tricks we can find out e.g. which Apple ][ model the software is running on and which CPU is used in that specific Apple ][ model. 

In the following paragraphs I want to demonstrate some of the concepts with short code snippets. The code is based in large part on information I found in French Touch's software archive. I extracted, edited and extended the code where necessary. The code given below is in Merlin32-format.

Here is a DSK-image with some example code trying to detect multiple hardware features of the Apple II series.



Detecting the CPU

The first Apple ][ was equipped with an NMOS 6502 microprocessor. Later models from the Apple //e enhanced on were equipped with a CMOS 65C02 version with an updated set of opcodes. Using 65C02 opcodes with a 6502-processor leads to unpredictable behaviour, i.e. generally speaking software designed for the 65C02 will not work on a standard 6502.

Since the 65C02 is pin compatible to the 6502 there is the possibility in modding older Apple ][ models the enhanced CPU. 

The Apple IIgs is driven by a 65816 CPU (16 bit architecture) which is downward compatible with the 6502 opcodes. Originally only an Apple IIgs was equipped with this CPU. However, there are projects online where a 65816-CPU is modded into older Apple ][ systems so a pure detection of e.g. an Apple //e system does not rule out the possibility that the CPU may have been modded to be a 65816!

Detecting which of the three CPUs is present is rather simple as the following short code snippet shows. The code returns the variable b65C02 which holds the value

  • $00: for a 6502 CPU
  • $01: for a 65C02 CPU
  • $02: for a 65816 CPU

* detect CPU type
        	BRA 	_is65C02  ; results in a BRA +4 if 65C02/65816 
          	LDA	#00	  
          	BEQ 	_contD 	  ; a 6502 drops directly through here 
_is65C02  	LDA 	#01	  ; 65C02/65816 arrives here
				  ; some 65816 code here
        	XBA               ; put $01 in B accu -> =NOP for 65C02
        	DEC	A         ; A=$00 if 65C02
        	XBA               ; get $01 back if 65816
        	INC  	A         ; makes $01 (65C02) or $02 (65816)
_contD    	STA 	b65C02    ; save variable value

How does this small subroutine work?

First a branch opcode BRA is issued which directs the processor to the label _is65C02 if the BRA-opcode is interpreted correctly by the CPU. A 6502 cannot deal with a BRA-opcode ($80) and the following four byte ($04) jump step. Hence a 6502 will read over the BRA-command ending up at the BEQ _contD statement with $00 in the accumulator. This immediately leads to a branch to _contD and storing the value $00 in the return variable b65C02 which identifies the CPU to be a 6502!

A 65C02 and a 65816 will interpret the BRA-opcode correctly branching to the line labelled _is65C02 loading the value $01 into the accumulator. Distinguishing between the 65C02 and the 65816 uses two characteristic features of the CPUs:

  • The code uses the 65816-opcode XBA which exchanges the HI-byte of the 16 bit accumulator with the LO-byte of the 65816. This opcode is not present in the 65C02. Since all unused opcodes are wired to a NOP-operation in the 65C02 the XBA-opcode is ignored by the 65C02
  • The LDA #01 command loads the value $01 in both the 8 bit accumulator of the 65C02 and the 16 bit accumulator of the 65816. Issuing an XBA-opcode will move the value $01 in the LO-byte of the 16 bit accumulator to the HI-byte and a $00 from the HI-byte to the LO-byte. In the 65C02 nothing will happen since the XBA is treated as a NOP.
  • Decrementing the accumulator via a DEC A will decrement the 65C02 accumulator from $01 to $00 and the 65816 accumulator from $00 to $FF leaving the HI-byte unchanged when the m-flag is set to 8-bit operations accordingly!
  • The next XBA-opcode will move the $01 from the HI-bte back to the LO-byte
  • INC A adds $01 to the accumulator resulting in a $01 in the 65C02 and $02 in the 65816. 

The result $00, $01 or $02 is returned in the variable b65C02

There has been an ongoing discussion in a Facebook's 6502 experts group and I present two alternative solutions for CPU detection on an extra page.

Automatic Machine Detection

While CPU detection was done by using different OP-codes that are specific for each CPU-type, determining the machine type is a bit easier to understand and is mainly based on reading certain memory addresses in ROM in order to find byte values that are specific for the different machines. Take a look at the following lines of code:

* detect type of Apple 2 computer
        	LDA 	$FBB3
        	CMP 	#$06        	; IIe/IIc/IIGS = 06 
        	BEQ 	checkII	   	; if not II ($38) or II+ ($EA)
_G22PLUS  	STA	bMachine    	; save $38 or $EA as machine byte
checkII	  	LDA 	$FBC0       	; detect IIc
        	BEQ 	_G2C        	; 0 = IIc / Other = no IIc
        	SEC			; IIgs or IIe ? 
        	JSR 	$FE1F       	; test for GS 
        	BCS 	_G2E        	; if carry flag is set -> IIE
        	LDA 	#$FF        	; IIGS -> $FF
        	STA 	bMachine
_G2E    	LDA 	#$7F        	; IIE -> $7F
    		STA 	bMachine    
_G2C    	STA 	bMachine    	; IIc -> $00

The first byte which is read from ROM is located at $FBB3. The value obtained from that memory location is specific for an Apple ][ ($38) or an Apple ][+ ($EA). So we can distinguish these two machines from the other types that return a $06 instead.

For detecting a IIc the memory location $FBC0 is read which returns a $00 if the machine is an Apple IIc.

This leaves the Apple //e and Apple IIgs: for distinguishing these two machines we set the carry flag and call a subroutine at $FE1F. In all machines but the Apple IIgs there is an RTS-opcode at this location resulting in an immediate return with the carry flag still set. In the Apple IIgs, however, there is a routine that returns compatibility information in the A-, X- and Y-registers. Furthermore the carry flag is reset.

In order to determine if the machine is a IIgs you have to observe the carry flag. If it is still set you have an Apple //e. If the carry flag has been cleared you have an Apple IIgs!

Hence the routine returns the following values:

  • $38: Apple ][
  • $EA: Apple ][+
  • $00: Apple IIc
  • $7F: Apple //e
  • $FF: Apple IIgs