← Small tutorials

04 / VALUES ACROSS THE LANGUAGE BOUNDARY

Ask ML.
Get a BASIC value.

WAIT and USR are easy to overlook until BASIC needs to work with machine language. WAIT tells our interface when to act. USR lets it ask for the data it needs, directly inside a BASIC expression.

Commodore 128 · BASIC 7.0 + 8502 assembly · Companion to the WAIT and FAC tutorials

01 / A FUNCTION YOU SUPPLY

The argument is a question for your ML routine.

USR(x) is a BASIC function whose implementation you supply in machine language. BASIC evaluates the argument, passes a numeric value in its floating-point accumulator, FAC1, and calls the installed routine. The usual numeric path returns a value in FAC1 for BASIC to use in the surrounding expression.

For AIS4CBM, we give the argument a particular meaning: the field we want from the currently locked vessel. With the vessel reader selected, USR(2) asks for the MMSI, USR(5) asks for speed over ground, and USR(34) asks for the vessel name.

SYS is useful for actions and register-based calls. USR is useful when ML supplies a value that BASIC can assign, print or use in a calculation. In this application the two cooperate: a SYS call selects and prepares the vessel; USR calls read its fields.

02 / CONNECT BASIC TO THE RIGHT ROUTINE

One USR entry, a selected operation.

On the C128, $1219/$121A hold the low and high bytes of the USR target address, in the jump entry beginning at $1218. Our installer points that entry at usrDelegate, a short bridge that handles the bank switch and return:

Basic8Config.asm · install the USR bridge
setDelegateUSR:
        lda #<usrDelegate
        sta $1219
        lda #>usrDelegate
        sta $121a
        rts

After a successful vessel lock, getVesselDataImpl selects getData as the routine behind that bridge:

Basic8Config.asm · excerpt after the lock succeeds
        lda #<getData
        sta usrDelegate.usrFarLo
        lda #>getData
        sta usrDelegate.usrFarHi
        jsr setDelegateUSR

usrFarLo and usrFarHi name the two address bytes of a JSR instruction inside the bridge. Updating them makes the next USR call reach the selected operation.

This selection matters. The time API selects a different handler: after SYS GT, the viewer uses USR(1) and USR(2) for time and date. Locking a vessel selects the field reader again. Keep a group of vessel reads together; do not select another USR service halfway through it.

03 / GIVE THE READER A VESSEL

Lock once. Read the fields you need.

There are two different identifiers in this transaction. A record ID chooses the vessel; a field selector chooses a value from it. The SYS call receives the record ID as low and high bytes. Each following USR call receives only a field selector.

The lock/fetch operation loads the vessel’s metadata and available payloads from REU into the runtime’s scratch area. It also selects the appropriate field table for that vessel class. USR reads from this fetched data, so BASIC does not have to repeat a REU fetch for every field.

Simplified BASIC subroutine · read only the MMSI
1000 BANK 0:SYS GV,1,LO,HI:RREG A,,,P
1010 IF (P AND 1)<>0 THEN RETURN
1020 MM=USR(2)
1030 SYS GV,4,LO,HI
1040 RETURN

This teaching fragment assumes an initialized runtime, GV=DEC("8022"), and a valid record ID already split into LO/HI. It is not a standalone program. RREG captures the returned processor status in P; bit 0 is carry. If the lock fails, the subroutine returns without reading or unlocking, and MM is unchanged.

Action 4 releases a read-only lock without persisting visual metadata. A display-update handler uses action 0 when it has consumed and updated that metadata; that path also acknowledges the vessel-message event. The fragment above performs no event acknowledgement. See the API transaction contract for the distinction.

The real viewer reads several fields together.

aiscbm8.bas · Class A static-data handler, line 360
360 NM$=USR(34):IM=USR(6):CS$=USR(24):DE$=USR(8):DR=USR(14)/10:VT=USR(30)

