In the MC Tutor Part One I briefly mentioned several places where you can store machine code. I will discuss them in more detail here.
The places I'll consider are storing code
These methods fall into two categories: Temporary (A and B) and permanent (C, D, and E). Temporary storage means that the code is part of an OPL procedure, and that as soon as the procedure is finished the machine code will also be removed. Permanent storage means that once the code is installed it will stay there forever, or at least until your next battery change. With programs that run continuously in the background (like for example my KEYS program) one of the permanent options is clearly called for, while with most small programs the temporary kind is best.
This is the method I adopted in the MC Tutor pages, and it is fully explained there. It is relatively easy to install code in a variable. The only real disadvantage is that this can't really be used for large programs, because its installation takes a little long and because the procedure becomes too large.
This method has a few good advantages. The code is instantly available, because the code is automatically loaded into memory with the procedure itself. However, the difficulties are considerable. It is very hard to implement, especially on the organiser itself, because the object code of a procedure must be altered. Another disadvantage is that the code must be totally relocatable, so no absolute addressing can be used within the program. I will not go into full detail here, as it is too complex to explain completely.
Of the three permanent storage methods, this method is probably used most often. I will therefore try to explain in detail how it works. To put it simply, you move down the language stack so that an area of memory is freed up just above it. This area can then be used to store your code.
Suppose you need 100 bytes for your code. Now follow the following steps:
As you can see, this method is not too difficult. It is used by Bill Aitken in his book, however he neglected to mention the importance of step 2. If you do not exit OPL before filling the area, then you will overwrite the language stack, and most likely cause the Psion to crash.
As you can see, the method outlined above necessarily uses two separate procedures, one for step 1 and one for step 3. It would be neater to combine the whole installation into one procedure, and this is only possible if we can avoid the Psion crashing when we overwrite the language stack. This is indeed possible but very tricky, and some knowledge of the language stack is necessary. Using the three step method is quite sufficient, but for those who are interested I'll outline the intricacies of a single installation procedure below.
When a procedure is run, the organiser places the return address on the language stack. This is followed by space reserved for all the variables that the procedure uses, and then the object code of the procedure is loaded on the stack as well.
Generally the installation procedure will use a long string containing the machine code in hex or some other form. This means that if you want to do the installation in one procedure, poking the machine code in place will overwrite the return address, and some of the variable space. It will not usually reach the object code of the program.
Space for the variables is reserved on the stack in the same order as they are declared in the local/global commands. By declaring the string containing the code first, you ensure that this string is the only variable affected. You will also have to poke the code starting from the end of the string, otherwise part the strings contents are disturbed before it has been copied. Finally, you must end the installation procedure with the STOP command, so that the Psion doesn't try to find its original return address from the altered stack area.
This method is relatively easy, but unfortunately it only works on the LA and LZ. The CM and standard XP models don't have the low RAM area ($400-$1FFF). This area is used by devices like the comms link to store their programs.
To grab some of this memory, you must first unboot the devices to clear them from memory. Then you can increase the two system variables at $2329 and $232B by the required amount. Afterwards you might as well reboot the devices again. The following OPL sequence illustrates how all this is done.
LOCAL A%(2),L% L%=100 :rem L% is the number of bytes to reserve. A%(1)=$3F18 :REM SWI 18 unboot devices A%(2)=$3900 :REM RTS USR(ADDR(A%()),0) POKEW $2329,$400+L% POKEW $232B,$400+L% A%(1)=$3F17 :REM SWI 17 boot devices USR(ADDR(A%()),0)
The area from $400 to $400+L%-1 is now free to use immediately.
This method is somewhat similar to the last one, in that it grabs some memory that is usually used for the devices. It works on all organiser II models. As before, the devices are first unbooted. Then the permanent allocator cell is grown by the number of bytes you need. All the other allocator cells are automatically moved up to accommodate this. Then the base of the permanent allocator cell is increased, so that the reserved space no longer is a part of it. Now the devices are booted again.
The following OPL sequence will do all this for you.
LOCAL A%(8),L% L%=100 :rem L% is the number of bytes to reserve. A%(1)=$3F18 :REM SWI 18 unboot devices A%(2)=$CE20 :REM LDX %2000 A%(3)=$004F :REM CLRA A%(4)=$5FDD :REM CLRB A%(5)=$41CC :REM STD 41 A%(6)=L% :REM LDD %length A%(7)=$3F02 :REM SWI 02 grow cell A%(8)=$3900 :REM RTS USR(ADDR(A%()),0) POKEW $2000,PEEKW($2000)+L% A%(1)=$3F17 :REM SWI 17 boot devices A%(2)=$3900 :REM RTS USR(ADDR(A%()),0)
The area from PEEKW($2000)-L% to PEEKW($2000)-1 is now free to use immediately.