How did the Apple IIe convert commands to upper case?


I've been using the Virtual ][ emulator in Apple IIe mode (with an Apple IIe Enhanced ROM).

After getting the ELIZA code from Why did BASIC programs tend to READ a redundant copy of DATA? and chopping it about1 to get it to run on the emulator, I realised that some lower to upper case conversion would be useful... but Applesoft BASIC doesn't seem to have any case conversion functions.

I've been playing around with the code below, which I found on Handling Upper and Lower Case on Apple Computers2 , to make all input become uppercase (via CALL802):


120 DATA 169,9,133,54,169,3,133,55,96,134,6,174,179,251,224,234,208,11,201,225,48,7,201,251,16,3,56,233,32,166,6,76,240,253,169,43,133,56,169,3,133,57,96,32,27,253,201,225,48,7,201,251,16,3,56,233,32,96
130 HOME
150 PRINT "After a CALL 768 is executed, this"
160 PRINT:PRINT "program will display only upper case"
170 PRINT:PRINT "on an Apple ][ Plus."
200 PRINT "Once a CALL 802 is executed, input will"
210 PRINT : PRINT "automatically be converted to upper"
220 PRINT: PRINT "case on any kind of Apple computer."

For anyone who is interested, the assembly code is in the footnote3.

Commands converted to uppercase

However, I then noted that A touch of Applesoft BASIC (1986), on page 30, states:

You can use both uppercase and lowercase characters, but the computer converts all letters to uppercase.

I then saw in this answer to What are the differences between the versions of AppleSoft BASIC?, that the Apple IIe Enhanced (1985) was the first version that...

Allowed entry of lowercase commands

Therefore, in the Apple IIe Enhanced ROM there must be already a call to convert lowercase to uppercase.

I can't find a decent dissection nor disassembly of a IIe Enhanced ROM. I've only found this one for the Apple II+ (from Apple II ROM Disassembly) - which won't include the functionality because it is too early.


  • Where is this code?
  • How is it done and does anyone have an assembly listing?
  • Is there a hook to this routine that can be called from BASIC?


1 Primarily, by removing UPPER$ as this doesn't exist in Applesoft BASIC (and REMming out the first line (line 10)).

2 Note that the browser will moan about a lack of certificates.

3 The assembler for the POKEd code above is as follows:

