Example 5
A mouse test

 

For this example, we are going to implement a simple program...

10 MODE 12
  20 OFF
  30 MOUSE ON
  40
  50 REPEAT
  60   MOUSE X%, Y%, Z%
  70   PRINT TAB(0,0);X%, y%, Z%
  80 UNTIL Z% = 7
  90 MOUSE OFF
 100 END
But why stop there? Let's have a little fun. Include the following...
  75 IF Z% = 4 OR Z% = 1 THEN PROCplot

 110 :
 120 DEFPROCplot
 130 IF Z% = 4 THEN colour = 3 ELSE colour = 0
 140 GCOL colour
 150 POINT X%, Y%
 160 ENDPROC
Now, holding down SELECT will draw yellow dots, and ADJUST will draw black dots (a sort of primitive 'erase').

 

This is a DDE example. This code is for objasm, but can be easily altered for those assemblers that don't accept compatible input...

Okay, no BASIC, so you can forget about the DIMs and the OPT and the FOR NEXT loop.
Ready?

 

 

; mousetest
;
; Version 0.01  8th July 2000
; by Richard Murray
;
; Downloaded from http://www.heyrick.co.uk/assembler/
;
Always write a header to tell you what is going on. Some people include version changes in the header but I prefer a separate file. Personal choice really, if I had 2Mb of comments, the final program wouldn't be affected - but I might need a pretty beefy computer to assemble it all!

a1 RN 0
a2 RN 1
a3 RN 2
a4 RN 3
v6 RN 9
sp RN 13
lr RN 14
pc RN 15
Definitions for the registers that we shall be using.
a1 to a4 (R0-R3) are general purpose registers.
v6 (R9) is a pointer to the storage area.
sp, lr, and pc (R13, R14 and R15) have the expected usage.

            AREA    |asmcode|, CODE, READONLY

            ENTRY
Defines the area, and sets this as the program entry point.
Note that this is, in fact, an error. The code area is defined as READONLY but later on we write to it (for the storage area and the string). However, it seems the system is pretty relaxed about things like this - however it is useful to keep an eye out for such things. Don't just stick in a standard area definition without thinking...like I just did.
(I shall consider myself bollocked, then, shall I? <grin>)

            ; Store registers that must be preserved
            MOV     sp, a2
            STMFD   sp!, {v6, lr}
The initial value of the stack pointer is passed to the application in R1. Actually, this is the RAM limit for the application (refer to OS_GetEnv), but it works for a fully descending stack such as that which is used by convention on RISC OS.
Remember, you are 'real' assembler programmer when you move away from BASIC. You cannot just assume the the stack pointer actually points to anything.
This, incidentally, applies for pure assembler code written in BASIC but run in a stand-alone fashion - such as the compressed image loader.

By now, you should be able to follow assembler without so many descriptions, so here goes...

            ; Set v6 to point to variable storage
            ADR     v6, storage

            ; Select MODE 12   (VDU 22, 12)
            SWI     256 + 22
            SWI     256 + 12

            ; OFF
            SWI     &36             ; OS_RemoveCursors

            ; MOUSE ON
            MOV     a1, #106        ; Set mouse state
            MOV     a2, #1          ; 1 = on
            SWI     &06             ; OS_Byte

            ; REPEAT
