Retour
ULM 3

 

ULM 3 is a tool, totaly written in machine language that add about 40 new functions to the BASIC.

ULM 3 est une collection de programme, entierement programmé en langage machine, qui ajoute un quarantaine de fonctions au basic.
La documentation est en anglais car je n'ai pas le temps d'en faire 2.

With few characters redefined, it could be used on an unexpanded PC-1350 (5 KB of RAM) but only few memory remains for the user and it isn't fully protected agains NEW, CLEAR or such ... In fact, it was designed to be secure with a memory card under MEM "C" memory mode.
WARNING : ULM3 is not movable (it can't be moved on memory without recompilation), and some functions needs a ROM 1. As I haven't ROM 0 machine, I can't port it to this ROM. As you have the full source code, it's up to you ... I think, this task is mainly to find out addresses of all function used in the UPPER ROM.
Anyway, some function like OLD can be used without ULM3.

This page was made using reverse engineering on the binary dump. All known important bugs were corrected. Anyway, the design of this code should be better and one day, I should write a better version ... but I have other thing to do ;-D

Download
ULM3 ULM2
  • ULM3.bin - Binary dump of ULM3, including some some redefined characters data (the same as POKE listing),
  • ULM3.s - Raw source code as generated by (should be useful to have the numeric value of labels, until labels definition files will be online)
  • ULM3.bas - Well, normally this BASIC program can configure ULM3. Unfortunately, I can't recover this progy from my tape. Until I made a recovery tool, this link is dead.
ULM2 is the previous version of this tool. Without documentation, without source code and without some ULM3 functions. It is there because it can be useful for machine without memory extension.
  • ULM2.bin - Binary dump of ULM2, including some some redefined characters data,
  • ULM2.bas - Configuration tool for ULM2, in BASIC.

The dispatcher

The root of ULM3 is a dispatcher called using the following syntax
CALL &68B5,command[;arg...][,command ...]

&68B5	IX		; skip ','
&68B6	IXL		; read the command
&68B7	CAL [CALL Rtn @dr <- X]	; store X
&68B9	CASE1 40 TESTS RETURN &6938
&68BD	CASE2
	'H'   [ULM3 Hexa]
	'M'   [ULM3 Music]
	'm'   [ULM3 Memorize screen]
	'r'   [ULM3 Restore screen]
	'T'   [ULM3 RAW keyboard test]
	'D'   [ULM3 defchar]
	'd'   [ULM3 defchar length]
	'C'   [ULM3 Clear]
	's'   [ULM3 Underline]
	'S'   [ULM3 Status]
	'I'   [ULM3 Imprime]
	'V'   [ULM3 VideoInverse]
	'P'   [ULM3 Internal Peek]
	'R'   [ULM3 set RESERVE]
	'@'   [ULM3 functions]
	'O'   [ULM3 Old]
	'c'   [ULM3 SetClear]
	'B'   [ULM3 Protect memory]
	'b'   [ULM3 Unprotect memory]
	'G'   [ULM3 Get]
	'v'   [ULM3 VideoInverse length]
	'A'   [ULM3 var address]
	'*'   [ULM3 Char repeat]
	'F'   [ULM3 Eval]
	'k'   [ULM3 install keyboard driver]
	'K'   [ULM3 force keyboard driver]
	'J'   [ULM3 Enable japanese]
	'j'   [ULM3 Disable japanese]
	&CF   [ULM3 CURSOR]
	'U'   [ULM3 Unit]
	&A1   [ULM3 AND]	***
	&A2   [ULM3 OR]		***
	&E0   [ULM3 GOSUB]
	&D5   [ULM3 FOR]
	'p'   [ULM3 STRPOS]
	&EC   [ULM3 BASIC]
	'w'   [ULM3 Undeline length]
	&C4   [ULM3 BEEP]
	&CE   [Clear screen buffer]
	'$'   [ULM3 Switch bank]
	DEFAULT [ERROR 1]
&6938	CAL [X <- CALL Rtn @dr] ; restore X
&693A	JRCP &6943	; Error ?
&693C	IXL		; check if the next char is ','
&693D	DX
&693E	CPIA &2C
&6940	JRZM &68B5
&6942	RC
&6943	LIA &58		; Restore the stack pointer
&6945	STR
&6946	RTN
Some explanations :

The dispatcher by itself is basically a CASE1 / CASE2 jump table. Each new function, which is a simple ASCII code, is associated with a machine language function.
As usual, the X register point to the following character to read, but, as this register should be modified by the called function, this pointer in backup in [CALL Rtn @dr] : so a call to [CALL Rtn @dr <- X] is needed after reading something on the command line.
Like 'normal' basic command, the CARRY is set if an error occur. To avoid heavy cleaning in each function code, the stack pointer is reset to $58.

Commodity functions

Here after, some functions used by many others.
A "..." is meaning the function continue in the next one.

[Last Result<-Xreg]
Store Xreg in [Last Result]

&6BAF	LIDP [Last Result]
&6BB2	LP $10  (Xreg)
&6BB3	LII &07
&6BB5	EXWD
...
[Xreg<-Last Result]
Recall Xreg stored in [Last Result]

&6BB6	LIDP [Last Result]
&6BB9	LP $10  (Xreg)
&6BBA	LII &07
&6BBC	MVWD
&6BBD	RTN
[U3 find variable]
Glue code to call [find variable]

&6BBE	LP $33		; We are looking for every kind of variable
&6BBF	ANIM &9F
&6BC1	ORIM &02
&6BC3	CAL [find variable]
&6BC5	CAL [CALL Rtn @dr <- X]
&6BC7	JPC [exit ULM3]
&6BCA	RTN
[U3 Store {B,A} in variable]

Store a 16 bits value in a variable.
  • {B,A} hold the 16 bits value
  • X point to the beginning of the variable name

&6B68	DX	; to compensate DX in beginning of [U3 Store Xreg in variable, X++]
...
[U3 Store {B,A} in variable, X++]

Idem but X point to the separator before the variable name

&6B69	LIQ $02  (A)
&6B6B	LP $18  (Yreg)
&6B6C	MVB	;{$19,$18} <- {B,A}]
&6B6D	CAL [BCD<-BIN unsigned]
...
[U3 Store Xreg in variable, X++]

Same as the next function but X point to the separator before the variable name

&6B6F	IX
...
[U3 Store Xreg in variable]

