post

EhBASIC bug Ibuffs location

Bug in EhBASIC 2.22 Daryl Rictor et al (from a discusson on 6502.org)

The following works:

; Ibuffs can now be anywhere in RAM, ensure that the max length is < $80
IRQ_vec   = VEC_SV+2      ; IRQ code vector
Ibuffs      = IRQ_vec+$14

Whereas the following doesn’t:

; Ibuffs can now be anywhere in RAM, ensure that the max length is < $80
Ibuffs      = $0400
Ibuffe      = Ibuffs + $47    ; end of input buffer

Of course be careful to make sure that Ram_base is quite faraway from Ibuffs.

So, Ibuffs can be anywhere in RAM that’s not $0400??
Any reason $0400 is magical?
Nope. $0400 is an example. $0500 gives you the same result: negative.
As long as Ibuffs is in the same page as ccflag and friends, it works. And ccflag can be at $0400.

Now, if you reference Ibuffs in relation to IRQ_vec (copy it over from min_mon.asm), then things work.
Perhaps someone somewhere made some assumptions of the page where Ibuffs need to be e.g. be in the same page as ccflag and friends etc.

The other problem is what I’ve already observed: the EhBASIC binary has to be located in memory higher than Ram_base and Ram_top. Otherwise strange things happen.

With Ibuffs set to $0400 AND Ram_base set to $0500, I did a Cold start and entered this line – 10 PRINT “HELLO WORLD”

I examined the memory at Ram_base and found that the stored line was formatted correctly. However, when I typed LIST, all I got back was HELLO WORLD, as if I had typed RUN. Typing RUN also returned HELLO WORLD. When I ran the SYS command (my added command to return me to my Monitor), it just ignored it and returned to the BASIC input prompt.

Next, I changed Ibuffs to $0401 and reassembled. This time, everything performed as it should.

Next, I set Ram_base to $0600 with Ibuffs set to $0400 and entered the same line. This time, LIST returned nothing. Examining memory at $0600, the stored line is there and is correct. RUN and SYS also return nothing.

Finally, I set Ram_base to $0600 and Ibuffs to $0401 and all worked as it should.
I think I found the problem… and it would explain the issue with Ibuffs and Ram_base being 1 page apart too.

This piece of code is the problem:

LAB_142A
   INY            ; increment pointer
   INY            ; increment pointer (makes it next line pointer high byte)
   STA   Ibuffs,Y      ; save [EOL] (marks [EOT] in immediate mode)
   INY            ; adjust for line copy
   INY            ; adjust for line copy
   INY            ; adjust for line copy
   DEC   Bpntrl      ; allow for increment (change if buffer starts at $xxFF)
   RTS

Previously in the code, Bpntrl gets set to Ibuffs. When the DEC instruction gets executed, Bpntrl get set to $FF. Later in the code, we have this:

LAB_15F6
   JSR   LAB_IGBY      ; increment and scan memory

LAB_15F9
   JSR   LAB_15FF   

The routine LAB_IGBY is located in zero page (to increase speed). it looks like this:

LAB_2CEE
   INC   Bpntrl      ; increment BASIC execute pointer low byte
   BNE   LAB_2CF4      ; branch if no carry
               ; else
   INC   Bpntrh      ; increment BASIC execute pointer high byte

; page 0 initialisation table from $C2
; scan memory

LAB_2CF4
   LDA   $FFFF         ; get byte to scan (addr set by call routine)
   CMP   #TK_ELSE      ; compare with the token for ELSE
   BEQ   LAB_2D05      ; exit if ELSE, not numeric, carry set

   CMP   #':'         ; compare with ":"
   BCS   LAB_2D05      ; exit if >= ":", not numeric, carry set

   CMP   #' '         ; compare with " "
   BEQ   LAB_2CEE      ; if " " go do next

   SEC            ; set carry for SBC
   SBC   #'0'         ; subtract "0"
   SEC            ; set carry for SBC
   SBC   #$D0         ; subtract -"0"
               ; clear carry if byte = "0"-"9"
LAB_2D05
   RTS

The code at LAB_2CEE will INC Bpntrl, which becomes $00 and then INC’s Bpntrh to $05. Now this pointer is pointing to the wrong page. And depending upon what is stored there, different things will happen.

This note is incorrect:

   DEC   Bpntrl      ; allow for increment (change if buffer starts at $xxFF)

It should read (change if buffer starts at $xx00).

My fix would be to add this:

LAB_142A
   INY            ; increment pointer
   INY            ; increment pointer (makes it next line pointer high byte)
   STA   Ibuffs,Y      ; save [EOL] (marks [EOT] in immediate mode)
   INY            ; adjust for line copy
   INY            ; adjust for line copy
   INY            ; adjust for line copy

; add patch for when Ibuffs is $xx00 - Daryl Rictor 
   LDA  Bpntrl         ; test for $00
   BNE  LAB_142P      ; not $00
   DEC   Bpntrh      ; allow for increment when $xx00
LAB_142P
; end of patch
   DEC   Bpntrl      ; allow for increment
   RTS

I have tested this fix and it worked for me.

Daryl

post

Enhanced 740 BASIC Language reference

Enhanced BASIC 740, language reference.

Most of EhBASIC74 is identical in operation to 6502 EhBASIC so this page will just describe the keywords that differ from that version.
Code that doesn’t use the features of one version should run identically on the other.

BASIC Keywords

Here is a list of BASIC keywords that differ from the 6502 version of EhBASIC. They
are only valid when entered in upper case as shown and spaces may not be included
in them. So, for example, TIMER1 is valid but TIMER 1 is not.

A2D CLEAR CNTR0 CNTR1 COUNT D2A1
D2A2 EVENT FLUSH INT0 INT1 INT2
INT3 INT4 IRQ NMI ON OFF
PRES12 PRESX PRESY PULSE PWM RETIRQ
RETNMI RETURN START STOP TIMER TIMER1
TIMER2 TIMERX TIMERY +

Anything in upper case is part of the command/function structure and must be present
Anything in lower case enclosed in < > is to be supplied by the user
Anything enclosed in [ ] is optional
Anything enclosed in { } and separated by | characters are multi choice options
Any items followed by an ellipsis, … , may be repeated any number of times
Any punctuation and symbols, except those above, are part of the structure and must be included

var is a valid variable name
var$ is a valid string variable name
var() is a valid array name
var$() is a valid string array name
expression is any expression returning a result
expression$ is any expression returning a string
result
addr is an unsigned integer in the range +/- 16777215 that will be
wrapped to the range 0 to 65535
b is a byte value 0 to 255
n is an integer in the range 0 to 63999
w is an integer in the range -32768 to 32767
i is a +ve integer value
r is real number
+r is a +ve value real number (0 is considered +ve)
$ is a string literal

BASIC74 Commands

FLUSH

This command, specific to version 1.10 on, clears the input buffer of any pending keystrokes. The buffer is also cleared by CLEAR and RUN.

INTx {ON|OFF|CLEAR}

Changes INTx event handling (where x is one of 0, 1, 2, 3 or 4).
ON
Enables the INTx handling subroutine after it has been turned off.
Note If a INTx event occured while the INTx event handling was disabled then it will be actioned immediately after this command.
OFF
Disables the INTx handling subroutine but does not remove the assignment. INTx events that occur while the handler is disabled will still be flagged for action.

CLEAR
Cleares the INTx handling assignment. After an INTx CLEAR the handling subroutine can only be restarted with an ON INTx command.


INTx {+|-}