repeat

            ; MOUSE x%, y%, z%
            SWI     &1C             ; OS_Mouse
            STMIA   v6, {a1-a3}     ; Store X%, Y%, Z%

            ; PRINT TAB(0,0);
            SWI     256 + 31        ; VDU 31, 0, 0
            SWI     256
            SWI     256

            ; PRINT x%, y%, z%
            LDR     a1, [v6, #0]    ; Retrieve X%
            BL      print_integer

            LDR     a1, [v6, #4]    ; Retrieve Y%
            BL      print_integer

            LDR     a1, [v6, #8]    ; Retrieve Z%
            BL      print_integer

            ; Something extra...
            LDR     a1, [v6, #8]    ; Retrieve Z%
            CMP     a1, #4          ; Is it 4? [SELECT]
            CMPNE   a1, #1          ; If not, is it 1? [ADJUST]
            BLEQ    plot            ; If either, then go to plot

            ; UNTIL z% = 7    (all buttons pressed)
            LDR     a1, [v6, #8]    ; Retrieve Z%
            CMP     a1, #7          ; Is it 7? [SELECT, MENU, ADJUST]
            BLNE    check_escape    ; If not, check for Esc press
            BNE     repeat          ; If not, repeat

            ; MOUSE OFF
            MOV     a1, #106        ; Set mouse state
            MOV     a2, #0          ; 0 = off
            SWI     &06             ; OS_Byte

            ; END
            SWI     &37             ; OS_RestoreCursors
            LDMFD   sp!, {v6, pc} 
            SWI     &11             ; OS_Exit (only, we shouldn't come to here...)
Of note, in this lot, are:

storage
            DCD     0
            DCD     0
            DCD     0

string
            DCD     0
            DCD     0
Storage. Roughly in the middle, so it doesn't get too far for an ADR instruction to cope with. I'm not sure if objasm automatically does long ADRs, but it is something to be aware of as other assemblers may require an ADRL instruction instead, if they do it at all.

This next routine prints an integer in much the same way that BASIC does when you tell it to PRINT X%.

print_integer
            ADR     a2, string      ; Get address of buffer
            MOV     a3, #8          ; Length of buffer
            SWI     &28             ; OS_BinaryToDecimal
            ; Integer has been converted to a string, a3 gives size.

            RSBS    a4, a3, #10     ; How many spaces left?
            BEQ     no_spaces

            ; print leading spaces
            MOV     a1, #32         ; ASCII 32 = space
spaces_loop
            SWI     &00             ; OS_WriteC
            SUBS    a4, a4, #1      ; Print until none left
            BNE     spaces_loop

no_spaces
            ADR     a2, string
            ; 
print_loop
            LDRB    a1, [a2], #1
            SWI     &00             ; OS_WriteC
            SUBS    a3, a3, #1
            BNE     print_loop
            MOV     pc, lr
This code might seem odd, why not simply OS_Write0?
The string conversion SWI doesn't terminate the string, it instead gives a length count. You could OS_WriteN with a count, or you could do the above...

Because we aren't in BASIC any more, we can't rely on an Escape press having any effect. While it is inefficient to check the escape state in every loop, it isn't that much faster to maintain a loop count and do it every X times round. So, in the spirit of keeping things simple...

check_escape
            SWI     &2C             ; OS_ReadEscapeState
            BCS     escape_code     ; If Carry (Esc pressed), abort.
            MOV     pc, lr

errblock
            DCD     17
            =       "Escape", 0
            ALIGN

escape_code
            MOV     a1, #126        ; Ack. Escape condition
            SWI     &06             ; OS_Byte
            SWI     &37             ; OS_RestoreCursors
            ADR     a1, errblock
            SWI     &2B             ; OS_GenerateError
Our way out is through OS_GenerateError. That causes the stored entry point to be restored (this is done by RISC OS, and used for things like when OS_Exit is called).

Next comes the plot routine.

plot
            MOVEQ   a2, #256        ; Set colour to be black
            CMP     a1, #4          ; If SELECT pressed,
            ADDEQ   a2, a2, #3      ; Set colour to be yellow.

            SWI     256 + 18        ; Set graphics colour
            SWI     256
            MOV     a1, a2
            SWI     &00             ; OS_WriteC

            MOV     a1, #69         ; Point, absolute, foreground
            LDMIA   v6, {a2-a3}     ; Load X% and Y% for plot
            SWI     &45             ; OS_Plot
            MOV     pc, lr
And, guess what? That's it!
            END
To make a program:
  1. Load !ObjAsm
  2. Load !Link
  3. Ensure the 'o' subdirectory exists. Create it if it doesn't.
  4. Drag your source to ObjAsm. Switch off ThrowBack if you don't have a capable editor loaded.
  5. Click on Run.
  6. Drag the Data file that appears into the 'o' subdirectory.
  7. Now, get that Data file and drag it to Link.
  8. Click on Run.
  9. Save the Absolute file that appears.
This can be automated partially with an Obey file, or automated more using a MakeFile. However, this isn't so important at this time - when you have an application with twenty odd sources in various languages - that is when you really need a MakeFile!

 

Download example: mousetest.txt


Return to assembler index
Copyright © 2004 Richard Murray