&6B70	CALL [Last Result<-Xreg]	; save the value to store
&6B73	CALL [U3 find variable]		; find the target variable
&6B76	CALL [Xreg<-Last Result]	; restore the value
&6B79	CAL [Tst string]		; Is it a string ?
&6B7B	JRNCP &6B8A
{storing a number}
&6B7D	LP $33		; check if the variable is numeric
&6B7E	TSIM &04
&6B80	JPNZ [ERROR 9]
&6B83	IY		; DP = address of the variable
&6B84	LP $10  (Xreg)
&6B85	LII &07
&6B87	EXWD		; Store the number
&6B88	RC
&6B89	RTN
{storing a string}
&6B8A 	LP $33		; check if the variable is not numeric
&6B8B	TSIM &04
&6B8D	JPZ [ERROR 9]
&6B90	TSIM &90	; check if it's a fixed variable
&6B92	JRZP &6B99
&6B94	LIA &F5
&6B96	IYS
&6B97	LIA &07
&6B99	DECA		; push the length of the variable
&6B9A	PUSH
&6B9B	LIQ $15		; X <- address of the string
&6B9D	LP $04  (Xl)
&6B9E	MVB
&6B9F	DX
&6BA0	LP $17		; B <- length of the string
&6BA1	LDM
&6BA2	EXAB
&6BA3	NOPT		; ??? Bug fix ???
&6BA4	IXL		; Read the string
&6BA5	DECB		; check if the string is not finished
&6BA6	JRNCP &6BAA
&6BA8	LEAVE		; if yes, exiting the loop
&6BA9	CLA
&6BAA	IYS		; store the value in the variable
&6BAB	LOOP &6BA4
&6BAD	RC
&6BAE	RTN

Following functions read a binary number which is stored in {$19,$18}
[U3 arg string position, X++]

0 -> 79

&6B18	IX
...
[U3 arg string position]

0 -> 79

&6B19	LIA &01
&6B1B	JRP &6B38
[U3 arg string position not null, X++]

1 -> 79

&6B1D	IX
...
[U3 arg string position not null]

1 -> 79

&6B1E	LIA &02
&6B20	JRP &6B38
[U3 arg BIN unsigned, X++]

0 -> 65535
&0000 -> &FFFF

&6B22	IX
...
[U3 arg BIN unsigned]

0 -> 65535
&0000 -> &FFFF

&6B23	LIA &03
&6B25	JRP &6B38
[U3 arg BYTE, X++]

0 -> 255

&6B27	IX
...
[U3 arg BYTE]

0 -> 255

&6B28	LIA &04
&6B2A	JRP &6B38
[U3 arg BYTE not null, X++]

1 -> 255

&6B2C	IX
...
[U3 arg BYTE not null]

1 -> 255

&6B2D	LIA &05
&6B2F	JRP &6B38
[U3 arg line number, X++]

1 -> 65279

&6B31	IX
...
[U3 arg line number]

1 -> 65279

&6B32	LIA &06
&6B34	JRP &6B38
[U3 arg BIN signed, X++]

??? -> ???

&6B36	IX
...
[U3 arg BIN signed]

??? -> ???

&6B37	CLA
&6B38	PUSH
&6B39	CAL [Int read argument]
&6B3B	CAL [CALL Rtn @dr <- X]
&6B3D	POP
&6B3E	JRCP &6BC7
&6B40	CASE1 6 TESTS RETURN &6BC7
&6B44	CASE2
	&01   [String Position<-BCD]
	&02   [String Position<-BCD not null]
	&03   [BIN unsigned<-BCD]
	&04   [Byte<-BCD]
	&05   [Byte<-BCD not null]
	&06   [Line Number<-BCD]
	DEFAULT [BIN signed<-BCD]
[U3 arg string, X++]

&6BCB	IX
...
[U3 arg string]

  • X point to the beginning of the string,
  • Y point to the end of the string.

&6BCC	CAL [Int read argument]
&6BCE	CAL [CALL Rtn @dr <- X]
&6BD0	JRCP &6BE6	; should be a JPC [ERROR 9]
&6BD2	CAL [Tst string]
&6BD4	JRCP &6BE7
&6BD6	LIQ $15		; X=beginning of the string
&6BD8	LP $04  (Xl)
&6BD9	MVB
&6BDA	DX
&6BDB	CAL [Y <- X]
&6BDD	LP $17		; Add the length -> Y
&6BDE	LDM
&6BDF	LIB &00
&6BE1	LP $06  (Yl)
&6BE2	ADB
&6BE3	CLA
&6BE4	IYS		; Clear the last character
&6BE5	RC
&6BE6	RTN
&6BE7	JP [ERROR 9]

Hexa

[U3 Hexa]

&6A85	CALL [U3 arg BIN unsigned]	;Read the value
&6A88	CAL [Xreg <- 0]	;build Xreg
&6A8A	LIB &6D	; data are stored on the string buffer
&6A8C	LIA &60
&6A8E	LIQ $02	;(A)
&6A90	LP $15
&6A91	MVB
&6A92	CAL [Y <- BA-1]
&6A94	LP $14
&6A95	ORIM &D0	;it's a string
&6A97	LP $17
&6A98	ORIM &04	; 4 bytes long
&6A9A	LP $19
&6A9B	LDM
&6A9C	SWP
&6A9D	CALL [Hexa DIGIT]
&6AA0	LDM
&6AA1	CALL [Hexa DIGIT]
&6AA4	LP $18  (Yreg)
&6AA5	LDM
&6AA6	SWP
&6AA7	CALL [Hexa DIGIT]
&6AAA	LDM
&6AAB	CALL [Hexa DIGIT]
&6AAE	JP [U3 Store Xref in variable, X++]
CALL &68B5,Hnumber;variable$

This function converts a 16 bits unsigned integer to an HEXADECIMAL string.
CALL &68B5,H255;K$ => K$=FF

PORTABILITY : ROM 1 only
[Hexa DIGIT] is a function in the external ROM. It's converting the lower 4 bits of A and store them at (Y) as Hexa digit. It's part of OPEN$ function.

[Hexa DIGIT]

&F163	ANIA &0F
&F165	CPIA &0A
&F167	JRCP &F16B
&F169	ADIA &07
&F16B	ADIA &30
&F16D	IYS
&F16E	RTN

Character repeat

[ULM3 Char repeat]

&675C	CALL [U3 arg BYTE]	; the char to repeat
&675F	LP $18  (Yreg)
&6760	LDM			;Fill the string buffer
&6761	LIDP &6E60
&6764	LII &4F
&6766	FILD
&6767	CALL [U3 arg string position not null, X++]	; length of the string
&676A	CAL [Xreg <- 0]		; build the result
&676C	LP $18  (Yreg)
&676D	LDM
&676E	LP $17
&676F	EXAM
&6770	LIB &6E
&6772	LIA &60
&6774	LIQ $02  (A)
&6776	LP $15
&6777	MVB
&6778	LP $14
&6779	ORIM &D0
&677B	JP [U3 Store Xreg in variable, X++]
CALL &68B5,*code;number;variable$