The surrounding dialog has already locked the vessel and checked for static data before it calls this handler. The assignments retrieve the name, IMO number, callsign, destination, draught and ship type. BASIC remains responsible for formatting these values for display.

Selected fields used by the current vessel viewer
SelectorFieldBASIC use
2MMSINumeric identity, such as MM=USR(2).
5Speed over groundClass A/B display: USR(5)/10 converts tenths of a knot.
6IMO numberIM=USR(6) in the Class A static handler.
24CallsignString: CS$=USR(24).
34NameString: NM$=USR(34).
35Course over groundUSR(35)/10 converts tenths of a degree.

Use the selectors and field meanings for the matching build and vessel class. USR returns the stored field value; scaling and AIS “not available” values still need interpretation. When the current reader cannot find a field or its payload is absent, it returns numeric zero. That is not automatically an empty string or proof of a measured zero, so check availability before assigning a string field.

04 / ONE QUESTION, TWO KINDS OF ANSWER

Numbers use FAC. Names need a BASIC string.

getData converts the USR argument from FAC1 to a field index. The field table identifies the source and offset in the locked record, and the descriptor identifies its size and type. MMSI comes from the registry scratch record; positional and static fields come from the fetched payload scratch.

BASIC passes a field selector through FAC1 to the installed USR bridge and vessel field reader. Numeric fields return through FAC1 with carry clear. Text fields use a BASIC string descriptor and the bridge's carry-set return path. Each call follows one branch.
Each call takes the branch for its field’s type. The example speed and name are illustrative values. Open full size ↗

A numeric result replaces the numeric argument.

For a numeric field, getDataByField copies the decoded bytes into tempStore, then calls trans to convert them to FAC1. The value that entered FAC1 was the field selector; the value leaving FAC1 is the answer. For example, a Class A/B speed field containing 123 becomes BASIC’s number 123, which the viewer divides by 10 to display 12.3 knots.

The numeric path returns with carry clear. Our integer-to-FAC tutorial follows the conversion for a larger value such as an MMSI.

A string requires more than a pointer.

For a text field, stringHandler determines the useful length, allocates space through BASIC’s string routines, copies the characters into the Bank 1 string area, and prepares the string descriptor. It then returns with carry set.

That carry bit is our bridge’s convention. It is not a general BASIC feature that turns any USR routine into a string-returning function. The delegate recognizes it and takes a different return path:

irqbridges.asm · complete usrDelegate, comments omitted
usrDelegate:
{
        lda $ff00
        sta initialBank+1
        lda #BANK0
        sta $FF00
        .label usrFarLo = *+1
        .label usrFarHi = *+2
        jsr $0000
        bcc !+
        pla
        pla
        initialBank:
!:      lda #$ff
        sta $ff00
        rts
}

With carry clear, the bridge restores the bank and returns normally. With carry set, the two PLA instructions discard one saved return address from the stack before returning. Together with the prepared BASIC string descriptor, this is the interpreter-specific return path that makes NM$=USR(34) work. The stack adjustment belongs to this USR bridge; it is not a general string-return recipe for arbitrary ML callers.

05 / TWO SMALL FEATURES, ONE CLEAN HANDOFF

WAIT says when. USR supplies the value.

Our BASIC interface waits for a relevant notification, obtains its vessel record ID, locks that record through SYS, reads the required fields through USR, and releases the record. ML owns the decoded storage; BASIC works with values that fit naturally into its expressions.

  • Install before calling. USR needs a valid target and the runtime’s memory setup.
  • Select the operation. The same USR syntax can reach the vessel reader or another service, depending on the active delegate.
  • Keep the transaction together. Lock, check success, read available fields, then unlock the same record.
  • Return the type BASIC expects. FAC handles numbers; our string path also prepares a descriptor and adjusts the return through the interpreter.

Read the code and the companion explanations

Assembly excerpts retain the current instructions, with comments omitted and spacing simplified. The short lock/read/release subroutine is a teaching example; line 360 is from the actual viewer. GitLab links follow main.