Sets the detection edge for INTx (where x is one of 0, 1, 2, 3 or 4). + sets a low to high transition (positive edge) as the trigger, – sets a high to low (negative edge) as the trigger. The default is -. Note that changing the trigger edge when the interrupt is enabled may cause the interrupt to trigger.


CNTRx {ON|OFF|CLEAR}

Changes CNTRx event handling (where x is either 0 or 1).
ON

Enables the CNTRx handling subroutine after it has been turned off.
Note If a CNTRx event occured while the CNTRx event handling was disabled then it will be actioned immediately after this command.

OFF

Disables the CNTRx handling subroutine but does not remove the assignment. INTx events that occur while the handler is disabled will still be flagged for action.

CLEAR

Cleares the CNTRx handling assignment. After a CNTRx CLEAR the handling subroutine can only be restarted with an ON CNTRx command.


CNTRx {+|-}

Sets the detection edge for CNTRx (where x is one of 0 or 1). + sets a low to high transition (positive edge) as the trigger, – sets a high to low (negative edge) as the trigger. The default is -. Note that changing the trigger edge when the interrupt is enabled may cause the interrupt to trigger.


PRESq <b>

Sets the value for prescaller q (where q is one of 12, X or Y).


TIMERp {TIMER|PWM|COUNT|PULSE}

Sets TIMERp timing mode (where p is either X or Y).
TIMER

The timer (X or Y) counts down (f/16)/(1+PRESq)/(1+TIMERp).

PWM

If the corresponding active edge selection bit (CNTR0 for timer X, CNTR1 for timer Y) is “0” (CNTRx -) then the counter counts as in timer mode while the pin is at “1”.
If the corresponding active edge selection bit (CNTR0 for timer X, CNTR1 for timer Y) is “1” (CNTRx +) then the counter counts as in timer mode while the pin is at “0”.
The Prescaller and timer latches are preset to $FF by setting this mode and when the selected event (-ve or +ve edge) happens the prescaller and timer values are copied and available to BASIC using the TIMERp (where p is either X or Y) function.

COUNT

The same as timer mode except pulses on the corresponding counter pin (CNTR0 for timer X, CNTR1 for timer Y) are counted. Selecting this mode also sets the corresponding pin to input mode.

PULSE

As timer mode except when the timer reaches $0000 the counter pin (CNTR0 for timer X, CNTR1 for timer Y) is inverted. Selecting this mode also sets the corresponding pin to output mode.


TIMERp {START|STOP}

Enables or disables counting for TIMERp (where p is either X or Y).


TIMERq {ON|OFF|CLEAR}

Changes TIMERq event handling (where q is one of 1, 2, X or Y).
ON

Enables the TIMERq handling subroutine after it has been turned off.
Note If a TIMERq event occured while the TIMERq event handling was disabled then it will be actioned immediately after this command.

OFF

Disables the TIMERq handling subroutine but does not remove the assignment. TIMERq events that occur while the handler is disabled will still be flagged for action.

CLEAR

Cleares the TIMERq handling assignment. After a TIMERq CLEAR the handling subroutine can only be restarted with an ON TIMERq command.

TIMERq <b>

Sets the value of TIMERq (where q is one of 1, 2, X or Y).


RETIRQ

This command has been removed from EhBASIC74. It’s function is now handled by an enhanced RETURN command.


RETNMI

This command has been removed from EhBASIC74. It’s function is now handled by an enhanced RETURN command.


RETURN

As well as returning program execution to the next statement after the last GOSUB in EhBASIC74 RETURN also restores execution to the next statement after event
subroutine has been executed. RETURN checks the type of event and automatically restores the enabled flag for the interrupt that caused the event.


ON

See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.


OFF

See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.


CLEAR

See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.


ON {IRQ|NMI} <n>

These commands have been removed from EhBASIC74 as there is no IRQ or NMI interrupt on the 740 series microprocessor.


ON INTx <n>

Set up the INTx routine pointers (where x is one of 0, 1, 2, 3 or 4). This sets up the effective GOSUB line that is taken when an interrupt happens. When the effective GOSUB is taken the interrupt, INTx, is turned off. The interrupt is turned back on when normal program flow is resumed by a matched RETURN command.


ON CNTRx <n>

Set up the CNTRx routine pointers (where x is one of 0 or 1). This sets up the effective GOSUB line that is taken when a counter interrupt happens. When the
effective GOSUB is taken the interrupt, CNTRx, is turned off. The interrupt is turned back on when normal program flow is resumed by a matched
RETURN command.


ON TIMERq <n>

Set up the TIMERq routine pointers (where q is one of 1, 2, X or Y). This sets up the effective GOSUB line that is taken when a timer interrupt happens. When the
effective GOSUB is taken the interrupt, TIMERq, is turned off. The interrupt is turned back on when normal program flow is resumed by a RETURN
command.


IRQ {ON|OFF|CLEAR}

This command has been removed from EhBASIC74 as there is no IRQ interrupt on the 740 series microprocessor.


NMI {ON|OFF|CLEAR}

This command has been removed from EhBASIC74 as there is no NMI interrupt on the 740 series microprocessor.


D2Ax <b>

Output byte b as an analog voltage on D-A port x (where x is 1 or 2). Setting either convertor also sets the corresponding pin to analog output mode.

D2Ax {ON|OFF}

Enables or disables the D2Ax output (where x is one of 0 or 1).


+

See INTx {+|-} or CNTRx
{+|-}.


See INTx {+|-} or CNTRx
{+|-}.

BASIC74 Functions

Functions always return a value, be it numeric or string, so are used on the right hand side of the = sign or in commands requiring an
expression e.g. after PRINT, within expressions, or in other functions.


A2D(<b>)

Returns the value of the A-D convertor input b, where b is 0 to 7. Any value for b outside this range will cause a function call error.


EVENT

Returns a boolean value. If any enabled interrupt has occured since EVENT was last examined then EVENT will return -1 else EVENT will return 0


TIMERp

Returns the count value from TIMERp (where p is either X or Y). As this is a count down timer the value returned is first subtracted from zero then returned as if it had been DEEKed from the timer registers. This function is only of any use when TIMERp is being used in PWM mode.

post

Enhanced 740 BASIC

Enhanced BASIC74 is a BASIC interpreter for the 740 microprocessor family. It is constructed to be quick and powerful and includes instructions to facilitate easy low level handling of 740 family hardware devices. It also retains most of the powerful high level instructions from similar BASICs.

EhBASIC74 is derived from EhBASIC for the 6502 and shares much of the code used in that BASIC. However EhBASIC74 also includes enhancements to make it specifically more usefull on the Mitsubishi 740 series of single chip micros. While it was developed on the M38067 it should be possible to run it on most of the 740 family with only minor modifications.

EhBASIC74 is free but not copyright free. For non commercial use there is only one restriction, any derivative work should include, in any binary image distributed, the string “Derived from EhBASIC” and in any distribution that includes human readable files file that includes the above string in a human readable form e.g. not as a comment in an HTML file.

  • Language reference
  • Code examples

19th June 2003.
Confusingly the main version number is now the 3806 specific version number so, while the generic BASIC version is still 1.09, this incarnation is 1.10.This adds an interrupt driven receive buffer and an extra command to help use it. Go see the language reference for details.

18th June 2003.
This is the last version for the SuprDupr and SuprChip boards that will use polled input. The next version will have an interrupt driven Rx buffer for better response. As such version 1.09 will remain available for download here.

AT keyboard interface driver