Repeat number times the character of code code and store this string in variable$.
CALL &68B5,*$41;5;K$ => K$=AAAAA

PORTABILITY : Portable

find a character in a string

CALL &68B5,pcode;exp$;var

find out in string2$ the character whose ASCII code is number (first form) or the first character of string1$.
The result is the position of this character in string2$ or 0 if it can't be found.

PORTABILITY : Portable
[ULM3 STRPOS]

&6680	CAL [Int read argument]
&6682	CAL [CALL Rtn @dr <- X]
&6684	JRCP &66A6	; error ?
&6686	CAL [Tst string]
&6688	JRCP &6697
&668A	LIQ $15	; it's a string ...
&668C	LP $04  (Xl)
&668D	MVB
&668E	DX
&668F	IXL	; read the 1st char
&6690	CPIA &FE	; shifted char ?
&6692	JRZM &668F
&6694	CAL [X <- CALL Rtn @dr]
&6696	JRP &669E
&6698	CAL [Byte<-BCD]	; read the ASCII code
&669A	JRCP &66A7
&669C	LP $18  (Yreg)
&669D	LDM
&669E	PUSH	; save the char
&669F	CALL [U3 arg string, X++]	; now we read the string
&66A2	POP	; restore the char
&66A3	JRNCP &66A8
&66A5	CAL [X <- CALL Rtn @dr]
&66A7	RTN
...
...
&66A8	EXAB
&66A9	LP $17	; reading the length of the string
&66AA	LDM
&66AB	DECA
&66AC	JRCP &66C5	; string empty
&66AE	PUSH
&66AF	LII &00
&66B1	INCI
&66B2	IXL
&66B3	CPIA &FE	; shifted char ?
&66B5	JRZM &66B2	;BUG : should be JRZP &66BE
&66B7	LP $03  (B)
&66B8	CPMA
&66B9	JRNZP &66BE	; exiting : char found
&66BB	LEAVE
&66BC	LIJ &00
&66BE	LOOP &66B1
&66C0	LP $01  (J)
&66C1	CPIM &01	; char found ?
&66C3	JRCP &66C7
&66C5	LII &00	; No ... result is 0
&66C7	LIJ &01
&66C9	LP $00  (I)
&66CA	LDM
&66CB	LIB &00
&66CD	CAL [X <- CALL Rtn @dr]
&66CF	JP [U3 Store {B,A} in variable, X++]

Printing on CE-126P

[ULM3 Imprime]
{print a line}
&6BEA	CALL [Test printer on]
&6BED	CAL [Wait 6 ms]
&6BEF	LIA &17			; 24 characters to print
&6BF1	PUSH
&6BF2	IXL
&6BF3	CPIA &FE		; it's a SHIFT : skipped
&6BF5	JRNZP &6BF8
&6BF7	IXL
&6BF8	CPIA &00		; end of this line ?
&6BFA	JRZP &6C0D
&6BFC	CALL [Print {A}]
&6BFF	LOOP &6BF2
&6C01	LIA &0D			; Print end of line
&6C03	CALL [Print {A}]
&6C06	LIA &0E
&6C08	CALL [Print {A}]
&6C0B	RC
&6C0C	RTN
&6C0D	CLA				; Fill until 24 char with space
&6C0E	CALL [Print {A}]
&6C11	LOOP &6C0D
&6C13	CALL &6C01
&6C16	SC
&6C17	RTN
[ULM3 Imprime]
&6C18	CALL [U3 arg string]	; Read the string to print
&6C1B	JRCP &6C2F
&6C1D	LIA &03			; The maximum length is 3x24 characters
&6C1F	PUSH
&6C20	CALL {print a line}
&6C23	JRCP &6C2B		; finished ?
&6C25 	IXL
&6C26	DX
&6C27	CPIA &00		; it's finished ?
&6C29	JRNZP &6C2C
&6C2B 	LEAVE
&6C2C	LOOP &6C20
&6C2E   RC
&6C2F   RTN
CALL &68B5,Istring

Print on CE-126P a characters string without any modification, unlink LPRINT basic function where some characters are converted.

PORTABILITY : ROM 1 Only (call some printing function of the upper ROM).

Music

[U3 Music]

&6AF6	CALL [U3 arg BIN unsigned]
&6AF9	LIQ $18  (Yreg)	;store parameter
&6AFB	LP $02  (A)
&6AFC	MVB
&6AFD	PUSH	;push length of this note
&6AFE	LIA &21	;Sound ON
&6B00	CAL [Out C <- {A}]
&6B02	EXAB
&6B03	PUSH	;push the 'frequency'
&6B04	EXAB
&6B05	LOOP &6B05
&6B07	CAL [Display ON]	;Sound OFF
&6B09	EXAB
&6B0A	PUSH	;push the 'frequency'
&6B0B	EXAB
&6B0C	LOOP &6B0C
&6B0E	LOOP &6AFE
&6B10	IXL	;another note
&6B11	CPIA ';'	
&6B13	JRZM [ULM3 Music]
&6B15	DX
&6B16	RC
&6B17	RTN
CALL &68B5,Mfrequency*256 + length;...

Beep with difference frequencies
CALL &68B5,M25*256+15;150*256+30

PORTABILITY : Portable

Memorize screen

CALL &68B5,maddress
CALL &68B5,raddress

Store the current screen in memory starting at address. 600 Bytes are needed to store screen data.

PORTABILITY : Portable
[U3 Memorize screen]

&69A2	CALL [U3 arg BIN unsigned]	;where to store the screen
&69A5	LIQ $18  (Yreg)
&69A7	LP $04  (Xl)
&69A8	MVB
&69A9	DX
&69AA	LIA &03
&69AC	PUSH
&69AD	POP
&69AE	PUSH
&69AF	CAL [Y<-@dr line video]
&69B1	LIA &04
&69B3	PUSH
&69B4	LIA &1D
&69B6	PUSH
&69B7	IY
&69B8	LDD
&69B9	IX
&69BA	STD
&69BB	LOOP &69B7
&69BD	LIA &E2
&69BF	LIB &01
&69C1	LP $06  (Yl)
&69C2	ADB
&69C3	LOOP &69B4
&69C5	LOOP &69AD
&69C7	RC
&69C8	RTN
[U3 Restore screen]

