Jaap's Psion II Page

Psion II Machine Code Tutorial, Part 2


What if the machine code needs different or more parameters?

Lets write a, slightly, more ambitious program, a program that reverses the order of a string. It would change "ABCDE" to "EDCBA". We can't pass a string to the machine code program as a parameter, only integers. Instead of passing the string value, we can pass the string address to the program. The program then knows exactly where to find the string value, and also where it can put the result. To reverse the string A$, the code is given the parameter ADDR(A$). The program will swap the last character with the first, the next to last with the second and so on, until the middle of the string is reached.

The machine code could look like this:

18     XGDX       'Put ADDR(A$) in the X register
E600 LDAB 0,X 'Put length of A$ in B
08 INX 'Put the address of the first string character in memory addresses 41 and 42.
DF41 STX 41 'The bytes at the addresses 41 to 4C can be freely used by machine code programs. Unfortunately the value of these is not always conserved in OPL so they can't be used to pass information between OPL and MC reliably.
3A ABX '\Get address of last character in string by adding the
09 DEX '/length minus 1.
54 LSRB 'Divide the length by two. Is number of swaps necessary.
2715 BEQ end 'If string is empty, or of length one, then return.
lp:
37 PSHB 'Temporarily store B, the number of swaps still needed.
A600 LDAA 0,X 'Get character from end.
3C PSHX 'Save position from end.
DE41 LDX 41 'Get position from beginning.
E600 LDAB 0,X 'Get character from beginning.
A700 STAA 0,X 'Store end character here.
08 INX '\Move position further forward to centre.
DF41 STX 41 '/and store it again.
38 PULX 'Get position from end.
E700 STAB 0,X 'Put beginning character here.
09 DEX 'Move further back to the centre.
33 PULB 'Get number of swaps still needed
5A DECB 'Decrease by 1, since just did a swap.
26EB BNE lp 'Loop until no swaps left to do
end:
39 RTS 'return

Here is its implementation in OPL.

REVERSE: LOCAL MC$(33),A$(20) MC$=CONV$:("18E60008DF413A0954271537A6003CDE41") MC$=MC$+CONV$:("E600A70008DF4138E70009335A26EB39") DO INPUT A$ USR(ADDR(MC$)+1,ADDR(A$)) PRINT A$ UNTIL GET=1 OR A$=""

This machine code routine will reverse any ordinary string variable.
If you wish to use it on one element of a string array, you have to calculate the memory address where that particular string starts because ADDR(A$()) returns the address of the first of the strings and ADDR(A$(3)) for example doesn't work. In particular, if you have declared A$() by LOCAL A$(10,20), then to reverse A$(X%) you will have to use USR(ADDR(MC$)+1,ADDR(A$())+21*(X%-1)) since we have to skip over X%-1 strings which each use 21 bytes of space. Look up how a string array is stored in part 1 if this is not clear to you.

As we have seen, the USR function only allows us to pass one integer value to the machine code program. How can we pass more information along, more than just one integer or string?

There are essentially two methods. One is to find some addresses in memory and simply POKE values there. For this you need to know exactly which parts of the memory are used and which parts are safe to change. The second, and better option, is to put all the information in an array, an simply pass along the address of that array.

Lets illustrate this with another program. Suppose we want to have a program that searches through a string variable, and replaces every occurrence of one character with another. The machine code program needs three pieces of information:
-The address of the string to be searched through.
-The character to be replaced.
-The character to replace it with.
These 3 things are put in an array, say A%().

A%(1)=ADDR(A$) A%(2)=ASC("%") A%(3)=63

The machine code program now only needs to know ADDR(A%()) to be able to look up all the information it needs.

The machine code could look like this:

18      XGDX       'Put the address of A%() in X
E605 LDAB 5,X '\ Put the replacement character A%(3) in memory
D741 STAB 41 '/ address 41. This makes looking it up easy later on.
A603 LDAA 3,X 'Put character A%(2) in register A.
A705 STAA 5,X '\ Swap values of A%(2) and A%(3). This way, the
E703 STAB 3,X '/ routine will undo the changes if it is called again.
'Remove these two lines if this is not wanted.
EE00 LDX 0,X 'Put address of string in X.
7F0042 CLR 42 'Clear address 42. Used to count number of changes made.
E600 LDAB 0,X 'Get current length of the string.
2711 BEQ end 'If empty string, then return.
lp:
08 INX 'Point to the next character in the string.
A100 CMPA 0,X 'Must it be changed?
2609 BNE skp 'If not the skip the next part.
36 PSHA 'Temporarily save value of A (=chr to search for)
9641 LDAA 41 '\Get replacement character and store it in
A700 STAA 0,X '/the string.
7C0042 INC 42 'Increase the counter.
32 PULA 'Retrieve value of A.
skp:
5A DECB 'Decrease B, the number of character left to go.
26EF BNE lp 'Loop until none left.
end:
D642 LDAB 42 '\
4F CLRA ' >Put counter value in X
18 XGDX '/
39 RTS 'return this value to OPL.

Now lets use this in an OPL procedure.

CHANGE: LOCAL MC$(38),A%(3),B% LOCAL A$(20) MC$=CONV$:("18E605D741A603"+"A705E703"+"EE007F0042E600271108A100") MC$=MC$+CONV$:("2609369641A7007C0042325A26EFD6424F1839") A%(1)=ADDR(A$) A%(2)=%* A%(3)=%+ INPUT A$ DO AT 1,1 PRINT A$ B%=USR(ADDR(MC$)+1,ADDR(A%())) UNTIL KEY PRINT B% GET

This program will change every "*" by "+", then every "+" by "*" and repeat this till a key is pressed. The number of changes made, is then shown. Try some other values of A%(2) and A%(3). If A%(2) and A%(3) are the same ASCII code, then no changes are visible, but the program will still return how often that character occurs.
The two lines in the MC program that swap A%(2) and A%(3) have been separated in the list if hex code, so they can easily be removed if you wish. There are no relative jumps crossing those two instructions so no other parts of the code need to be changed if you do this.
To use this routine on an element of a string array we have to use the same trick as with the REVERSE routine.

These programs also show how you can pass any information from machine code back to OPL. The first program returned a string, by changing the value of a string variable directly. The second program changed the values of two integer variables. If an MC program needs to return a lot of information, just give it the address of a variable or array to store that information in.

In Part 3 of this series I will cover... A complete game using machine code!