The SuprDupr has a PS2 style socket for an AT keyboard. However, as it arrived, the data and clock go to the GAL and so were jumped directly to port 7, bits 7 and 6, on the micro. There are also positions for pullup resistors on both the keyboard data and clock lines, these are not needed and were omitted. These details may have changed as this was an early production board.
The software is unfinished and provided as a starting point for anyone who wishes to make their own. As provided the driver provides very reliable basic keyboard handling. During developement it was noticed that the keyboard driver would sometimes hang so some test code was added to monitor the reliability (included in the source but commented out) and it was found that the routine wouldn’t return about once in every 750,000 calls.

This was eventually traced to a faulty keyboard but highlighted the vunerability of the code to spurious responses from the keyboard. As a result the robustness of the code was improved by adding timeouts and the resulting code proved reliable over hundreds of millions of calls.

Download the source here

LCD port driver

The SuprDupr has a port wired to enable use of many inteligent LC displays. The display, pictured right, is an LC7980 type though most of the other graphics and
text types are similarly driven.
One change needed to be made to the SuprDupr to use this display and that was to disconnect the earthy end of the contrast pot from 0v and connect it to the -ve
charge pump output on the MAX232 chip. This is because the greater multiplex on these graphics displays requires a more -ve bias to maintain the viewing angle.

A down side to this is that display contrast is effected by the loading on the RS232 port and use of such things as a RS232 line monitor can have an adverse
effect on the display. Also if the load from the display is to great then the MAX232 charge pump will not start. However this should not be a problem in normal use.
Another change made to the SuprDupr was to wire +12v to the spare pin on the LCD port to provide power for the backlight. This could be connected through a PNP
transistor and controlled by software via one of the port pins.

The software is unfinished and provided as a starting point for anyone who wishes to make their own. It is usable and was used to produce the displays pictured above and below.

Download the source here


8 Bit ISA slot

I wanted a quick way to connect an 8 bit ISA slot, only to access the IO adresse range, to the SuprChip V microcontroller. The circuit I came up with appears on the right.

The address lines A5 to A15 can either be set using jumpers or switches (I used switches) or, if you only need one address range, can be hard wired to the corresponding address.

Any of the five interrupt lines, from IRQ3 to IRQ7, can be selected by the use of a jumper. Again if you will only need one (or none) of these then the desired interrupt can be hard wired.

This circuit could be fairly easily adapted as a memory mapped device by connecting the corresponding address and data lines (possibly through buffers), by driving the IOR and IOW pins with decoded read and write signals, and by connecting the interrupt, via an invertor, to the NMI input on the processor. The RESET line can be driven, again via an invertor, by the processor RESET line or by an output port line. This line is active high.

While experimenting with different network cards I discovered some of them need the +12v supply in addition to the +5v supply. This additional connection is shown in red on the diagram.

As I had a stack of 3Com 3C509B NICs of various types I decided to try to use them. Well it turns out that even with PnP disabled you still need to access the card’s id port on $01xx. To allow this Port 6 bit 6 was connected to the A9 line, so both $03xx and $01xx can be accessed by the software. This additional connection is shown in green on the diagram.

Though the slot can be accessed entirely from EhBASIC it was decided to create two short routines to make accessing the slot faster and easier. To that end one function, to read a byte, and one subroutine, to write a byte, were written.

After initialising the function and subroutine addresses thus ..

..
30 DOKE $44,$EF20 : REM USR(n) = read from IO address $xxnn	
31 rr = $EF00 : REM call address for write to IO address
..

It is a simple matter to write a byte by doing ..

20500 CALL rr,cr,dd : REM set control register			
20505 REM cr is the IO address - $00 to $1F
20510 REM dd is the byte value for that IO address

.. and read a byte by doing ..

10050 by = USR(cr)						
10055 REM cr is the IO address - $00 to $1F
10060 REM the byte value is returned to by

You can download the assembly source for the read and write routines.

post

EhBASIC LOAD and SAVE notes

The quickest and easiest way to save the program is to do this …

Type LIST but not the return ..
Set your terminal program to ASCII capture..
Hit return to start the LIST command..
Once the list is complete stop the ASCII capture.

To load the program back there are two possible, similar, ways.

If your hardware supports hardware handshake properly just select the listing and use ASCII send to transfer it (You’ll need to edit the ‘Ready’ prompt from the end of the listing).

If your hardware doesn’t support hardware handshaking then you’ll need to paste a number of SPACE characters at the start of each line to give the interpreter enough time to tokenise the previous line. The number of spaces depends on two things, how fast your processor is and how fast the serial link is. The faster the processor the fewer spaces, the faster the serial link the more spaces. Otherwise just do as above.

(Alternately edit the NULL command to insert $20’s instead of $00’s into the start of each line and do ..
NULL n
LIST
NULL 0
when you save the list)

If you want to save the program as binary you should save (Smeml) to (Svarl)-1.

If you want to save the program as ASCII you should redirect the character output vector to write to your filesystem and then call LIST. Doing this can also allow you to specify line numbers or ranges as with LIST. The output vector should be restored and the file closed when LIST returns or an error is encountered.

To load a binary program start loading it at (Smeml) and set (Svarl) to the last address + 1 then call LAB_1477 to clear the variables and reset the execution pointer. An easy way to do the first part is by copying (Smeml) to (Svarl) and using (Svarl) as a post incremented save pointer. If there is a chance that the program has been relocated it’s probably a good idea to rebuild the line pointer chain.

To load an ASCII program redirect the character input vector to read from your filesystem and return to the main interpreter loop. The input vector should be restored and the file closed when the file end is reached or an error is encountered.

I prefer ASCII format as it’s far more portable, easier to manipulate and can include direct commands and comment lines that aren’t saved to memory.

Is there an easy way to support a filename in the LOAD and save COMMANDs, and get access to that from my code? Right now I am prompting the user for it.

Call the evaluate following expression routine, LAB_EVEZ, and look for a string by testing if the data type flag, Dtypef, is negative. If it is the string descriptor is on the descriptor stack so don’t forget to pop it off there by calling LAB_22B6 once you’re done with it.

Look at Jeff Trantor’s implemention of LOAD and SAVE for the Apple 1 with a CFFA1 and for the OSI C1P via the serial line.

Daryl Rictor wrote a simple SAVE and LOAD program:

;******** LOAD & SAVE PATCH FOR ENHANCED BASIC ON 65C02 Simulator

psave		
		jsr	pscan
		ldy	#$00
		lda	itempl
		sta	(itempl),y
		iny
		lda	itemph
		sta	(itempl),y
		ldx	smeml
		lda	smemh
		jsr	print2byte
		jsr	print_cr
		sec
		lda	itempl
		sbc	smeml
		tax
		lda	itemph
		sbc	smemh
		jsr	print2byte
		jsr	print_cr
		rts

pload		
		jsr	pscan
		lda	itempl
		sta	svarl
		sta	sarryl
		sta	earryl
		lda	itemph
		sta	svarh
		sta	sarryh
		sta	earryh
		JMP   LAB_1319		
pscan
		lda	smeml
      	sta	itempl
      	lda	smemh
      	sta	itemph
pscan1	ldy   #$00
		lda   (itempl),y
		bne   pscan2
		iny   
		lda   (itempl),y
		bne   pscan2
		clc
		lda   #$02
		adc   itempl
		sta	itempl
		lda	#$00
		adc	itemph
		sta	itemph
		rts
pscan2	ldy   #$00
		lda	(itempl),y
		tax
		iny
		lda	(itempl),y
		sta	itemph
		stx	itempl
		bra	pscan1

Another version of this:

;******** LOAD & SAVE PATCH FOR ENHANCED BASIC ON SBC-3
; Daryl Rictor, Feb 2009
;

.include dos.lbl			; get DiskOS pointers
;input		; sbc scanned input
;output		; text output
;DiskOS		; diskos entry
;SaveF		; save program file from BASIC
;LoadF		; load program file from BASIC

;ibuffs+1 ($420) contains the name, start addr, end address

psave		ldx	#$00
		lda	ibuffs+1
		stz	ibuffs+1	; this fixes syntax error in ehbasic
		bne	psave15
		rts
psave1		lda	ibuffs+1,x
		stz	ibuffs+1,x	; this fixes syntax error in ehbasic
		beq	psave3
psave15		cmp	#$b9
		bne	psave2
		lda	#"/"
psave2		sta	buffer+2,x
		inx
		cpx	#$46
		bne	psave1
		rts			; entry too long, abort
psave3		lda	#","
		sta	buffer+2,x
		inx
		phx
		jsr	pscan		; Find end of program
		plx
		lda	smemh
		jsr	ToHex
		lda	smeml
		jsr	ToHex
		lda	#","
		sta	buffer+2,x
		inx
		lda	itemph
		jsr	ToHex
		lda	itempl
		jsr	ToHex
		lda	#$00
		sta	buffer+2,x
		jsr	SaveF		; save file to IDE (in DiskOS)
		rts

pload		ldx	#$00
		lda	ibuffs+1
		stz	ibuffs+1	; this fixes syntax error in ehbasic
		bne	pload15
		rts
pload1		lda	ibuffs+1,x
		beq	pload3
pload15		cmp	#$b9
		bne	pload2
		lda	#"/"
pload2		sta	buffer+2,x
		inx
		cpx	#$47
		bne	pload1
		rts			; entry too long, abort
pload3		lda	#$00
		sta	buffer+2,x
		jsr	Loadf		; load file from IDE (in DiskOS)
		jsr	pscan
		lda	itempl
		sta	svarl
		sta	sarryl
		sta	earryl
		lda	itemph
		sta	svarh
		sta	sarryh
		sta	earryh
		JMP   LAB_1319		

pscan
		lda	smeml
	      	sta	itempl
      		lda	smemh
	      	sta	itemph
pscan1		ldy   #$00
		lda   (itempl),y
		bne   pscan2
		iny   
		lda   (itempl),y
		bne   pscan2
		clc
		lda   #$02
		adc   itempl
		sta	itempl
		lda	#$00
		adc	itemph
		sta	itemph
		rts
pscan2		ldy   #$00
		lda	(itempl),y
		tax
		iny
		lda	(itempl),y
		sta	itemph
		stx	itempl
		bra	pscan1

SysJMP		JSR	Main_Loop	; in DiskOS
		rts

ToHex		PHA                     ;  prints AA hex digits
		LSR                     ;  MOVE UPPER NIBBLE TO LOWER
		LSR                     ;
		LSR                     ;
		LSR                     ;
		JSR   SaveDig           ;
		PLA                     ;
SaveDig		AND   #$0F              ;
		CMP   #$0A              ;
		BCC   SaveDig1          ;
		ADC   #$66              ;
SaveDig1	EOR   #$30              ;
		sta   buffer+2,x        ;
		inx
		rts

And another approach:

There seems to be quite a lot of mixed information about how to deal with string arguments in LOAD and SAVE. I’d like to present the way I came up with:
At the beginning of either LOAD and SAVE, i call a subroutine that gets the string argument using LAB_EVEX and opens the file:

openfile:
                jsr LAB_EVEX
                lda Dtypef
                bne @go
                ; not a string, trigger syntax error
                ldx #$02
                jsr LAB_XERR
@go:
                ldy #$00
@l:
                lda (ssptr_l),y
                beq @open
                cmp #'"'
                beq @term
                iny
                bne @l
@term:
                lda #$00
                sta (ssptr_l),y
@open:
                lda ssptr_l
                ldx ssptr_h
                jsr krn_open
                bne     io_error
                rts
io_error:
                pha
                jsr     krn_primm
                .asciiz "io error: "
                pla
                jmp krn_hexout

As it seems, after calling LAB_EVEX, ssptr points to the string given as parameter including the closing “, which I overwrite with $00 to be compatible with my open routine, which expects the address of a null terminated string.
In another thread, it was suggested to pop the string from the descriptor stack using LAB_22B6, which in my case resulted in ut1_ph/l not pointing to the string, but anywhere in the area of $exxx, where my os resides. So I went for the above approach. It works, but it feels rather dirty due to the overwriting of the closing “. At this point, I am open for suggestions.

post

IDE bus interface circuit

IDE bus interface circuit

Hardware

IDE Bus interface circuit.

The connector on the top left of the diagram is from my own 6502 boards (see the sbc project) and is as it is for two reasons. It’s easy
to wire on a stripboard layout and I have a lot of 26 way ribbon, headers and plugs. All the signals are directly from the 6502 except /SEL0 and /SEL1 which are used to select the block $F1xx with /SEL0 = 1 and /SEL1 = 0.

A 40 pin IDC header, re-cycled from a PC motherboard, is used to connect to the IDE bus.

All the capacitors are low ESR electrolytics and are placed one near each chip. If you don’t have this type to hand you can use standard electrolytics with some low value
ceramic capacitor, say 0.1uF, in parallel.

The GAL16V8A is used to generate the read and write strobes for the buffers and latches and to generate the IOR and IOW strobes for the IDE bus. All are active low. The interface uses 32 bytes in the address range as it is a 16 bit port with 16 addresses. For anyone interested the equations for this chip are in ide_01.pld and can be compiled with WinCUPL. The fuse file, ide_01.jed and the compiler listing, ide_01.txt are also included.

IDE ATA Interface address map. High byte at address+1
Address Read Write Width
$00 Data 16 Bit
$02 Error Precomp 8 Bit
$04 Sector count 8 Bit
$06 LBA bits 0 to 8 / CHS Sector number 8 Bit
$08 LBA bits 8 to 15 / CHS Cylinder low 8 Bit
$0A LBA bits 16 to 23 / CHS Cylinder high 8 Bit
$0C Drive select & LBA bits 24 to 27 / CHS Head select 8 Bit
$0E Status Command 8 Bit
$1C Alternate status Device control 8 Bit
$1E Drive address Not used 8 Bit

For ATAPI devices some of the register names and functions change. Here is the ATAPI map.

IDE ATAPI Interface address map. High byte at
address+1
Address Read Write Width
$00 Data 16 Bit
$02 Error Feature 8 Bit
$04 Interrupt reason Not used 8 Bit
$06 Not used
$08 Byte count low 8 Bit
$0A Byte count high 8 Bit
$0C Device select 8 Bit
$0E Status Command 8 Bit
$1C Alternate status Device control 8 Bit
$1E Not used

All reads and writes to the IDE bus are sixteen bits wide. On write, first the lower eight bits are written to the even address (A0 = 0) and latched by one 74LS574, Then the upper eight bits are written to the corresponding odd address (A0 = 1) and are presented to the upper eight bits of the IDE bus by the 74LS245, a bi-directional buffer used here in the one direction only. These eight bits, along with the previously latched lower
eight bits, make up the sixteen bits needed for a write operation.
On read the lower eight bits are read from the IDE bus even address (A0 = 0) via the second 74LS245 and, as this read takes place the upper, eight bits from the IDE bus are latched by the second 74LS574. The upper eight bits can then be read from the 74LS574 latch.

The interface is arranged thus so that ..

  • All 16 bits are used, this allows us to utilise the full capacity of the drive.
  • Reads and writes are symmetrical, i.e. both are done low byte and then high byte.
  • The status registers on ATA devices are all on the low byte, fast polling is possible because only the low byte needs to be read.

The disadvantage is that every write must be sixteen bits, even if the top eight bits
aren’t used.

Other features are ..

  • An activity LED. Illuminates during ATA device activity. If required this could be wired off board.
  • A reset jumper. This ties the IDE bus reset to the CPU reset so both are reset together.
  • An IRQ jumper. This is to allow an interrupt based driver to be developed.

The finished board.

Close-up views.

Software.
At present only a BASIC demonstration program is available. It can be seen here.
Download this file

10 REM set disk interface values
20 REM enhanced BASIC version
30 REM supports ATA but not ATAPI devices
100 AD=$F100 : REM interface base address
110 IO=AD+$00 : REM input/output register - 16 bits
120 ER=AD+$02 : REM error/precomp register
130 SC=AD+$04 : REM sector count
140 SS=AD+$06 : REM LBA bits 0 to 7
150 CL=AD+$08 : REM LBA bits 8 to 15
160 CH=AD+$0A : REM LBA bits 26 to 23
170 HR=AD+$0C : REM drive/LBA bits 24 to 27
180 SR=AD+$0E : REM status/command
190 RR=AD+$0C+$10 : REM alternate status/device control
200 AS=AD+$0E+$10 : REM drive address
210 MD=%10100000 : REM these bits must always be set
220 BK=%01000000 : REM value to set LBA mode
1000 REM set parameters to defaults
1005 DOKE RR,6 : REM write device control
1010 DOKE SC,1 : REM sector count
1020 DOKE SS,0 : REM LBA bits 0 to 7
1030 DOKE CL,0 : REM LBA bits 8 to 15
1040 DOKE CH,0 : REM LBA bits 26 to 23
1050 DOKE HR,MD+BK : REM drive/LBA upper 4 bits
1055 DOKE RR,2 : REM write device control
1060 GOSUB 1300
1065 PRINT : GOSUB 3100
1070 PRINT
1080 PRINT "0  Select drive"
1090 PRINT "1  Reset device"
1100 PRINT "2  Identify device"
1110 PRINT "3  Sleep device"
1120 PRINT "4  Idle device"
1130 PRINT "5  Standby device"
1140 PRINT "6  Query power mode"
1150 PRINT "7  Execute diagnostics"
1160 PRINT "8  Recalibrate"
1170 PRINT "9  Test read"
1175 PRINT "10 Write BASIC""
1180 PRINT
1190 PRINT "Choose one";
1200 INPUT I
1210 I=I+1
1220 IF (I<1) OR (I>11) THEN 1200
1230 PRINT
1240 ON I GOSUB 3000,1300,6000,2200,2300,2400,2500,2600,2900,2700,20000
1250 GOTO 1065
1300 REM reset device(s)
1310 DOKE HR,(MD+BK) EOR 16 : REM select other drive
1320 CMD=8
1330 GOSUB 11020
1340 DOKE HR,MD+BK : REM select drive
1350 CMD=8
1360 GOSUB 11020
1370 RETURN
2200 REM go to sleep
2210 CMD=230
2220 DOKE SR,CMD : REM send command
2230 RETURN
2300 REM go to idle mode
2310 CMD=225
2320 GOTO 11000
2400 REM go to standby mode
2410 CMD=224
2420 GOTO 11000
2500 REM query device power mode
2510 CMD=229
2520 GOSUB 11000
2530 I=DEEK(SC) AND 255 : REM sector count
2540 PRINT "Device is in ";
2550 IF I=0 THEN PRINT "standby"; : GOTO 2590
2560 IF I=128 THEN PRINT "idle"; : GOTO 2590
2570 IF I=255 THEN PRINT "active"; : GOTO 2590
2580 PRINT "unknown";
2590 PRINT " mode"
2595 RETURN
2600 REM execute diagnostics
2610 CMD=144
2620 GOSUB 11000
2630 I=(DEEK(ER) AND 255) : REM read error register
2640 PRINT "Device 0 ";
2650 IF (I AND 127)=1 THEN PRINT "passed" : GOTO 2670
2660 PRINT "failed"
2670 PRINT "Device 1 ";
2680 IF (I AND 128) THEN PRINT "failed" : GOTO 2695
2690 PRINT "passed or not present"
2695 RETURN
2700 REM test read
2710 INPUT "Sector number";IX
2725 GOSUB 10000
2730 CMD=32
2740 GOSUB 11000
2750 REM display sector
2760 S$=""
2770 PRINT
2780 FOR WC=0 TO 255
2790 GOSUB 12000
2800 HH=(HI/256) AND 127
2810 IF HH < 32 THEN HH=46
2815 HL=HI AND 127
2820 IF HL <32 THEN HL=46
2825 S$=S$+CHR$(HL)
2830 S$=S$+CHR$(HH)
2865 PRINT " ";HEX$(HI,4);
2870 IF (WC AND 7)< >7 THEN 2885
2875 PRINT "    ";S$
2880 S$=""
2885 NEXT
2890 RETURN
2900 REM recalibrate device (seek to 0)
2910 IX=0
2920 GOSUB 10000
2930 CMD=16
2940 GOTO 11000
3000 REM change drive selection
3010 DO : INPUT "Which drive [0/1]";I
3020 LOOP WHILE (I AND -2)
3030 MD=MD AND 239
3040 IF I THEN MD=MD OR 16
3050 DOKE HR,MD+BK : REM select drive
3060 RETURN
3100 REM display allegedly selected drive
3110 PRINT "Drive ";
3120 IF (MD AND 16) THEN PRINT "1"; :GOTO 3140
3130 PRINT "0";
3140 PRINT " selected"
3150 RETURN
6000 REM read ID data and display drive parameters
6010 CMD=236
6020 GOSUB 11000
6030 GOSUB 12000
6035 REM will identify some ATAPI devices
6040 PRINT "ATA";
6050 IF (HI AND -32768) THEN PRINT "PI";
6060 PRINT " Device - ";
6070 IF (HI AND 128)=0 THEN PRINT "non";
6080 PRINT "removable media"
6085 PRINT
6090 GOSUB 12000 : CYL=HI
6100 GOSUB 12000
6110 GOSUB 12000 : HE=HI
6120 GOSUB 12000 : GOSUB 12000
6130 GOSUB 12000 : SPT=HI
6140 GOSUB 12000 : GOSUB 12000 : GOSUB 12000
6150 PRINT "Cylinders",CYL
6160 PRINT "Heads",HE
6170 PRINT "Sectors",SPT
6180 SIZE = CYL*HE*SPT/2048
6190 PRINT "Capacity",SIZE;" MB"
6195 PRINT
6200 PRINT "Serial # "," "; : CNT=10 : GOSUB 7000 : PRINT
6210 GOSUB 12000 : GOSUB 12000 : GOSUB 12000
6220 PRINT "Rev: "," "; : CNT=4 : GOSUB 7000 : PRINT
6230 PRINT "Model: "," "; : CNT=20 : GOSUB 7000 : PRINT
6240 PRINT
6250 FOR I=1 TO 7
6260 GOSUB 12000
6270 NEXT
6280 GOSUB 12000 : PRINT "Current logical cylinders",HI
6300 GOSUB 12000 : PRINT "Current logical heads",HI
6310 GOSUB 12000 : PRINT "Current logical sectors",HI
6320 GOSUB 12000 : CAP=HI
6330 GOSUB 12000 : CAP=HI*65536+CAP
6340 PRINT "Current capacity in sectors",CAP
6350 GOSUB 12000 : GOSUB 12000 : CAP=HI
6360 GOSUB 12000 : CAP=HI*65536+CAP
6370 PRINT "User addressable LBA sectors";CAP
6380 FOR I=62 TO 255 : GOSUB 12000 : NEXT
6400 RETURN
7000 REM read CNT words and output ASCII
7010 FOR I=1 TO CNT
7020 LO=PEEK(IO) : REM read data
7030 HI=PEEK(IO+1) : REM read data
7040 IF HI > 31 THEN PRINT CHR$(HI);
7050 IF LO > 31 THEN PRINT CHR$(LO);
7060 NEXT
7070 RETURN
10000 REM Set LBA block number and sector count of 1
10005 REM As BASIC only has 24 significant bits this
10006 REM routine will break if the block number is
10007 REM greater than 16777216 (just over 8GB limit!)
10010 I1=IX AND 255
10020 IX=IX/256 : I2=IX AND 255
10030 IX=IX/256 : I3=IX AND 255
10040 IX=IX/256 : I4=IX AND 15
10050 DOKE HR,I4+MD+BK : REM drive/LBA upper 4 bits
10060 DOKE SS,I1 : REM LBA bits 0 to 7
10070 DOKE CL,I2 : REM LBA bits 8 to 15
10080 DOKE CH,I3 : REM LBA bits 26 to 23
10090 DOKE SC,1 : REM sector count
10100 RETURN
11000 REM send command to drive, ensure ready
11010 DO : LOOP UNTIL (PEEK(SR) AND 192)=64 : REM get status
11020 DOKE SR,CMD : REM send command
11030 DO : I=PEEK(SR) : REM get status
11040 LOOP UNTIL (I AND 128)=0
11050 IF (I AND 1)=0 THEN RETURN
11060 I=PEEK(ER) : PRINT "IDE Error, command";CMD : REM read error register
11062 IF (I AND 2) THEN PRINT "No media"
11064 IF (I AND 4) THEN PRINT "Aborted"
11066 IF (I AND 8) THEN PRINT "Media change requested"
11068 IF (I AND 16) THEN PRINT "Sector ID not found"
11070 IF (I AND 32) THEN PRINT "Media changed"
11072 IF (I AND 64) THEN PRINT "Uncorrectable error"
11090 RETURN
12000 REM read one word, return it in HI
12010 WAIT SR,16 : REM wait status
12020 HI=DEEK(IO) : REM read data
12030 RETURN
20000 REM write C000h-DFFDh to disk
20010 AA=$C000
20030 FOR WX = 1 TO 16
20040 PRINT "Writing sector";WX
20050 IX=WX
20060 GOSUB 10000
20070 CMD=48
20080 GOSUB 11000
20090 FOR WC=1 TO 256
20120 HI=DEEK(AA)
20140 AA=AA+2
20150 GOSUB 22000
20160 NEXT
20190 NEXT
20999 RETURN
22000 REM write one word in HI
22010 DO : LOOP UNTIL (PEEK(SR) AND 144)=16 : REM get status
22020 DOKE IO,HI : REM write data
22030 RETURN

LazyPROM

LazyPROM – The idle person’s EPROM emulator.

Even with a new EPROM eraser that can handle 14 EPROMs at once I got fed up going through the program/test/erase cycle that is part of developing code.
I did think, briefly, of building a full featured EPROM emulator, but that was far too much like work. There had to be an easier way.
As I have a few old Dallas smart sockets kicking about I decided that one of them, with a 32k low power CMOS RAM, would be ideal.

The circuit.

The circuit is very simple. The pinout for a standard 32k ROM and standard 32k EPROM differ on only two pins. This means most of the circuit is just pin for pin connections as can be seen in the diagram below.


LazyPROM diagram.
With the switch in the ‘RAM’ position all the pins are connected pin to pin and the RAM can be programmed either in a programmer or as part of a microprocessors main memory. With the switch in the ‘EPROM’ position the RAM’s R/W line is disconnected from the header and pin 27 on the header is now connected to the RAM’s A14 pin. This makes the RAM chip look like a 32k EPROM to the target socket.

Construction.

The whole thing was put together on a small piece of prototyping board in well under an hour. The most dificult part was soldering the headers to the track side of the board.
Note – Don’t be tempted to use anything but hard, gold, turned pin header pins for the header. The, easier to find, square pins will either not fit, or damage, most IC sockets and the cheaper tinned header pins will be damaged more easily during use.


 

In use.

As no special voltages or timings are needed to program the RAM in the smart socket it would be fairly easy to make a programmer for this, if you don’t already have something suitable. The Dallas smart socket is supposed to retain RAM contents for up to 10 years but the
ones I have, being very second user, are an unknown quantity. They certainly work for a week or so though one was originally completely dead and required some surgery to replace both batteries. More on that elsewhere.
The first one I built proved to be so usefull that I’ve since built another and both have proved invaluable in hacking the Ferguson SRB1 DMAC receiver.

EhBASIC Internals

Enhanced BASIC internals

Floating point numbers.
Floating point numbers are stored in memory in four bytes. The format of the
numbers is as follows.

Exponent S Mantissa 1 Mantissa 2 Mantissa 3

Exponent
This is the power of two to which the mantissa is to be raised. This number is biased to +$80 i.e. 2^0 is represented by $80, 2^1 by $81 etc. Zero is a special case and is used to represent the value zero for the whole of the number.
S
Sign bit. This bit (b7 of mantissa 1) is one if the number is negative.
Mantissa 1/2/3
This is the 24 bit mantissa of the number and is normalised to make the highest bit (b7 of mantissa 1) always one. So the absolute value of the
mantissa varies between 0.5 and 0.9999999403954 . As we know that the highest bit is always one it is replaced by the sign bit in memory.
Example.

$82,$49,$0F,$DB = +3.14159274	nearest floating equivalent to pi
 |   ||  |   |
 |   |\--+---+- = 0.785398185	absolute value of mantissa
 |   |
 |   \--------- = +		b7 of mantissa 1 is zero
 |
 \------------- = x 2^2 = 4	mantissa to be multiplied by 4

Values represented in this way range between + and – 1.70141173×10^38

BASIC program memory use.

A BASIC program is stored in memory from Ram_base upwards. It’s format is ..
$00 Start of program marker byte
.. then each BASIC program line which is stored as ..
start of next line pointer low byte
start of next line pointer high byte
line number low byte
line number high byte
code byte(s)
$00 End of line marker byte
.. and finally ..
$00 End of program marker byte 1
$00 End of program marker byte 2
If there is no program in memory only the start and end marker bytes are present.

BASIC variables memory use.
After the program come the variables and function references, all six bytes
long, which are stored as ..
1st character of variable or function name (+$80 if FN name)
2nd character of variable or function name (+$80 if string)
.. then for each type ..

Numeric
String
Function
Exponent String length BASIC execute pointer low byte
Sign (bit 7) + mantissa 1 String pointer low byte BASIC execute pointer high byte
Mantissa 2 String pointer high byte Function variable name 1st character
Mantissa 3 $00 Function variable name 2nd character

After the variables come the arrays, which are stored as ..
1st character of variable name
2nd character of variable name (+$80 if string)
array size in bytes low byte (size includes this header)
array size in bytes high byte
number of dimensions
[dimension 3 size high byte] (lowest element is zero)
[dimension 3 size low byte]
[dimension 2 size high byte] (lowest element is zero)
[dimension 2 size low byte]
dimension 1 size high byte (lowest element is zero)
dimension 1 size low byte
.. and then each element ..

Numeric
String
Exponent String length
Sign (bit 7) + mantissa 1 String pointer low byte
Mantissa 2 String pointer high byte
Mantissa 3 $00

The elements of every array are stored in the order ..
index1 [0-n], index2 [0-n], index3 [0-n]
i.e. element (1,2,3) in an array of (3,4,5) would be the ..
1 + 1 + 2*(3+1) + 3*(3+1)*(4+1) = 70th element
(As array dimensions range from 0 to n element n will always be the (n+1)th
element in memory.)

String placement in memory.

Strings are generally stored from the top of available RAM, Ram_top, working down, however if the interpreter encounters a line such as ..

	100 A$ = "This is a string"

.. then the high/low pointer in the A$ descriptor will point to the string in
program memory and will not make a copy of the string in the string memory.
String descriptors in BASIC.
A string descriptor is a three byte table that describes a string, it is of the format ..
base = string length
base+1 = string pointer low byte
base+2 = string pointer high byte

Stack use in BASIC.

GOSUB and DO both push on the stack ..
BASIC execute pointer high byte
BASIC execute pointer low byte
current line high byte
current line low byte
command token (TK_GOSUB or TK_DO)

FOR pushes on the stack ..
BASIC execute pointer low byte
BASIC execute pointer high byte
FOR line high byte
FOR line low byte
TO value mantissa3
TO value mantissa2
TO value mantissa1
TO value exponent
STEP sign
STEP value mantissa3
STEP value mantissa2
STEP value mantissa1
STEP value exponent
var pointer for FOR/NEXT high byte
var pointer for FOR/NEXT low byte
token for FOR (TK_FOR)

post

EhBASIC Useful routines

There are many subroutines within BASIC that can be useful if you wish to use your own assembly routines with it. Here are some of them with a brief description of their function. For full details see the source code.

Note that most, if not all, of these routines need EhBASIC to be initialised before they will work properly and can not be used in isolation from EhBASIC.

The routines.

LAB_IGBY
BASIC increment and get byte routine. gets the next byte from the BASIC command stream. If the byte is a numeric character then the carry flag will be set, if the byte is a termination byte, either null or a statement separator, then the zero flag will be set. Spaces in the command stream will automatically be ignored.
LAB_GBYT
BASIC get byte routine. Gets the current byte from the BASIC command stream but does not change the pointer. Otherwise the same as above.
LAB_COLD
Performs a cold start. BASIC is reset and all BASIC memory is cleared.
LAB_WARM
Performs a warm start. Execution is stopped and BASIC returns to immediate mode.
LAB_OMER
Do “Out of memory” error, then warm start. The same as error $0C below.
LAB_XERR
With X set, do error #X, then warm start.

X Error X Error
$00 NEXT without FOR $02 syntax
$04 RETURN without GOSUB $06 out of data
$08 function call $0A overflow
$0C out of memory $0E undefined statement
$10 array bounds $12 double dimension array
$14 divide by 0 $16 illegal direct
$18 type mismatch $1A long string
$1C string too complex $1E continue error
$20 undefined function $22 LOOP without DO

LAB_INLN
Print “? ” and get BASIC input. Returns XY (low/high) as a pointer to the start of the input line. The input is null terminated.
LAB_SSLN
Search Basic for a line, the line number required is held in the temporary integer, from start of program memory. Returns carry set and a pointer to
the line in Baslnl/Baslnh if found, if not it returns carry and a pointer to the next numbered line in Baslnl/Baslnh.
LAB_SHLN
Search Basic for temporary integer line number from AX. Same as above but starts the search from AX (low/high).
LAB_SNBS
Scan for next BASIC statement (: or [EOL]). Returns Y as index to : or [EOL] from (Bpntrl).
LAB_SNBL
Scan for next BASIC line. Same as above but only returns on [EOL].
LAB_REM
Perform REM, skip (rest of) line.
LAB_GFPN
Get fixed-point number into temporary integer.
LAB_CRLF
Print [CR]/[LF] to output device.
LAB_PRNA
Print character in A to output device.
LAB_GVAR
Get variable address. Returns a pointer to the variable in Lvarpl/h and sets the data type flag, $FF=string, $00=numeric.
LAB_EVNM
Evaluates an expression and checks the result is numeric, if not it does a type mismatch. The result of the expression is returned in FAC1.
LAB_CTNM
Check if source is numeric, else do type mismatch.
LAB_CTST
Check if source is string, else do type mismatch.
LAB_CKTM
Type match check, set carry for string, clear carry for numeric.
LAB_EVEX
Evaluate expression.
LAB_GVAL
Get numeric value from line. Returns the result in FAC1.
LAB_SCCA
Scan for the byte in A as the next byte. If so return here, else do syntax error then warm start.
LAB_SNER
Do syntax error, then warm start.
LAB_CASC
Check byte is alpha (“A” to “Z” or “a” to “z”), return carry clear
if so.
LAB_EVIN
Evaluate integer expression. Return integer in FAC1_3/FAC1_2
(low/high).
LAB_EVPI
Evaluate +ve integer expression.
LAB_EVIR
Evaluate integer expression, check is in range -32786 to 32767
LAB_FCER
Do function call error, then warm start.
LAB_CKRN
Check that the interpreter is not in immediate mode. If not then return, if so do illegal direct error.
LAB_GARB
Perform garbage collection routine.
LAB_EVST
Evaluate string.
LAB_ESGL
Evaluate string, return string length in Y.
LAB_SGBY
Scan and get byte parameter, return the byte in X.
LAB_GTBY
Get byte parameter and ensure numeric type, else do type mismatch error. Return the byte in X.
LAB_EVBY
Evaluate byte expression, return the byte in X.
LAB_GADB
Get two parameters as in POKE or WAIT. Return the byte (second parameter)in X and the integer (first parameter) in the temporary integer pair, Itempl/Itemph.
LAB_SCGB
Scan for “,” and get byte, else do Syntax error then warm start.
Return the byte in X.
LAB_F2FX
New convert float to fixed routine. accepts any value that fits into 24 bits, +ve or -ve and converts it into a right truncated integer in the
temporary integer pair, Itempl/Itemph.
LAB_UFAC
Unpack the four bytes starting (AY) into FAC1 as a floating point number.
LAB_PFAC
Pack the floating point number in FAC1 into the current variable (Lvarpl).
LAB_STFA
Stores a 16 bit number in FAC1. Set X to the exponent required (usually $90) and the carry set for +ve numbers and clear for -ve numbers. The
routine will clear FAC1 mantissa3 and then normalise it.
LAB_AYFC
Save integer AY (A = high byte, Y = low byte) in FAC1 and convert to float. The result will be -32768 to +32767.
LAB_MSSP
Make string space A bytes long. This returns the following.
str_ln = A = string length
str_pl = Sutill = string pointer low byte
str_ph = Sutilh = string pointer high byte
LAB_RTST
Return string. Takes the string described instr_ln, str_pl and str_ph and puts it on the string stack. This is how you return a string to BASIC.

post

EhBASIC Using USR()

Enhanced BASIC, using USR()

USR(<expression[$]>) calls the machine code function pointed to by the user jump vector after evaluating <expression[$]> and placing the result in the first floating accumulator. Once the user function exits, via an RTS, the value in the floating accumulator is passed back to EhBASIC.

Either a numeric value or a string can be passed, and either type can be returned depending on the setting of the data type flag at the end of the user code and the return point (see code examples for details).

It can also be extended by adding parameters to USR() and parsing them from within the routine in the same way that CALL can be extended, just remember to get the value from FAC1 first.

How to – numeric source, numeric result.

First you need to write the code.

; this code demonstrates the use of USR() to quickly calculate the square of a
; byte value. Compare this with doing SQ=A*A or even SQ=A^2.
	.include BASIC.DIS	; include the BASIC labels file. this allows
				; you easy access to the internal routines you
				; need to parse the command stream and access
				; some of the internals of BASIC. It is usually
				; output by the assembler as part of the listing
				; or as a separate, optional, file.
; for now we'll put this in the spare RAM at $F400
	*=	$F400
Square
	JSR	LAB_EVBY	; evaluate byte expression, result in X and FAC1_3
	LDA	#$00		; clear A
	STA	FAC1_2		; clear square low byte (use FAC1 as the workspace)
				; (no need to clear the high byte, it gets shifted out)
	TXA			; copy byte to A
	LDX	#$08		; set bit count
Nextr2bit
	ASL	FAC1_2		; low byte *2
	ROL	FAC1_1		; high byte *2+carry from low
	ASL	A		; shift byte
	BCC	NoSqadd		; don't do add if C = 0
	TAY			; save A
	CLC			; clear carry for add
	LDA	FAC1_3		; get number
	ADC	FAC1_2		; add number^2 low byte
	STA	FAC1_2		; save number^2 low byte
	LDA	#$00		; clear A
	ADC	FAC1_1		; add number^2 high byte
	STA	FAC1_1		; save number^2 high byte
	TYA			; get A back
NoSqadd
	DEX			; decrement bit count
	BNE	Nextr2bit	; go do next bit
	LDX	#$90		; set exponent=2^16 (integer)
	SEC			; set carry for +ve result
	JMP	LAB_STFA	; set exp=X, clearFAC1 mantissa3, normalise & return

Now you need to set up the address for your function. This is done by DOKEing an address into the USR() vector e.g.

DOKE $0B,$F400		; set the user function address to addr
			; $0B	- user function vector address
			; $F400	- routine address

Finally you need to set the vector in your BASIC program and use that to call the function

.
10 DOKE $0B,$F400
.
.
.
145 SQ=USR(A)
.

How to – numeric source, string result.
As before, first you need to write the code.

; this code demonstrates the use of USR() to generate a string of # characters.
; the length of the required string is the parameter passed.
	.include BASIC.DIS	; include the BASIC labels file. this allows
				; you easy access to the internal routines you
				; need to parse the command stream and access
				; some of the internals of BASIC. It is usually
				; output by the assembler as part of the listing
				; or as a separate, optional, file.
; for now we'll put this in the spare RAM at $F400
	*=	$F400
STRING
	JSR	LAB_EVBY	; evaluate byte expression, result in X and FAC1_3
	TXA			; string is byte length
	BEQ	NUL_STRN	; branch if null string
	JSR	LAB_MSSP	; make string space A bytes long A=$AC=length,
				; X=$AD=Sutill=ptr low byte, Y=$AE=Sutilh=ptr high byte
	LDA	#"#"		; set character
	LDY	FAC1_3		; get length
SAV_HASH
	DEY			; decrement bytes to do
	STA	(str_pl),Y	; save byte in string
	BNE	SAV_HASH	; loop if not all done
NUL_STRN
	PLA			; dump return address (return via get value
	PLA			; from line, this skips the type checking and
				; so allows a string result to be returned)
	JMP	LAB_RTST	; check for space on descriptor stack then put
				; string address and length on descriptor stack
				; & update stack pointers

Now you need to set up the address for your function. This is done by DOKEing an address into the USR() vector e.g.

DOKE $0B,$F400		; set the user function address to addr
			; $0B	- user function vector address
			; $F400	- routine address

Finally you need to set the vector in your BASIC program and use that to call the function

.
10 DOKE $0B,$F400
.
.
.
145 HA$=USR(A)
.

How to – string source, numeric result.

As before, first you need to write the code.

; this code demonstrates the use of USR() to test a string of characters.
; if all the string is alpha -1 is returned, else 0 is returned.
	.include BASIC.DIS	; include the BASIC labels file. this allows
				; you easy access to the internal routines you
				; need to parse the command stream and access
				; some of the internals of BASIC. It is usually
				; output by the assembler as part of the listing
				; or as a separate, optional, file.
; for now we'll put this in the spare RAM at $F400
	*=	$F400
ALPHA
	JSR	LAB_EVST	; evaluate string
	TAX			; copy length to X
	BEQ	NOT_ALPH	; branch if null string
	LDY	#$00		; clear index
ALP_LOOP
	LDA	(ut1_pl),Y	; get byte from string
	JSR	LAB_CASC	; is character "a" to "z" (or "A" to "Z")
	BCC	NOT_ALPH	; branch if not alpha
	INY			; increment index
	DEX			; decrement count
	BNE	ALP_LOOP	; loop if not all done
	LDA	#$FF		; set for -1
	BNE	IS_ALPHA	; branch always
NOT_ALPH
	LDA	#$00		; set for 0
IS_ALPHA
	TAY			; copy byte
	LDX	#$00		; clear byte
	STX	Dtypef		; clear data type flag, $00=numeric
	JMP	LAB_AYFC	; save & convert integer AY to FAC1 & return

Now you need to set up the address for your function. This is done by DOKEing an address into the USR() vector e.g.

DOKE $0B,$F400		; set the user function address to addr
			; $0B	- user function vector address
			; $F400	- routine address

Finally you need to set the vector in your BASIC program and use that to call the function

.
10 DOKE $0B,$F400
.
.
.
145 AL=USR(A$)
.

How to – string source, string result.
As before, first you need to write the code.

; this code demonstrates the use of USR() invert the case of a string of
; characters. only alpha characters will be affected.
	.include BASIC.DIS	; include the BASIC labels file. this allows
				; you easy access to the internal routines you
				; need to parse the command stream and access
				; some of the internals of BASIC. It is usually
				; output by the assembler as part of the listing
				; or as a separate, optional, file.
; for now we'll put this in the spare RAM at $F400
	*=	$F400
ALPHA
	JSR	LAB_EVST	; evaluate string
	STA	str_ln		; set string length
	STX	str_pl		; set string pointer low byte
	STY	str_ph		; set string pointer high byte
	TAX			; copy length to X
	BEQ	NO_STRNG	; branch if null string
	LDY	#$00		; clear index
ALP_LOOP
	LDA	(ut1_pl),Y	; get byte from string
	JSR	LAB_CASC	; is character "a" to "z" (or "A" to "Z")
	BCC	NOT_ALPH	; branch if not alpha
	EOR	#$20		; toggle case
	STA	(ut1_pl),Y	; save byte back to string
NOT_ALPH
	INY			; increment index
	DEX			; decrement count
	BNE	ALP_LOOP	; loop if not all done
NO_STRNG
	PLA			; dump return address (return via get value
	PLA			; from line, this skips the type checking and
				; so allows a string result to be returned)
	JMP	LAB_RTST	; check for space on descriptor stack then put
				; string address and length on descriptor stack
				; & update stack pointers

Now you need to set up the address for your function. This is done by DOKEing an address into the USR() vector e.g.

DOKE $0B,$F400		; set the user function address to addr
			; $0B	- user function vector address
			; $F400	- routine address

Finally you need to set the vector in your BASIC program and use that to call the function

.
10 DOKE $0B,$F400
.
.
.
145 A$=USR(A$)
.