&6980	CALL [U3 arg BIN unsigned]	;where the screen is stored
&6983	LIQ $18  (Yreg)
&6985	LP $04  (Xl)
&6986	MVB
&6987	DX
&6988	LIA &03
&698A	PUSH
&698B	POP
&698C	PUSH
&698D	CAL [Y<-@dr line video]
&698F	LIA &04
&6991	PUSH
&6992	LIB &1E
&6994	CAL [{IXL - IYS}*B]
&6996	LIA &E2
&6998	LIB &01
&699A	LP $06  (Yl)
&699B	ADB
&699C	LOOP &6992
&699E	LOOP &698B
&69A0	RC
&69A1	RTN

Memory protection

[ULM3 Protect memory]

&6ABD	CAL [LIDP Mem flags]
&6ABF	ORID &01
&6AC1	RTN
[ULM3 Unprotect memory]

&6ABD	CAL [LIDP Mem flags]
&6ABF	ANID &FE
&6AC1	RTN
CALL &68B5,B
CALL &68B5,b

Protect (B) or Unprotect (b) the memory.
If the memory is protected, some function like NEW, CLEAR, LOAD, CLOAD, ... fails. It's mainly use to avoid user mistake or with commercial memory modules.

PORTABILITY : Portable

ANDing or ORing memory

[ULM3 OR]
&681A   LIA &47         ;ORMA
&681C   JRP &6820
...
[ULM3 AND]
&681E   LIA &46         ;ANMA
&6820   LIDP &683B      ; auto-modify the code
&6823   STD             ; to set the operation to make
&6824   CALL [U3 arg BIN unsigned]
&6827   LIQ $18  (Yreg) ; read the address
&6829   LP $04  (Xl)    ; and store it in X
&682A   MVB
&682B   DX
&682C   CAL [push X]    ; push the address to modify
&682E   CAL [X <- CALL Rtn @dr]
&6830   CALL [U3 arg BYTE, X++]
&6833   IXL             ; read the next character
&6834   LP $10  (Xreg)
&6835   EXAM
&6836   CAL [pop X]
&6838   IXL
&6839   LP $18  (Yreg)
&683A   EXAM
&683B   ORMA
&683C   MVDM
&683D   LP $10  (Xreg)
&683E   LDM
&683F   CPIA &3B        ; another value ?
&6841   JRZM &682C
&6843   RC
&6844   RTN
CALL &68B5, AND @dr;val1[;val2...]
CALL &68B5, OR @dr;val1[;val2...]

These commands are doing massive AND or OR actions in memory. For example,
CALL &68B5, AND &6C30;1;2;3
is exactly the same as
POKE &6C30, PEEK &6C30 AND 1, PEEK &6C31 AND 2, PEEK &6C32 AND 3

CLEAR

CALL &68B5,C[;var]

Like CLEAR basic command but keep ULM3 memory allocated.
If the memory is protected nothing is cleared.
The optional var is set with the start address of ULM3.

PORTABILITY : ROM 1 only due to call of &BCE1.
[ULM3 Clear]

&6706	CAL [Protected memory ?]	; if the memory is protected
&6708	JRNZP &6712			; we'll not CLEAR variables
&670A	CALL [{B,A} <- U3 Stored Start Variable pointer]
&670D	CAL [LIDP Mem flags]
&670F	CALL &BCE1			; Call end of CLEAR basic code
&6712	IXL
&6713	CPIA &3B			; ';' ?
&6715	JRNZP &67E6
&6717	LIAB [Start ULM3]
&671B	JP [U3 Store {B,A} in variable]
[{B,A} <- U3 Stored Start Variable pointer]

&64C5	LIDP [U3 Stored Start Variable pointer]
&64C8	LP $02  (A)
&64C9	MVBD
&64CA	RTN
ROM 1 - end of CLEAR code

&BCE1	TSID &10
&BCE3	JRZP &BCE9
&BCE5	LIA &00
&BCE7	LIB &60
&BCE9	LIDP [Start Variable pointer]
&BCEC	LP $02  (A)
&BCED	EXBD
&BCEE	LIDL &13
&BCF0	ANID &36
&BCF2	RC
&BCF3	RTN

Set Clear

[ULM3 SetClear]

&6724	CAL [Protected memory ?]
&6726	JPNZ [ERROR 1]
&6729	CALL [U3 arg BIN unsigned]
&672C	LP $18  (Yreg)
&672D	LIDP [Start Variable pointer]
&6730	EXBD
&6731	RTN
CALL &68B5,caddress

Force the value of [Start Variable pointer]. Notez-bien : nothing but the memory protection is checked, so playing with this function without care is the better way to trash the memory and loose data.

PORTABILITY : Portable and Relogeable
SetClear can be used also as standalone because it doesn't need other ULM3 functions.

Old

[ULM3 OLD]

&6A70	CAL [X <- Start BASIC]
&6A72	IX
&6A73	ANID &00
&6A75	IX
&6A76	IXL
&6A77	LIB &00
&6A79	LP $04  (Xl)
&6A7A	ADB
&6A7B	IXL
&6A7C	CPIA &FF
&6A7E	JRNZM &6A75
&6A80	CAL &14CB
&6A82	LP $04  (Xl)
&6A83	EXBD
&6A84	RTN
CALL &68B5,O

Old will recover a program erased by a NEW command, by a reset or if the Start Basic pointer has been modified.

PORTABILITY : Portable and Relogeable
Old can be used also as standalone because it doesn't need other ULM3 functions.

Internal PEEK

[ULM3 Internal Peek]

&6B59	CALL [U3 arg BYTE]	; read the register address
&6B5C	LP $18  (Yreg)
&6B5D	LDM
&6B5E	CPIA &60		; check if it's in internal memory range
&6B60	JPNC [ERROR 3]
&6B63	STP
&6B64	LDM
&6B65	LIB &00			; {B,A} <- (p)
&6B67	IX			; to compensate DX in beginning of [U3 Store {B,A} in variable]
... followed by [U3 Store {B,A} in variable] code ...
CALL &68B5,Paddress;var

Store in var the content of a register.

PORTABILITY : Portable
Obviously, many registers are modified by this function, so to use with care.

Extended functions

[ULM3 functions]

&6A34	CALL [U3 arg string position]	; Read the function number
&6A37	LP $18  (Yreg)
&6A38	LDM
&6A39	CASE1 16 TESTS RETURN &6BCA	; call function
&6A3D	CASE2
	&01   &6BCA
	&02   &6BCA
	&03   &6BCA
	&04   &6BCA
	&05   &6BCA
	&06   &6BCA
	&07   &6BCA
	&08   &6BCA
	&09   &6BCA
	&0A   &6BCA
	&0B   &6BCA
	&0C   &6BCA
	&0D   &6BCA
	&0E   &6BCA
	&0F   &6BCA
	&10   &6BCA
	DEFAULT [ERROR 3]
CALL &68B5,@num[;...]

@ is used to add new function to ULM3 for example to extend its functionality.
By default, it call &6BCA which is only a RTN