0000:            1 **********************************
0000:            2 *                                *
0000:            3 * Routines for handling upper &. *
0000:            4 * lower case on Apple ][ Plus,   *
0000:            5 * //e, and //c computers         *
0000:            6 *                                *
0000:            7 **********************************
0000:            8 *
0036:            9 CWSL     EQU $36     ;output hook
0038:           10 KWSL     EQU $38     ;input hook
FDF0:           11 COUT     EQU $FDF0   ;sends character to screen
FB83:           12 IDBYTE   EQU $FB83   ;test for model of Apple
FD1B:           13 KEYIN    EQU $FD18   ;routine to get keystroke
0006:           14 SAVEX    EQU $06     ;temporary storage
0300:           15          ORG $300
0300:           16 *
0300:           17 **** This routine handles screen output.
0300:           18 *
0300:A9 09      19 HOOKOUT  LDA #>OUT   ;Hook routine
0302:85 36      20          STA CWSL    ;called 'OUT'
0304:A9 03      21          LDA #<OUT   ;to handle output
0306:85 37      22          STA CWSL+1
0308:60         23          RTS
0309:86 06      24 OUT      STX SAVEX   ;Save X-register
030B:AE 83 FB   25          LDX IDBYTE  ;Is this computer a
030E:E0 EA      26          CPX #$EA    ;][ Plus?
0310:D0 0B      27          BNE SEND    ;No.
0312:C9 E1      28          CMP #$E1    ;Check whether
0314:30 07      29          BMI SEND    ;character is
0316:C9 FB      30          CMP #$FB    ;lower case
0318:10 03      31          BPL SEND    
031A:38         32          SEC         ;Make lower case into
031B:E9 20      33          SBC #$20    ;upper case.
031D:A6 06      34 SEND     LDX SAVEX   ;Restores X-register
031F:4C F0 FD   35          JMP COUT    ;Send character to screen.
0322:           36 *
0322:           37 **** This routine handles keyboard input.
0322:           38 *
0322:A9 28      39 HOOKIN   LDA #>IN    ;Hook routine
0324:85 38      40          STA KWSL    ;called 'IN'
0326:A6 03      41          LDA #<IN    ;to handle input.
0328:85 39      42          STA KWSL+1
032A:60         43          RTS
032B:20 1B FD   44 IN       JSR KEYIN   ;Get a keystroke.
032E:C9 E1      45          CMP #$E1    ;Check whether
0330:30 07      46          BMI RETURN  ;character is
0332:C9 FB      47          CMP #$FB    ;lower case.
0334:10 03      48          BPL RETURN
0336:38         49          SEC         ;Make lower case into
0337:E9 20      50          SBC #$20    ;upper case.
0339:60         51 RETURN   RTS

4 Answers 4


This is actually done as part of the Applesoft tokenizer. Basically when you type a line of code into the Applesoft interpreter, the very first thing that happens is that the line is tokenized -- it goes through the line and

  1. eliminates all space characters
  2. compares all byte sequences with the keywords and replaces each sequence that matches a keyword with a single byte code for the keyword.
  3. looks for " characters and supresses the above changes between them

So the case conversion actually happens implicitly in step 2 -- when comparing bytes, it ignores (masks out) bit 5 (0x20) so it does this match case-insensitively.

This tokenized form is then what is stored memory and interpreted. When you give it a LIST command, all the keyword token bytes are expanded -- replaced by the keyword in with a space both before and after. Since the original case of the text that was converted to a single byte is lost, the keyword is always displayed in upper-case.

  • I'm sure that you are correct, and maybe I am missing something, but looking at the Apple IIe character set, 'p' is $30 (00110000) and 'P' is $70 (01110000), so it would require XOR 01000000 which is XOR $40, not $20, to toggle bit 6 (not bit 5). So I'm confused. Commented Nov 28, 2023 at 18:46
  • Ah, I see that it is the ASCII code that is manipulated, and not the Apple IIe character code. The ASCII and Apple II character sets do not match at all and even the positions of the upper and lower case characters is reversed. Commented Nov 28, 2023 at 20:47
  • 1
    @Greenonline: I can't think of any context in which the Apple IIe would bytes to characters in the manner shown for that Apple //e table. It doesn't match screen codes, nor codes employed by the I/O routines, nor the codes used by any part of the AppleSoft BASIC interpreter.
    – supercat
    Commented Nov 28, 2023 at 23:31
  • 1
    @Greenonline: That link looks to be just wrong. The Apple IIe's character set is the same as the Apple II/II+, except with the last 32 characters (0xE0-0xFF) replaced with the lower case letters.
    – Chris Dodd
    Commented Nov 29, 2023 at 0:19
  • @ChrisDodd - yes, this answer also states that the page is messed up Commented Nov 29, 2023 at 6:40

This post has a brief history of Applesoft. You'll note it was modified in 1984 to allow entry of lower-case keywords.

If you look at the PARSE routine (at $d56c) in the ][+ version of Applesoft in ROM, it looks like this:

PARSE           inx                   ;next input character
LD56D           lda   INPUT_BUFFER,x
                bit   DATAFLG         ;in a DATA statement?
                bvs   LD578           ;yes (DATAFLG = $49)

If you compare it to the code in the enhanced //e ROM, you'll notice a small change:

LD56D           jsr   $F78C

The code there handles reading from the input buffer and converting the characters when not in quoted text. (The SHLOAD function starts at $f775; the cassette routines were removed to make room for this and other changes.)

  • Just out of interest, how did you get the line change in the //e? By examining the ROM yourself, or did you find an online disassembly? Commented Nov 28, 2023 at 16:03
  • 1
    @Greenonline: I found the relevant part of the code in the classic Applesoft disassembly, booted up AppleWin in enhanced //e mode, and looked for a difference.
    – fadden
    Commented Nov 28, 2023 at 23:39

[Oioioi, this is one of the darkest corner of the II line :))]

How did the Apple IIe convert to upper case?

The Apple IIe did not convert at all. That's why the IIe Programmers Reference mentions:

... Applesoft BASIC, however, accepts commands and
program statements only in the upper-case format. The lowercase letters are reserved
for literal messages that are to be printed to the screen.

[...] If the CAPS LOCK key is locked in its down position, the letters of the
alphabet always appear in an upper-case form, whether you are holding down one of
the SHIFT keys or not. It is the typing mode that is most useful for entering BASIC
commands and programs, because those characters must be entered as upper-case

