|
Example 7
|
|
But when you are using a machine with no indicator, particularly a machine slower than you are used to, you start to get worried when nothing appears to be happening.
So for the purposes of this tutorial, we shall create a blinking LED light.
This code is not 32bit safe.
The 'documented' way to return from vector routines requires flags to be restored. I do not wish
to make this software 32bit if it means it may fail on an older version of RISC OS.
I'll leave this up to you. I have marked all of the trouble-spots in red
so you can find them easily.
By the way, the "Technical information on 26/320bit RISC OS binary interfaces" (v0.2) states:
...only joking. The sane people among us would see that the least used keyboard LED is Scroll Lock. Why can't we just use that?
Why not indeed.
FileV, ArgsV, BGetV, BPutV,
GBPBV, and FindV.
Firstly, we hook some code up to all of the vectors. It can be the exact same code. Dump registers that must be preserved, then switch on the LED, restore registers, and return without claiming the vector.
So our LED is on. Good-oh. What we NOW need is a way to turn it off. We cannot guarantee any specific number of calls to the LED switcher oner, so we'll get more complicated.
We shall arrange for a callback to kick in after 20cs to turn off the LED.
When the LED is turned on, the callback will be set up. If the callback is ALREADY set up,
it will have been unset. When the callback is called, it will be unset.
So it goes something like:
Vector handler:
Preserve registers
Is callback set up?
Yes? Remove it.
Switch on Scroll Lock LED
Set up callback handler
Restore registers
Callback handler
Switch off Scroll Lock LED
Remove callback
EQUD 0 ; Start-up code
EQUD initialise ; Initialisation
EQUD finalise ; Finalisation
EQUD 0 ; Service call handler
EQUD module_title ; Module title
EQUD module_help ; Module help
EQUD 0 ; Help and command decoding table
EQUD 0 ; SWI chunk base number
EQUD 0 ; SWI handling code
EQUD 0 ; SWI decoding code
EQUD 0 ; SWI decoding code
.module_title
EQUS "DiscAccess"
EQUB 0
ALIGN
.module_help
EQUS "DiscAccess"+CHR$(9)+"0.01 (14 Oct 2000)"
EQUB 0
ALIGN
.initialise
STMFD R13!, {R14}
\ Attach vector handler to the six vectors that deal with file ops.
MOV R0, #&8 ; FileV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&9 ; ArgsV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&A ; BGetV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&B ; BPutV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&C ; GBPBV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&D ; FindV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
LDMFD R13!, {PC}
.finalise
STMFD R13!, {R14}
MOV R0, #&8 ; FileV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&9 ; ArgsV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&A ; BGetV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&B ; BPutV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&C ; GBPBV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&D ; FindV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
LDMFD R13!, {PC}
.vector_handler
\ IMPORTANT! WE ARE IN SVC MODE!
STMFD R13!, {R0 - R2, R14} ; Preserve registers
\ Our code will go here
LDMFD R13!, {R0 - R2, R14} ; Restore registers
MOVS PC, R14 ; Pass this one on
If you run this, you'll notice that nothing happens. This is good. This is what is supposed
to happen. Vector FileV (&08)
01863af0 00000100 DiscAccess
018287f0 00000000 Filer+Util
038310cc 01800204 FileSwitch
0380a5b8 00000000
Vector ArgsV (&09)
01863af0 00000100 DiscAccess
03832b98 01800204 FileSwitch
0380a5b8 00000000
Vector BGetV (&0a)
01863af0 00000100 DiscAccess
03831c04 01800204 FileSwitch
0380a5b8 00000000
Vector BPutV (&0b)
01863af0 00000100 DiscAccess
03831d94 01800204 FileSwitch
0380a5b8 00000000
Vector GBPBV (&0c)
01863af0 00000100 DiscAccess
0182882c 00000000 Filer+Util
03831f6c 01800204 FileSwitch
0380a5b8 00000000
Vector FindV (&0d)
01863af0 00000100 DiscAccess
038317bc 01800204 FileSwitch
0380a5b8 00000000
which clearly shows that we are hanging on to the file vectors.
Please notice two things. Firstly, we use "OS_AddToVector" so that we add ourselves
to the vector list, rather than claiming the vector (which could cause problems with other
software!).
Also note that the "MOVS PC,R14" is not 32 bit compliant,
however it is the official (documented) way to return from vector code when passing on.
\ Our code will go
here" to invert the status of the Scroll Lock key, whatever it is currently.
\ Read
MOV R0, #202 ; Update keyboard status, but using EOR
MOV R1, #0 ; mask 0 and AND mask 255, we can read
MOV R2, #255 ; the state...
SWI "OS_Byte" ; New value in R1
\ Invert
EOR R1, R1, #2 ; Bit 1 is Scroll Lock, invert it.
\ Write
MOV R2, #0 ; AND mask is 0, so EOR value is used.
SWI "OS_Byte"
\ Update keyboard LEDs
MOV R0, #118
SWI "OS_Byte"
Now assemble the module, and try loading something fairly complicated. Not necessarily large,
but something that does a bunch of file accessing, like a word processor or email client.
In case you are unsure, there should be a "MOV R0, #202" following the
"\ Write" marker. However this is unnecessary as OS_Byte preserves
R0, and it is not altered between then and when we need it again.
First, change:
\ Invert
EOR R1, R1, #2 ; Bit 1 is Scroll Lock, invert it.
to:
\ Switch on Scroll Lock
ORR R1, R1, #2 ; Bit 1 is Scroll Lock, set it.
Now after the "\ Update keyboard LEDs" code, add:
\ Set up timed callback to switch LED off again
MOV R0, #20 ; After 20 centiseconds
ADR R1, callback_handler
MOV R2, #0
SWI "OS_CallAfter"
Then at the bottom add:
.callback_handler
\ This isn't quite interrupt-level code, but getting close!
STMFD R13!, {R0 - R2, R14}
\ Unset the keyboard LED
\ Read
MOV R0, #202
MOV R1, #0
MOV R2, #255
SWI "OS_Byte"
\ Switch off Scroll Lock
AND R1, R1, #253 ; 253 preserves every bit EXCEPT bit 2.
\ Write
MOV R2, #0
SWI "OS_Byte"
\ Update LEDs
MOV R0, #118
SWI "OS_Byte"
\ Remove callback
ADR R0, callback_handler
MOV R1, #0
SWI "OS_RemoveTickerEvent"
\ Return
LDMFD R13!, {R0 - R2, PC}
Assemble and run this.Go to the part where the timed callback is set up, and try for a shorter delay. Drastically shorter, like 2cs.
All in an hour's work! :-)
Please be aware that this utility, logically, will add a delay to file operations. I have measured the delay to be something in the region of 28%. I tried some optimisations such as:
LDMFD R13!, {R0-R2, PC}^
rather than:
LDMFD R13!, {R0-R2, R14}
MOVS PC, R14
but it made a negligible difference.LDMFD R13!, { ... }^ is not 32-bit friendly.
You will need to set <DiscAcc$Dir>, or simply edit the code...
REM >SourceCode
REM
REM DiscAccess module source
REM
REM Version 0.01
REM
REM Assembler programming example 7
REM Downloaded from: http://www.heyrick.co.uk/assembler/
:
ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END
:
DIM code% 2048
:
FOR pass% = 4 TO 6 STEP 2
P%=0
O%=code%
[ OPT pass%
EQUD 0 ; Start-up code
EQUD initialise ; Initialisation
EQUD finalise ; Finalisation
EQUD 0 ; Service call handler
EQUD module_title ; Module title
EQUD module_help ; Module help
EQUD help_table ; Help and command decoding table
EQUD 0 ; SWI chunk base number
EQUD 0 ; SWI handling code
EQUD 0 ; SWI decoding code
EQUD 0 ; SWI decoding code
.module_title
EQUS "DiscAccess"
EQUB 0
ALIGN
.module_help
EQUS "DiscAccess"+CHR$(9)+"0.01 (14 Oct 2000)"
EQUB 0
ALIGN
.help_table
EQUS "DiscAccess" ; Keyword string
EQUB 0
ALIGN
EQUD 0 ; Pointer to code (there is no code!)
EQUD 0 ; Parameter information (no parameters)
EQUD 0 ; Pointer to syntax string
EQUD discaccess_help ; Pointer to help string
EQUD 0 ; End of command table
.discaccess_help
; Only put a linefeed where one is required, else put a trailing space.
; RISC OS will wrap the text as appropriate to fit the dimensions in use...
EQUS "DiscAccess is a simple little module that blinks your Scroll Lock "
EQUS "key when files are accessed."+CHR$13
EQUS "It is designed to take the place of a dedicated HD indicator on "
EQUS "machines that don't have such a thing (ie, the A3000); so you "
EQUS "probably won't need it on a RiscPC!"+CHR$13+CHR$13
EQUS "DiscAccess was written by Richard Murray as a tutorial for the "
EQUS CHR$34+"Teach yourself ARM assembler"+CHR$34+", which is freely "
EQUS "available at:"+CHR$13
EQUS CHR$160+CHR$160+"http://www.heyrick.co.uk/assembler/"+CHR$13
EQUS CHR$160+CHR$160+"(this is example seven!)"+CHR$13+CHR$13
EQUB 0
ALIGN
; The hard spaces (CHR$160) force a small indent.
.initialise
STMFD R13!, {R14}
\ Attach vector handler to the six vectors that deal with file ops.
MOV R0, #&8 ; FileV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&9 ; ArgsV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&A ; BGetV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&B ; BPutV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&C ; GBPBV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
MOV R0, #&D ; FindV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_AddToVector"
LDMFD R13!, {PC}
.finalise
STMFD R13!, {R14}
MOV R0, #&8 ; FileV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&9 ; ArgsV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&A ; BGetV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&B ; BPutV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&C ; GBPBV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
MOV R0, #&D ; FindV
ADR R1, vector_handler
MOV R2, #0
SWI "OS_Release"
LDMFD R13!, {PC}
.vector_handler
\ IMPORTANT! WE ARE IN SVC MODE!
STMFD R13!, {R0 - R2, R14} ; Preserve registers
\ Read
MOV R0, #202 ; Update keyboard status, but using EOR
MOV R1, #0 ; mask 0 and AND mask 255, we can read
MOV R2, #255 ; the state...
SWI "OS_Byte" ; New value in R1
\ Switch on Scroll Lock
ORR R1, R1, #2 ; Bit 1 is Scroll Lock, set it.
\ Write
MOV R2, #0 ; AND mask is 0, so EOR value is used.
SWI "OS_Byte"
\ Update keyboard LEDs
MOV R0, #118
SWI "OS_Byte"
\ Set up timed callback to switch LED off again
MOV R0, #2 ; After 2 centiseconds
ADR R1, callback_handler
MOV R2, #0
SWI "OS_CallAfter"
LDMFD R13!, {R0 - R2, R14} ; Restore registers
MOVS PC, R14 ; Pass this one on
.callback_handler
\ This isn't quite interrupt-level code, but getting close!
STMFD R13!, {R0 - R2, R14}
\ Unset the keyboard LED
\ Read
MOV R0, #202
MOV R1, #0
MOV R2, #255
SWI "OS_Byte"
\ Switch off Scroll Lock
AND R1, R1, #253 ; 253 preserves every bit EXCEPT bit 2.
\ Write
MOV R2, #0
SWI "OS_Byte"
\ Update LEDs
MOV R0, #118
SWI "OS_Byte"
\ Remove callback
ADR R0, callback_handler
MOV R1, #0
SWI "OS_RemoveTickerEvent"
\ Return
LDMFD R13!, {R0 - R2, PC}
.stuff_at_the_end
EQUB 10
EQUB 10
EQUS "DiscAccess module © 2000 Richard Murray"
EQUB 10
EQUS "http://www.heyrick.co.uk/assembler/ (example 7)"
EQUB 10
]
NEXT pass%
:
OSCLI("Save <DiscAcc$Dir>.DiscAccess "+STR$~code%+" +"+STR$~P%)
OSCLI("SetType <DiscAcc$Dir>.DiscAccess FFA")
:
END
Now that it has been a good year since it was first posted, I shall here 'revisit' the project and detail some of the optimisations made.
It was considered to bump the callback delay up to ten centiseconds (instead of two) so the keyboard got less of a clobbering. In fact, given the new code, there should be a lot less data passing to the keyboard now. The LED will no longer flicker like crazy on a debatch (like the harddisc LED does), but instead it'll 'pulse' several times a second.
Don't just take my word for it:
Little BASIC program to block-read a news file of 5,163,098 bytes.
4096 byte blocks 1024 byte block
No DiscAccess 3.93 seconds 7.41 seconds
DiscAccess v0.01 4.85 seconds 11.31 seconds
DiscAccess v0.10 3.99 seconds 7.53 seconds
You will need to set <DiscAcc$Dir>, or simply edit the code...
REM >SourceCode
REM
REM DiscAccess module source [revisited]
REM
REM Version 0.10 28th February 2002
REM
REM Extended from assembler programming example 7
REM Downloaded from: http://www.heyrick.co.uk/assembler/
REM
:
ON ERROR PRINT REPORT$+" at "+STR$(ERL/10) : END
:
DIM code% 2048
:
FOR pass% = 4 TO 6 STEP 2
P%=0
O%=code%
[ OPT pass%
EQUD 0 ; Start-up code
EQUD initialise ; Initialisation
EQUD finalise ; Finalisation
EQUD 0 ; Service call handler
EQUD module_title ; Module title
EQUD module_help ; Module help
EQUD help_table ; Help and command decoding table
EQUD 0 ; SWI chunk base number
EQUD 0 ; SWI handling code
EQUD 0 ; SWI decoding code
EQUD 0 ; SWI decoding code
.module_title
EQUS "DiscAccess"
EQUB 0
ALIGN
.module_help
EQUS "DiscAccess"+CHR$(9)+"0.10 (28 Feb 2002) [http://www.heyrick.co.uk/assembler/]"
EQUB 0
ALIGN
.help_table
EQUS "DiscAccess" ; Keyword string
EQUB 0
ALIGN
EQUD 0 ; Pointer to code (there is no code!)
EQUD 0 ; Parameter information (no parameters)
EQUD 0 ; Pointer to syntax string
EQUD discaccess_help ; Pointer to help string
EQUD 0 ; End of command table
.initialise
STR R14, [R13, #-4]!
\ Set up the OS version
BL get_os_version
\ Use "MOV R2, R0" instead of the BL if you don't want hacky. <g>
\ Attach vector handler to the six vectors that deal with file ops.
\ Using OS_Claim now, instead of OS_AddToVector.
\ Constants...
ADR R1, vector_handler
ADR R0, release_code
STR R2, [R0]
MOV R0, #&8 ; FileV
SWI "OS_Claim"
BVS release_eight
MOV R0, #&9 ; ArgsV
SWI "OS_Claim"
BVS release_nine
MOV R0, #&A ; BGetV
SWI "OS_Claim"
BVS release_ten
MOV R0, #&B ; BPutV
SWI "OS_Claim"
BVS release_eleven
MOV R0, #&C ; GBPBV
SWI "OS_Claim"
BVS release_twelve
MOV R0, #&D ; FindV
SWI "OS_Claim"
BVS release_thirteen
LDMFD R13!, {PC}
.vector_handler
\ IMPORTANT! WE ARE IN SVC MODE!
\ MAKE THIS *FAST* - NO BRANCHES (AT ALL) FOR
\ FASTEST VARIANT...
STMFD R13!, {R0 - R2, R14} ; Preserve registers
\ Check LED state - if LED currently on, skip all this...
MOV R0, #1
LDR R0, led_state
CMP R0, #1
BEQ vector_handler_exit
CMP R12, #0
B update_old_method
; Set Scroll Lock bit
LDRB R0, [R12, #0]
ORR R0, R0, #2 ; Bit 1 is Scroll Lock, set it.
STRB R0, [R12, #0]
; Force keyboard status update
MOV R0, #118
SWI "OS_Byte"
; Flag LEDs are on
MOV R0, #1
STR R0, led_state
; Exit point here, so we can lose one branch.
LDMFD R13!, {R0 - R2, R14}
MOVS PC, R14 ; Got to keep V and C flags.
.update_old_method
\ Read
MOV R0, #202 ; Update keyboard status, but using EOR
MOV R1, #0 ; mask 0 and AND mask 255, we can read
MOV R2, #255 ; the state...
SWI "OS_Byte" ; New value in R1
\ Switch on Scroll Lock
ORR R1, R1, #2 ; Bit 1 is Scroll Lock, set it.
\ Write
MOV R2, #0 ; AND mask is 0, so EOR value is used.
SWI "OS_Byte"
\ Update keyboard LEDs
MOV R0, #118
SWI "OS_Byte"
\ Flag LEDs are on
MOV R0, #1
STR R0, led_state
\ Set up timed callback to switch LED off again
MOV R0, #10 ; After 10 centiseconds
ADR R1, callback_handler
MOV R2, #0
SWI "OS_CallAfter"
.vector_handler_exit
LDMFD R13!, {R0 - R2, R14} ; Restore registers
MOVS PC, R14 ; Pass this one on (preserving V and C)
.callback_handler
STMFD R13!, {R0 - R2, R14}
\ Unset the keyboard LED
\ Read
MOV R0, #202
MOV R1, #0
MOV R2, #255
SWI "OS_Byte"
\ Switch off Scroll Lock
AND R1, R1, #253 ; 253 preserves every bit EXCEPT bit 2.
\ Write
MOV R2, #0
SWI "OS_Byte"
\ Update LEDs
MOV R0, #118
SWI "OS_Byte"
\ Flag LEDs are off
MOV R0, #0
ADR R1, led_state
STR R0, [R1]
\ Remove callback
ADR R0, callback_handler
MOV R1, #0
SWI "OS_RemoveTickerEvent"
\ Return
LDMFD R13!, {R0 - R2, PC}
.finalise
STR R14, [R13, #-4]!
ADR R1, vector_handler
ADR R0, release_code
LDR R2, [R0]
MOV R0, #&8 ; FileV
ADR R1, vector_handler
SWI "OS_Release"
BVS frelease_failed
MOV R0, #&9 ; ArgsV
SWI "OS_Release"
BVS frelease_failed
MOV R0, #&A ; BGetV
SWI "OS_Release"
BVS frelease_failed
MOV R0, #&B ; BPutV
SWI "OS_Release"
BVS frelease_failed
MOV R0, #&C ; GBPBV
SWI "OS_Release"
BVS frelease_failed
MOV R0, #&D ; FindV
SWI "OS_Release"
BVS frelease_failed
LDR PC, [R13], #4
.get_os_version
STR R14, [R13, #-4]!
MOV R0, #129
MOV R1, #0
MOV R2, #255
SWI "OS_Byte"
MOVVS R0, #0 ; Can this OS_Byte fail?
CMP R0, #&A4
B status_address
CMP R0, #&A5
B status_address
CMP R0, #&A6
B status_address
CMP R0, #&A7
B status_address
CMP R0, #&A8
B status_address ; And RISC OS 4.00 IIRC
MOV R2, #0 ; OS version not known
LDR PC, [R13], #4
.status_address
MOV R2, #&900 ; Keyboard status byte is &9C4, byte 0.
ADD R2, R2, #&C4
LDR PC, [R13], #4
.led_state
EQUD 0
.release_code
EQUD 0
\ Error release
.release_thirteen
MOV R0, #&C
SWI "OS_Release"
BVS release_failed
.release_twelve
MOV R0, #&B
SWI "OS_Release"
BVS release_failed
.release_eleven
MOV R0, #&A
SWI "OS_Release"
BVS release_failed
.release_ten
MOV R0, #&9
SWI "OS_Release"
BVS release_failed
.release_nine
MOV R0, #&8
SWI "OS_Release"
BVS release_failed
.release_eight
ADR R0, claim_fail_message
LDR R14, [R13], #4
ORRS PC, R14, #1<<28
.release_failed
ADR R0, release_fail_message
LDR R14, [R13], #4
ORRS PC, R14, #1<<28
.frelease_failed
ADR R0, frelease_fail_message
LDR R14, [R13], #4
ORRS PC, R14, #1<<28
.claim_fail_message
EQUD 17
EQUS "Unable to claim required vectors, cannot start." + CHR$(0)
ALIGN
.release_fail_message
EQUD 17
EQUS "Unable to claim required vectors, and unable to release those claimed. Uh-oh!"
EQUB 0
ALIGN
.frelease_fail_message
EQUD 17
EQUS "Unable to release claimed vectors. Uh-oh!"
EQUB 0
ALIGN
.discaccess_help
; Only put a linefeed where one is required, else put a trailing space.
; RISC OS will wrap the text as appropriate to fit the screen dimensions in use...
EQUS "DiscAccess is a simple little module that blinks your Scroll Lock key when "
EQUS "files are accessed."+CHR$13
EQUS "It is designed to take the place of a dedicated HD indicator on machines that "
EQUS "don't have such a thing (ie, the A3000); so you probably won't need it on a "
EQUS "RiscPC!"+CHR$13+CHR$13
EQUS "DiscAccess was written by Richard Murray as a tutorial for the "+CHR$34+"Teach "
EQUS "yourself ARM assembler"+CHR$34+", which is freely available at:"+CHR$13
EQUS CHR$160+CHR$160+"http://www.heyrick.co.uk/assembler/"+CHR$13
EQUS CHR$160+CHR$160+"(this is the extended version of example seven!)"+CHR$13+CHR$13
EQUB 0
ALIGN
; The hard spaces (CHR$160) force a small indent.
.stuff_at_the_end
EQUB 10
EQUB 10
EQUS "DiscAccess module © 2002 Richard Murray"
EQUB 10
EQUS "http://www.heyrick.co.uk/assembler/ (example 7)"
EQUB 10
]
NEXT pass%
:
OSCLI("Save <DiscAcc$Dir>.DiscAccess "+STR$~code%+" +"+STR$~P%)
OSCLI("SetType <DiscAcc$Dir>.DiscAccess FFA")
:
END
You might exhibit behaviour when no harddiscs (or floppies) are being accessed. This is because
DiscAccess works at a high level so picks up on filing system activity (rather than disc
activity). To see what I mean, open a TaskWindow and type:
Filer_OpenDir Resources:$.Resourcesand watch the Scroll Lock flicker as information is read from a pseudo-filing system containing a bunch of stuff held in ROM. :-)