PORTABILITY : Portable (but called function should not be).

Set RESERVE

[UML3 set RESERVE]

&6845	CALL [U3 arg string position]
&6848	LP $18  (Yreg)
&6849	LDM
&684A 	CASE1 9 TESTS RETURN &689C
&684E	CASE2
	&01	&686C
	&02	&6871
	&03	&6876
	&04	&687B
	&05	&6880
	&06	&6885
	&07	&688A
	&08	&688F
	&09	&6894
DEFAULT [ERROR 3]
&686C	LIB &6F
&686E	LIA &6E
&6870	RTN
&6871	LIB &6F
&6873	LIA &82
&6875	RTN
&6876	LIB &6E
&6878	LIA &6F
&687A	RTN
...
...
&687B	LIB &6F
&687D	LIA &6E
&687F	RTN
&6880	LIB &6E
&6882	LIA &6F
&6884	RTN
&6885	LIB &6F
&6887	LIA &6E
&6889	RTN
&688A	LIB &6F
&688C	LIA &6E
&688E	RTN
&688F	LIB &6F
&6891	LIA &6E
&6893	RTN
&6894	CALL [U3 arg BIN unsigned, X++]
&6897	LIQ $18  (Yreg)
&6899	LP $02  (A)
&689A	MVB
&689B	RTN
&689C	JRCP &68A3
&689E	LIDP [Reserve pointer]
&68A1	LP $02  (A)
&68A2	EXBD
&68A3	RTN
CALL &68B5,Rnum
CALL &68B5,R9;address

Switch between different RESERVE data. Up to 8 different reserve bank informations can be stored into ULM3, but using R9, you can specify your own RESERVE bank.

PORTABILITY : Portable

STATUS

[ULM3 Status]

&69C9	CALL [U3 arg string position]
&69CC	LP $18  (Yreg)
&69CD	LDM
&69CE	CASE1 10 TESTS RETURN &6A19
&69D2	CASE2
	&00	&6A15
	&01	[LIDP Start BASIC pointer]
	&02	[LIDP End BASIC pointer]
	&03	&6A11
	&04	&6A0D
	&05	&6A09
	&06	&6A05
	&07	&6A01
	&08	&69FD
	&09	&69F3
	DEFAULT [ERROR 3]
&69F3	CALL [U3 arg BIN unsigned, X++]
&69F6	LIQ $18  (Yreg)
&69F8	LP $06  (Yl)
&69F9	MVB
&69FA	DY
&69FB	IY
&69FC	RTN
...

...
&69FD	LIDP [Reserve pointer]
&6A00	RTN
&6A01	LIDP [Start Last MERGEd pointer]
&6A04	RTN
&6A05	LIDP [Error @ddr]
&6A08	RTN
&6A09	LIDP [Break @ddr]
&6A0C	RTN
&6A0D	LIDP [CALL Return @ddr]
&6A10	RTN
&6A11	LIDP [READ pointer]
&6A14	RTN
&6A15	LIDP [Start Variable pointer]
&6A18	RTN
&6A19	LP $02  (A)
&6A1A	MVBD
&6A1B	JP [U3 Store {B,A} in variable, X++]
CALL &68B5,Snum;var
CALL &68B5,S9;address;var

STATUS, return the value of a system pointer.
There is the value for num:
0 Start Variable
1 Start BASIC
2 End BASIC
3 READ
4 CALL Return @ddr
5 Break @ddr
6 Error @ddr
7 Start Last MERGEd
8 Reserve
9 Specify the address of the pointer to see.

PORTABILITY : Portable

Variable address

[ULM3 var address]

&680F	CALL [U3 find variable]
&6812	IY
&6813	LIQ $06  (Yl)	; {B,A} <- Y
&6815	LP $02  (A)
&6816	MVB
&6817	JP [U3 Store {B,A} in variable, X++]
CALL &68B5,Avar1;var2

var2 = address of var1

PORTABILITY : Portable

Unit

[ULM3 Unit]

&6732	LIDP &787C
&6735	LIA &C1		;DEGREE
&6737	TSID &02
&6739	JRZP &673C
&673B	INCA		;RADIAN
&673C	TSID &04
&673E	JRZP &6742
&6740	LIA &C3		;GRAD
&6742	LIDP [OldKey]
&6745	STD
&6746	JP &6AD7	;end of GET to store the result
CALL &68B5,Uvar$

var$ = current trigonometric unit.

PORTABILITY : Portable

Get cursor position

[ULM3 CURSOR]

&67A1	LIDP [CSR Y position]
&67A4	LDD
&67A5	PUSH
&67A6	LIDL &8B	; [CSR X position]
&67A8	LDD
&67A9	LIB &00
&67AB	CALL [U3 Store {B,A} in variable]
&67AE	POP
&67AF	LIB &00
&67B1	JP [U3 Store {B,A} in variable, X++]
CALL &68B5,CURSOR Xpos;Ypos

Store in Xpos and Ypos the actual position of the text cursor.

PORTABILITY : Portable

Tokenize

[ULM3 BASIC]

&6947	CAL [Clear input buffer]
&6949	LIDP [String buffer]	; clear the string buffer
&694C	LIA &0D
&694E	LII &4F
&6950	FILD
&6951	CALL [U3 Read string if not in interactive mode]
&6954	JRCP &69A1
&6956	ORID &0D		; the string is Zero ended : now it's a &0d
&6958	CAL [Y <- Input Buffer]
&695A	CALL [Tockenize]
&695D	CAL [X <- CALL Rtn @dr]
&695F	JRCP &69A1
&6961	CAL [Xreg <- 0]	; create the result
&6963	LIB &6E
&6965	LIA &B0
&6967	LP $15
&6968	LIQ $02  (A)
&696A	MVB
&696B	LP $14
&696C	ORIM &D0
&696E	CAL [X <- BA-1]	; compute the length of the string
&6970	LIB &FF
&6972	IXL
&6973	INCB
&6974	CPIA &0D
&6976	JRNZM &6972
&6978	EXAB
&6979	LP $17
&697A	EXAM
&697B	CAL [X <- CALL Rtn @dr]
&697D	JP [U3 Store Xreg in variable, X++]
CALL &68B5,BASIC;string;var$

Tokenize a string. It's needed before using CALC or you will get a syntax error on Basic functions.

PORTABILITY : ROM 1 only because of call to [Tockenize]
[U3 Read string if not in interactive mode]

&68A4	CALL [U3 Fail if in Interactive mode]
&68A7	JP [U3 arg string]
[U3 Fail if in Interactive mode]