Similar does the latest (1987) edition on page 49.

At this point it might be worth to remember that the IIe's locking is a CAPS lock, not a SHIFT lock, meaning that only the letter keys get shifted, no other.

However the Apple IIc added in 1984 (beside other features) support to enter BASIC programs in lower case as well - by treating all characters outside of strings as upper case. A year later the Apple IIe Enhanced was created to match the IIc's modification. The Enhanced was the last modification(*1), as the Platinum did not change any ROM content (IIRC)

The difference between an original IIe and Enhanced is easy to spot when powering up, as the original IIe announced itself as "Apple ][", while the Enhanced says "Apple //e" (*2).

Then again the non enhanced IIe did also add limited support for all upper case entry as part of the 80 column firmware. When active (PR#3) and in 80 column mode (ESC-8). Entering ESC-R would lock an uppercase mode which will turn all entered characters into uppercase, except when a quotation mark is entered.

With the enhanced IIe 80 column card and mode got activated by default and the MONITOR (!) got changed to use this new input routine as if ESC-R was entered, what resulted in Applesoft behaving more friendly :) ESC-R in turn was removed.

Where is this [to upper] code?

It's part of the C100..CFFF ROM holding the 80 column card routines and only available when those are active, which makes usage rather complicated, as IO cards may page them out.

How is it done? Got an assembly listing?

For the latest (IIe Enhanced) ROM your best reference would of course be the 1987 Technical Reference listing 80col as well as Monitor ROM in Apendix J. Be careful when reading as address management is at work with the 80col ROM.

Is there a hook to this routine that can be called from BASIC?

Not really, as calling Cxxx routines depend on the exact machine state.

You see, lower case and the Apple II is a topic for sure beating any A20 adventure on a C:))

My best suggestion would be using a simple to-upper routine for strings. Here's a quickhack that should do the trick for all versions of Applesoft:

; Simple TO-UPPER function for Applesoft BASIC
; It should work with all Apple II models in all
; video modes as it only uses Applesoft functions
; and assumes only ASCII (compaible)
; Invocation via "CALL <adr>,<stringvar>"
; - adr is 768 if located at $0300, see ORG)
; - stringvar can be any Applesoft String Variable
;   it must be noted after the address, separated
;   by a colon, with no spaces inbetween.
;   The variable will be created if not existing
; After execution all lower case alpha characters
; are converted to upper case. This includes
; '{', '|', '}' to support some non-US 646
; (DE,FI,SE) variants.

;ZP-Locations used
STRADR   EQU   $06         ; String Address (Free ZP Loc)
VALTYP   EQU   $11         ; Type of Variable; $00=Num, $FF=Str
VARPTR   EQU   $83         ; Variable Value Pointer

;BASIC Functions used
CHKCOM   EQU   $DEBE       ; Scan for comma; SN-ERR if not next char
PTRGET   EQU   $DFE3       ; Locate a variable; SN-ERR if illegal name; Create if not exist
SYNERR   EQU   $DEC9       ; Syntax Error

         ORG   $0300       ; Or wherever it should go

         JSR   CHKCOM      ; Check and read past comma
         JSR   PTRGET      ; Look up Variable
         LDA   VALTYP      ; Numeric?
         BEQ   SNERR       ;  -> Error

         LDY   #$0
         LDA   (VARPTR),Y  ; Length of String
;         BEQ   DONE        ; Empty String? -> Done
         TAX               ; Save Length
         LDA   (VARPTR),Y  ; Low byte of string pointer
         STA   STRADR
         LDA   (VARPTR),Y  ; High byte of string pointer
         STA   STRADR+1 
         TXA               ; Length to Y for indexing
; Converting all characters starting from the last
         DEY               ; Index next char
         CPY   #$FF        ; Already past first char?
         BEQ   DONE
         LDA   (STRADR),Y  ; Get char
         CMP   #$61
         BCC   NEXT        ; Below 'a'
         CMP   #$7E        ; Last char to conv +1 ($7B if z should be last)
         AND   #$FF-$20    ; Clear lower case bit
         STA   (STRADR),Y  ; Store to string
         BNE   NEXT        ; Branchalways as Z is never set.

SNERR    JMP   $DEC9       ; Syntax Error

Greenonline was so kind to hack it in and test:

FWIW, the relocatable code is 50 bytes long

