Lee Davison
http://retro.hansotten.nl/lee-davison-web-site/
About small SBC systems
Lee Davison
http://retro.hansotten.nl/lee-davison-web-site/
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.
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.
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.
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.
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.
Enables or disables counting for TIMERp (where p is either X or Y).
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).
This command has been removed from EhBASIC74. It’s function is now handled by an enhanced RETURN command.
This command has been removed from EhBASIC74. It’s function is now handled by an enhanced RETURN command.
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.
See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.
See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.
See INTx {ON|OFF|CLEAR}, CNTRx
{ON|OFF|CLEAR} or TIMERq {ON|OFF|CLEAR}.
These commands have been removed from EhBASIC74 as there is no IRQ or NMI interrupt on the 740 series microprocessor.
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.
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.
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.
This command has been removed from EhBASIC74 as there is no IRQ interrupt on the 740 series microprocessor.
This command has been removed from EhBASIC74 as there is no NMI interrupt on the 740 series microprocessor.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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
;******** 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
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
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.
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.
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 ..
The disadvantage is that every write must be sixteen bits, even if the top eight bits
aren’t used.
Other features are ..
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 – 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.
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.
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.
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 ..
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 ..
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"
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)
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.
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
DOKE $0B,$F400 ; set the user function address to addr ; $0B - user function vector address ; $F400 - routine address
. 10 DOKE $0B,$F400 . . . 145 SQ=USR(A) .
; 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
DOKE $0B,$F400 ; set the user function address to addr ; $0B - user function vector address ; $F400 - routine address
. 10 DOKE $0B,$F400 . . . 145 HA$=USR(A) .
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
DOKE $0B,$F400 ; set the user function address to addr ; $0B - user function vector address ; $F400 - routine address
. 10 DOKE $0B,$F400 . . . 145 AL=USR(A$) .
; 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
DOKE $0B,$F400 ; set the user function address to addr ; $0B - user function vector address ; $F400 - routine address
. 10 DOKE $0B,$F400 . . . 145 A$=USR(A$) .
Enhanced BASIC, extending CALL
CALL <address> calls a machine code routine at location address. While this in itself is useful it can be extended by adding parameters to the CALL and parsing them from within the routine.
This technique can also be used to pass extra parameters to the USR() function.
B>How to.
First you need to define the parameters for your CALL. This example is for an imaginary bitmapped graphic device.
CALL PLOT,x,y | Set the pixel at x,y | |
PLOT | routine address | |
x | x axis value, range 0 to 255 | |
y | y axis value, range 0 to 64 |
This will then be the form that the call will always take.
Now you need to write the code.
.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 PLOT JSR LAB_SCGB ; scan for "," and get byte STX PLOT_XBYT ; save plot x JSR LAB_SCGB ; scan for "," and get byte CPX #$40 ; compare with max+1 BCS PLOT_FCER ; if 64d or greater do function call error STX PLOT_YBYT ; save plot y ; now would be your code to perform the plot command ;. ;. ;. ;. ;. RTS ; return to BASIC ; does BASIC function call error PLOT_FCER JMP LAB_FCER ; do function call error, then warm start ; now we just need the variable storage PLOT_XBYT .byte $00 ; set default PLOT_YBYT .byte $00 ; set default END
. 10 PLOT = $F400 . . 145 CALL PLOT,25,14 : REM set pixel .