&68AA	LP $36		; check if we are in interactive mode
&68AB	TSIM &02
&68AD	JRZP &68B2
&68AF	CLA
&68B0	CAL [ERROR (accu)]
&68B2	JP &6BC7

Evaluating an expression

[ULM3 Eval]

&6749	CALL [U3 arg string]	; read the string
&674C	JRCP &67E7		; exit on error
&674E	CAL [Int read argument]
&6750	JRNCP &6757
&6752	CAL [Xreg <- 0]		;Clear the result on error
&6754	LP $17
&6755	ORIM &55
&6757	CAL [X <- CALL Rtn @dr]
&6759	JP [U3 Store Xreg in variable, X++]
CALL &68B5,Fstring;var

Evaluate an expression.
e.g : CALL &68B5,F"X^2";Y

PORTABILITY : Portable

WARNING : if the string contains BASIC function, it could be tokenized before calling Eval or you will got some syntaxe error
CALL &68B5,BASIC "SIN X";K$,FK$;Y
Obviously, if Eval is used in a loop, the tokenization should be done only once.

Exiting stacks

[ULM3 FOR]

&66D2	IXL
&66D3	CPIA 'A'
&66D5	JRZP &66E3	; exit all FOR ?
&66D7	LIDP [FOR stack pointer]
&66DA	LDD
&66DB	CPIA &18	; check if the stack is not empty
&66DD	JRCP &66E3
&66DF	SBIA &12
&66E1	STD
&66E2	RTN
&66E3	CAL [CALL Rtn @dr <- X]
&66E5	LIA &06		; reset the pointer
&66E7	LIDL &2B
&66E9	STD
&66EA	RC
&66EB	RTN
[ULM3 GOSUB]

&66EC	IXL
&66ED	CPIA 'A'
&66EF	JRZP &66FD	; exit all GOSUB ?
&66F1	LIDP [GOSUB stack pointer]
&66F4	LDD
&66F5	SBIA &02
&66F7	CPIA &90	; check if the stack is not empty
&66F9	JRCP &66FF
&66FB	STD
&66FC	RTN
&66FD	CAL [CALL Rtn @dr <- X]
&66FF	LIA &90
&6701	LIDL &2C
&6703	STD
&6704	RC
&6705	RTN
CALL &68B5,FOR [A]
CALL &68B5,GOSUB [A]

Unstack one FOR (or GOSUB) : it's usefull to exit easily from FOR/NEXT or GOSUB/RETURN. With A, we are existing from all pending FOR or GOSUB.

PORTABILITY : Portable

Video commodities

These functions are low level tools used by others video functions.

PORTABILITY : Portable

[U3 Inc text cusor]
  • M text cursor X
  • N text cursor Y

&6AB1	INCM	; Inc X pos
&6AB2	LP $0A  (M)
&6AB3	CPIM &18	; Next line ?
&6AB5	JRCP &6ABA
&6AB7	ANIM &00
&6AB9	INCN
&6ABA	RTN
[U3 Calc @dr video]
  • input : {N,M} = pos text cursor
  • output : Y = video @ddresse.

&67E8	LP $0B  (N)
&67E9	LDM
&67EA	CAL [Y<-@dr line video]
&67EC	LP $0A  (M)
&67ED	LDM
&67EE	JP &1CF9	; end of [Y<-video @dr of Working CSR]
[U3 Calc message beginning]
We only have the current cursor position. To got the beginning of the message, we need to decrease this position by the length of the string.
  • output: M text cursor X
  • output: N text cursor Y
  • output: I length of the message

&67F1	LIDP [CSR X position]
&67F4	LII &FF
&67F6	LP $0A  (M)
&67F7	MVBD
&67F8	LIB &6D	; Beginning of the string buffer
&67FA	LIA &5F
&67FC	CAL [X <- BA]
&67FE	IXL
&67FF	CPIA &00	; it's the end of this string ?
&6801	JRNZP &6804
&6803	RTN
&6804	INCI	; inc string length
&6805	DECM	; dec cursor position
&6806	JRNCM &67FE
&6808	DECN
&6809	LIA &17
&680B	LP $0A  (M)
&680C	EXAM
&680D	JRM &67FE

Video manipulations

CALL &68B5,V
Video inverse of the last displayed message
CALL &68B5,vnum
Video inverse starting from the actual CURSOR position and on num characters
CALL &68B5,s
Underline the last displayed message
CALL &68B5,wnum
Underline starting from the actual CURSOR position and on num characters

The last displayed message is the last message displayed by PRINT or PAUSE commands if the output is not redirected to the printer (PRINT = PRINT). Give unpredicted result with INPUT.

Notez bien : There is no clean way to access informations about the last displayed messages. So these functions use [U3 Calc message beginning] to retrieve those informations, but it's only a big dirty hack : so you MUST use them JUST AFTER the PRINT or the PAUSE command. Otherwise, the string buffer should be modified and the result is unpredicted.

PORTABILITY : Portable
[ULM3 Undeline length]

&677E	LIDP &67D9	; modify the code ...
&6781	ANID &80	; for setting the last digit
&6783	LIA &47		; ORMA
&6785	JRP &678E
[ULM3 VideoInverse length]

&6787	LIDP &67D9	; modify the code ...
&678A	ORID &FF	; for reversing the video data
&678C	LIA &45		; SBM
&678E	LIDL &DC
&6790	STD
&6791	CALL [U3 Fail if in Interactive mode]
&6794	CALL [U3 arg BYTE not null]
&6797	LP $18  (Yreg)	; read the length of the message
&6798	LDM
&6799	DECA
&679A	LIDP [CSR X position]	; read cursor position
&679D	LP $0A  (M)		; M=X, N=Y
&679E	MVBD
&679F	JRP &67CF
[ULM3 underline]

&67B4	LIDP &67D9	; modify the code ...
&67B7	ANID &80	; for setting the last digit
&67B9	LIA &47		; ORMA
&67BB	JRP &67C4
[ULM3 VideoInverse]

&67BD	LIDP &67D9	; modify the code ...
&67C0	ORID &FF	; for reversing the video data
&67C2	LIA &45		; SBM
&67C4	LIDL &DC
&67C6	STD
&67C7	CALL [U3 Fail if in Interactive mode]
&67CA	CALL [U3 Calc message beginning]
&67CD	LP $00  (I)	; Read the length of the string
&67CE	LDM
&67CF	PUSH
&67D0	CALL [U3 Calc @dr video]
&67D3	CAL [X <- Y]
&67D5	LIA &05	; size of a char
&67D7	PUSH
&67D8	LIB &80	;Auto-modified code
&67DA	IXL
&67DB	LP $03  (B)
&67DC	ORMA	;Auto-modified code
&67DD	EXAB
&67DE	STD
&67DF	LOOP &67D8
&67E1	CALL  [U3 Inc text cusor]
&67E4	LOOP &67D0
&67E6	RC
&67E7	RTN

Characters redefinition

CALL &68B5,D
Redefine characters of the last displayed message
CALL &68B5,d
Redefine characters on the the whole display

The last displayed message is the last message displayed by PRINT or PAUSE commands if the output is not redirected to the printer (PRINT = PRINT). Give unpredicted result with INPUT.

Notez bien : There is no clean way to access informations about the last displayed messages. So these functions use [U3 Calc message beginning] to retrieve those informations, but it's only a big dirty hack : so you MUST use them JUST AFTER the PRINT or the PAUSE command. Otherwise, the string buffer should be modified and the result is unpredicted.

PORTABILITY : Portable
[U3 Calc @dr buffer]
  • input : {N,M} = pos text cursor
  • output : Y = @dr. of the character in the screen buffer.

&6552	LP $0B  (N)
&6553	LDM
&6554	CAL [Y<-@dr line screen buffer]
&6556	LP $0A  (M)
&6557	LDM
&6558	LP $06  (Yl)
&6559	ADM
&655A	DY
&655B	RTN
[U3 {B;A}<-(Defchar bank pointer)]

&671E	LIDP [U3 Defchar bank pointer]
&6721	LP $02  (A)
&6722	MVBD
&6723	RTN
[ULM3 defchar length]

&64F4	LIB &00	; start position is {0,0}
&64F6	CLA
&64F7	LP $0A  (M)
&64F8	LIQ $02  (A)
&64FA	MVB
&64FB	LII &5F	; Redefining the whole display
&64FD	JRP &6505
[ULM3 defchar]

&64FF	CALL [U3 Calc message beginning]
&6502	CALL [U3 Fail if in Interactive mode]
&6505	LP $00  (I)	; Read the length of the string
&6506	LDM
&6507	PUSH
&6508	CALL [U3 Calc @dr buffer]
&650B	IY	; Read the character
&650C	LDD
&650D	LP $03  (B)
&650E	EXAB
&650F	LDM
&6510	ANIA &F0	; Check if it's a redefined char
&6512	CPIA &10
&6514 	JRNZP &651A
&6516	SBIM &10	; and calcul the char number
&6518	JRP &652C
&651A	CPIA &80
&651C	JRZP &6522
&651E	CPIA &90
&6520	JRNZP &6526
&6522	SBIM &70
&6524	JRP &652C
&6526	CPIA &E0
&6528	JRNZP &654B
&652A	SBIM &B0
&652C	LDM
&652D	PUSH
&652E	CALL [U3 Calc @dr video]
&6531	CALL [U3 {B;A}<-(Defchar bank pointer)]
&6534	CAL [X <- BA-1]
&6536	POP
&6537	LIB &00		; compute the beginning of data
&6539	RC		; offset = char number * 6
&653A	SL
&653B	JRNCP &653E
&653D	INCB
&653E	LP $04  (Xl)
&653F	ADB
&6540	RC
&6541	SL
&6542	JRNCP &6545
&6544	INCB
&6545	LP $04  (Xl)
&6546	ADB		; X = start back + offset
&6547	LIB &06	; a char is 6 bytes long
&6549	CAL [{IXL - IYS}*B]
&654B	CALL [U3 Inc text cusor]
&654E	LOOP &6508
&6550	RC
&6551	RTN

Switch characters bank

[ULM3 Switch bank]

&64CD	CALL [U3 arg string position]
&64D0	LP $18  (Yreg)
&64D1	LDM		; read the bank number
&64D2	CPIA &09
&64D4	JRZP &64EE
&64D6	JPNC [ERROR 3]
&64D9	SL		; offset of this pointer
&64DA	LIB &00
&64DC	CAL [X <- BA-1]
&64DE	LIAB [U3 Start defcar banks]
&64E2	LP $04  (Xl)
&64E3	ADB		; add the offset
&64E4	IX
&64E5	LP $02  (A)
&64E6	MVBD
&64E7	LP $02  (A)
&64E8	LIDP [U3 Defchar bank pointer]
&64EB	EXBD
&64EC	RC
&64ED	RTN
&64EE	CALL [U3 arg BIN unsigned, X++]
&64F1	LP $18  (Yreg)
&64F2	JRM &64E8
CALL &68B5,$num
CALL &68B5,$9;address

Set redefinition table number num as the active table
If num=9, you can specify the table address as the second argument.

PORTABILITY : Portable

RAW keyboard test

[ULM3 RAW keyboard test]

&6A1E	CALL [U3 arg BIN unsigned]
&6A21	LIQ $18  (Yreg)
&6A23	LP $02  (A)
&6A24	MVB
&6A25	LIP $5C (Port A)
&6A27	EXAM
&6A28	EXAB
&6A29	LIDP &7E00
&6A2C	STD
&6A2D	OUTA
&6A2E	INA
&6A2F	LIB &00
&6A31	JP [U3 Store {B,A} in variable, X++]
CALL &68B5,T{&7E00}*256 + {portA};var

Test if is some key are pressed, in RAW mode. The input value of portA is stored in var.

PORTABILITY : Portable

GET

[ULM3 GET]

&6AC7	CAL [LIDP indicators]
&6AC9	ANID &EF	; remove 'RUN' (see note 1)
&6ACB	LIA &23		; see note 2
&6ACD	CAL &1206	; [GET + 2], after LIA 03
&6ACF	CAL [LIDP indicators]
&6AD1	TSID &28	; set 'RUN' if 'PRO' and 'RSV' are unset
&6AD3	JRNZP &6AD7
&6AD5	ORID &10
&6AD7	CAL [Xreg <- 0]	; set the result
&6AD9	LIB &6F		;the character is in &6F57
&6ADB	LIA &57		; [OldKey]
&6ADD	LP $15
&6ADE	LIQ $02  (A)
&6AE0	MVB
&6AE1	LP $14		; it's a string ...
&6AE2	ORIM &D0
&6AE4	LP $17		; only 1 character long
&6AE5	ORIM &01
&6AE7	CAL [X <- CALL Rtn @dr]
&6AE9	JP [U3 Store Xreg in variable]
CALL &68B5,Gvar$

Like INKEY$ but wait until a key is pressed.

PORTABILITY : Portable

NOTE 1 : [GET] will crash the system if a program is launched by [DEF]+key. To avoid this, the 'RUN' indicator is unset while we are waiting for an input.

NOTE 2 : This LIA will product a 'beep' at each key pressed. See BEEP command

Japanese mode

[ULM3 Enable japanese]

&6AF1	CAL [LIDP &6F16]
&6AF3	ORID &80
&6AF5	RTN
[ULM3 Disable japanese]

&6AEC	CAL [LIDP &6F16]
&6AEE	ANID &7F
&6AF0	RTN
CALL &68B5,J
CALL &68B5,j

Enable (J) or disable (j) Japanese mode.

PORTABILITY : Portable

BEEP

[ULM3 BEEP]

&655C	IXL		; read the argument
&655D	CAL [CALL Rtn @dr <- X]
&655F	EXAB
&6560 	LIA &03
&6562	LP $03  (B)
&6563	CPIM &DA	; STOP
&6565	JRZP &656E
&6567	CPIM &D3	; ON
&6569	JPNZ [ERROR 1]
&656C	ORIA &20	; we need a beep
&656E	LIDP &65AE	; in the keyboard driver
&6571	STD
&6572	LIDL &8B	; in the keyboard driver
&6574	STD
&6575	LIDP &663B	; in the keyboard driver
&6578	STD
&6579	LIDP &6ACC	; in GET
&657C	STD
&657D	RC
&657E	RTN
CALL &68B5,BEEP ON CALL &68B5,BEEP STOP

Activate or stop beep on each key hit.
Works only if the keyboard driver is installed and on GET.
Notez-bien : if the keyboard driver is installed, a beep is ALWAYS emitted if the input buffer is full.

PORTABILITY : Portable

Install the keyboard driver

[ULM3 install keyboard driver]

&658F	LDR	; save the stack pointer
&6590	EXAB
&6591	LIA &5C	; set [ULM3 Keyboard driver]
&6593	STR	; as keyboard interpretation function
&6594	LIA &65
&6596	PUSH
&6597	LIA &9D
&6599	PUSH
&659A	EXAB	; restore the stack pointer
&659B	STR
&659C	RTN
[ULM3 force keyboard driver]

&657F	LIA &5C
&6581	STR
&6582	CAL &0901	; make some cleaning
&6584	JRP [ULM3 Keyboard driver]
CALL &68B5,k
Smoothly install the keyboard driver. It's totally transparent whatever the current status of the machine is.
CALL &68B5,K
Force the install of the keyboard driver. The current CPU stack is cleared and the machine is reset to an interactive use in RUN MODE.
Caution :

PORTABILITY : ROM 1 only (in fact these functions by themselve are portable but it's not the case of the driver)

Keyboard driver

[ULM3 Keyboard driver]

&659D	CAL [LIDP indicators]
&659F	ANID &EF	; unset RUN
&65A1	LIDP &6EFF	; check if the input buffer is full
&65A4	LDD
&65A5	CPIA &0D
&65A7	JRZP &65AD
&65A9	LIA &33
&65AB	JRP &65AF
&65AD	LIA &23		; warning : auto-modified by BEEP
&65AF	CAL &1206	; [GET]+2
&65B1	CAL [LIDP indicators]
&65B3	TSID &28
&65B5	JRNZP &65B9
&65B7	ORID &10	; reactivate RUN
&65B9	TSID &03	; SHIFT or DEF
&65BB	JRNZP &65F7
&65BD	EXAB
&65BE	LP $03  (B)
&65BF	LDM
&65C0	CASE1 16 TESTS RETURN [ULM3 Keyboard driver]
&65C4	CASE2		; normal key dispatcher (from &8021)
	&02   &E301	; [CLS]
	&03   &0901	; [CA]
	&04   &E6AA	; [Up]
	&05   &D84C	; [Down]
	&06   &AEC0	; [P=NP]
	&07   &D15B	; [BRK]
	&08   &AE25	; [MODE]
	&0A   &AED7	; [OFF]
	&0B   &E801	; [INS]
	&0C   &E8A0	; [DEL]
	&0D   &B250	; [ENTER]
	&0E   &E490	; [left]
	&0F   &E4E6	; [right]
	&13   &E5B4	; [Shift left]
	&14   &E5A7	; [Shift right]
	&16   &AED5	; [Lock]
DEFAULT &AA48
;
; processing SHIFT or DEF
;
&65F7	TSID &01	; SHIFT
&65F9	JRZP &6603
&65FB	CPIA &08	; [MODE] : switch to reserve mode
&65FD	JRZP &6601
&65FF	ANID &FC	; turn off SHIFT and DEF
&6601	JRM &65BD
&6603	ANID &FC	; turn off SHIFT and DEF
&6605	EXAB
&6606	LIA &65		; the return address is the driver
&6608	PUSH
&6609	LIA &9D
&660B	PUSH
&660C	EXAB
&660D	SBIA &30
&660F	JRCP &6618
&6611	CPIA &09
&6613	JPC &684A	; jump to [ULM3 set RESERVE]
&6616 	JRZP &662E
&6618	ADIA &30
&661A	CPIA &0A	;OFF ?
&661C	JRNZP &6623
&661E	CAL [Save system memory]
&6620	JP [Auto power OFF]
...
...
;
; processing DEF
;
&6623	PUSH
&6624	CALL &D512
&6627	POP
&6628	ORIA &80
&662A	EXAB
&662B	JP &AC51
;
; entering a character by its code
;
&662E	CALL [U3 internal bug fix get]
&6631	NOPW
&6632	CALL [U3 conv kbd digit]
&6635	JRCM &662E	; unwanted character
&6637	SWP
&6638	LP $0F
&6639	EXAM
&663A	LIA &23
&663C	CAL &1206
&663E	CALL [U3 conv kbd digit]
&6641	JRCM &663A	; unwanted character
&6643	LP $0F
&6644	ORMA
&6645	LDM
&6646	JP &AA66	; add a new character
[U3 internal bug fix get]
&6586	CAL [LIDP indicators]
&6588	ANID &EF
&658A	LIA &23
&658C	JP &1206
[U3 conv kbd digit]
&6649	SBIA &28	; convert the char to an offset
&664B	JRCP &6659
&664D	CPIA &12
&664F	JRCP &665B
&6651	SBIA &07
&6653	JRCP &6659
&6655	CPIA &18
&6657	JRCP &665B
&6659	SC
&665A	RTN
&665B	LIB &00		; read the number associated w/ this key
&665D	CAL [X <- BA-1]
&665F	LIAB [U3 conversion table]
&6663	LP $04  (Xl)
&6664	ADB
&6665	IXL
&6666	RC
&6667	RTN
[U3 conversion table]
&6668	DC &0A, &0A, &0C, &0C, &0D, &0D, &0F, &0B
&6670	DC &00, &01, &02, &03, &04, &05, &06, &07
&6678	DC &08, &09, &0A, &0B, &0C, &0D, &0E, &0F

This driver add following new feature to the keyboard :

PORTABILITY : ROM 1 only, many calls to upper ROM