110 DATA 32,190,222,32,227,223,165,17,240
120 DATA 37,160,0,177,131,170,200,177,131
130 DATA 133,6,200,177,131,133,7,138,168
140 DATA 136,192,255,240,14,177,6,201,97
150 DATA 144,245,201,126,41,223,145,6,208
160 DATA 237,96,76,201,222

910 X$="hello"
920 PRINT X$
930 CALL 768,X$
940 PRINT X$

I've tested it by setting X$="hello" and it works..!

*1 - Wenn the IIgs did again change some monitor routines, but that's way beyond the IIe.

*2 - Also the machine identifier for any model after the II+ is $06 at $FBB3 and further distinguished at $FBEC as

  • $EA for an original IIe,
  • $E0 for an enhanced IIe, and
  • $00 for a IIc
  • Interesting bit of code, thanks. I'll have to find and download a good assembler to run on the emulator. I might come up with a BASIC loader (as in the example in my question), once I get the decimal values for the instructions. Commented Nov 26, 2023 at 12:49
  • FWIW, the relocatable code is 50 bytes long and (if you want to use a BASIC loader) the FOR loop is 110 FOR SPOT=768 TO 817:READ CODE:POKE SPOT,CODE:NEXT and the DATA statement for the decimal values is: 120 DATA 32,190,222,32,227,223,165,17,240,37,160,0,177,131,170,200,177,131,133,6,200,177,131,133,7,138,168,136,192,255,240,14,177,6,201,97,144,245,201,126,41,223,145,6,208,237,96,76,201,222. I've tested it by setting X$="hello" and it works..! Commented Nov 27, 2023 at 16:47
  • 1
    @Greenonline Thanks a lot. Making me quite proud about my apple memory still working :)) (also, any other (string) variable than X should work as well). I would add your loader t the answer if that's fine with you. (Another hint about BASIC: only two chararacters of a variable name are valid, so SPOT and SPIT are the same variable.
    – Raffzahn
    Commented Nov 27, 2023 at 16:50
  • 2
    Yes, feel free to add the BASIC loader, please. Commented Nov 27, 2023 at 17:23
  • 1
    I did try using EDASM as per Going Back to EDASM, the 1980 Apple II Editor/Assembler but the emulator refuses to go into 80 column mode & it was just a disaster (visually). So I used Virtual 6502 / Assembler instead to get the hex dump (comment out ORG). Then converted the hex to decimal using a decent hex-to-decimal convertor that could convert a whole hex line, rather than one at a time Commented Nov 27, 2023 at 18:03

All of those answers were serious TLDR, sorry.

XOR $#20

This is how you convert upper to lower and back again, presuming your character is in the accumulator (why bother calling $FDED?)

  • 1
    Thanks for the answer. You seem to be the first person who has mentioned $FDED, so what resides there and how is that relevant? Please edit and expand upon your answer a little. Commented Nov 28, 2023 at 13:00
  • 3
    @Greenonline $FDED is the address of COUT, the basic Monitor routine to output one character to the standard screen. Kind of the Apple II's Standard-Out. THe answer is rather useless as, beside missing the question, it's application may create quite unexpected results.For example the digit '3' will be flipped into $11, which, when outputted, would not print a character but switch the Apple IIe into 40 column mode.
    – Raffzahn
    Commented Nov 28, 2023 at 15:23
  • @Raffzahn - That's very interesting what you say about $11. I finally managed to get Virtual ][ into 80 column mode and then ran EDASM. However, when I paste your assembly code (from the browser) into EDASM, after pasting a few characters, the emulator suddenly flips into 40 column mode again, and prints the pasted characters in inverse - thereby creating the visual disaster, that I mentioned above. I guess that some control characters must be pasted. Commented Nov 28, 2023 at 15:36
  • @Greenonline strange but possible - I thought to have only used 7 bit clean writing.
    – Raffzahn
    Commented Nov 28, 2023 at 16:36
  • @Raffzahn - I've narrowed it down. It seems to be comments (;). I can paste a series of just the instructions (without the comments) fine, i.e. STRADR EQU $06, and so on. However, if I paste ;BASIC Functions used then it jumps into 40 col mode and inverts the characters on that pasted line. Probably a bug in the emulator, the way it handles pasting the clipboard from the MacBook. Oh well :-) Commented Nov 28, 2023 at 17:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .