It is the 1729th of March 2020 (aka the 23rd of November 2024)
You are 3.149.250.65,
pleased to meet you!
mailto:blog-at-heyrick-dot-eu
BeagLEDs
I have written a short program called "BeagLEDs" which is a RISC OS module for the Beagleboard (any type). It will flash the left user LED on and off second by second, and the right user LED according to filesystem activity.
Note, however, that as we hook into filesystem activity at a fairly high level, it will blink for Ramdisc and ResourceFS accesses just as for real storage.
The blinking is damped, so the indicator will flick on for at least 15cs. This is to minimise lag due to our activities.
On the RISC OS Open forum, I said I'd give a run-down of how the module operates, so here it is.
Here's the pre-amble. Basically, after a version comment, I load in four macro files:
The first describes data storage in terms that I'm used to from BASIC (EQUD as opposed to DCD, or odd punctuation (=, %, etc)). One that is useful that you'll see here is EQUSZA which is a string, zero terminated, and aligned.
Next, generic SWI definitions, tweaked slightly so it has OS_Hardware...
Then, a bunch of macros supposed to be for pushing and pulling registers, exiting functions, and so on. I don't tend to use these as I'm used to STMFD R13!, {blah}, however I do use the SetV macro which is a little bit of code to set the V flag.
Debug was never used in the end, this ought to be removed in the next build.
The important macro definitions are given at the end, you can collect them all into one file and tweak the stuff below. The SWInames file should be present with your development environment. If you don't have this, you'll need to define the following SWIs: XOS_ReadSysInfo, XOS_Memory, XOS_Hardware, XOS_Module, XOS_Claim, and XOS_Release.
; BeagLEDs
;
; by Rick Murray
; Version: 0.01
; Date : Monday, 24th May 2012
; Started: Monday, 21st May 2012
;
; Our inclusions
GET ^.h.equx ; EQUB, EQUD, EQUSZ, EQUSZA...
GET ^.h.SWINames ; SWI definitions
GET ^.h.pushpull ; Push, Pull, PushLR, PullLR, Return, PullRet...
GET ^.h.debug ; DebugMsg (only used during actual debugging)
Now we define the module name, version, date, and copyright string. Doing it here saves having stuff scattered around the source.
Now follow a number of definitions. These are simply names, so using "TickerV" in the code will result in the assembler swapping in "&1C"; much like #define in C.
; File vector numbers
FileV * &8
ArgsV * &9
BGetV * &A
BPutV * &B
GBPBV * &C
FindV * &D
TickerV * &1C
; LED mask - USERLED0 = GPIO #149; USERLED1 = GPIO #150.
USERLED0 * 1 << 21 ; User LED0 is for the ticker
USERLED1 * 1 << 22 ; User LED1 is for the fileops
TICKERLED * USERLED0 ; therefore...
FILEOPLED * USERLED1
; GPIO5 base address held in code, <GPIO5_BASE>.
; GPIO-specific register offsets (from GPIO base)
GPIO_OE * &34
GPIO_DATAOUT * &3C
GPIO_CLRDATAOUT * &90
GPIO_SETDATAOUT * &94
; Our status array offsets
LED_TICKERCOUNT * 0 ; Counts down from 100 for timing of blinking LED
LED_FILEOPCOUNT * 4 ; Counts down from 15 for timing of file activity LED
LEDSTATE_TICKER * 8 ; Is '1' or '0' for state of blinking LED
LEDSTATE_FILEOP * 12 ; Is '1' or '0' for state of file activity LED
GPIO_ADDRESS * 16 ; The mapped-in address of our GPIO
It is worth noting that the OS vectors can be looked up in the RISC OS PRM1; the two LEDs are documented in the Beagle technical guide; the GPIO stuff is from the DM3730 TRM (should be compatible with the earlier DM3530); and the array is ours.
Now for the assembly preamble. We define an area, and mark it as code. Then we tell the assembler where the code entry point is (though, in the context of a RISC OS module, this is defined by convention). We don't mark it as 32 bit as we pass this via the command line in the Makefile. Otherwise, add A32bit to the AREA definition.
; ===========
; Here we go!
; ===========
AREA |BeagLEDs$Code|, CODE
ENTRY
Now we define a standard RISC OS module. The start of the file is a header that contains information words and addresses. Refer to the PRMs if you need further details.
entrypoint
; Standard RISC OS module header
EQUD 0 ; no Start code
EQUD (initialise - entrypoint) ; Initialise code
EQUD (finalise - entrypoint) ; Finalise code
EQUD 0 ; no Service call
EQUD (titlestring - entrypoint) ; Module title string
EQUD (helpstring - entrypoint) ; Module help string
EQUD 0 ; no help/command
EQUD 0 ; no SWI chunk
EQUD 0 ; no SWI handler code
EQUD 0 ; no SWI decoding table
EQUD 0 ; no SWI decoding code
EQUD 0 ; no Messages file
EQUD (thirtytwo - entrypoint) ; 32bit flag word
titlestring
= CODENAME, 0
ALIGN
helpstring
= CODENAME, 9, CODEVERS, " (", CODEDATE, ") ", CODECOPY
ALIGN
thirtytwo
; Bit zero set indicates this is a 32bit-compatible module...
EQUD 1
A word of data holding the address of the GPIO5 register.
GPIO5_BASE
EQUD &49056000 ; location of GPIO5 registers
The module initialisation function. RISC OS on a Beagleboard is a HAL build of RISC OS (HAL means Hardware Abstraction Layer; as traditional RISC OS used to talk directly to the hardware and was, thus, rather tied to the Acorn chipset). So we look for a HAL version of RISC OS, then knowing we're on something that'll work, we can then ask the HAL directly if we are on a Beagleboard.
; =====================
; MODULE INITIALISATION
; =====================
initialise
; We're in SVC mode, so preserve return address
STR R14, [R13, #-4]!
; First job - check what platform we're running on.
; We need a Platform class of 5 (HAL) at least.
MOV R0, #8
SWI XOS_ReadSysInfo
BVS not_a_beagle ; code 8 failed? give up
CMP R0, #5
BNE not_a_beagle ; not HAL version? give up
; Now ask the OS (via HAL) to confirm it is a Beagle.
MOV R0, #1024 ; HALDeviceType_Comms
ADD R0, R0, #3 ; HALDeviceComms_GPIO
MOV R1, #0 ; is our first call (to enum)
MOV R8, #4
SWI XOS_Hardware ; OS_Hardware 4 - enumerate devices
BVS not_a_beagle ; fail? abort!
CMP R1, #-1 ; no match?
BEQ not_a_beagle ; abort if no match
LDR R0, [R2, #64] ; boardtype is at offset +64
CMP R0, #0 ; type 0 is Beagle
BNE not_a_beagle
The board type is at offset +64, and the board revision is at offset +68. As the Beagles have LED0 and LED1 on the same GPIO, we don't need to look to the revision. Any old Beagle will do.
Here are the currently defined board types and revisions:
Board
Revision
Type
Notes
0
0
Beagleboard rev A or B
User LED0 on GPIO 149 User LED1 on GPIO 150
1
Beagleboard rev C1, C2, or C3
2
Beagleboard rev C4
3
Beagleboard xM rev A
4
Beagleboard xM rev B
5
Beagleboard xM rev C
1
n/a
DevKit8000
User LED0 on GPIO 186 User LED1 on GPIO 163
2
0
IGEPv2 rev B or C
User LED0 on GPIO 26 (red, green=27) User LED1 on GPIO 28 (red, green=complicated)
1
IGEPv2 rev C (not compatible with B)
3
0
RaspberryPi model A
Moot point, RasberryPi
has no user LEDs onboard...
1
RaspberryPi model B
Now we allocate space in the module area for our array (20 bytes) unless we are being re-entered due to *RMTidy or *RMReInit in which case we'll already have a memory block.
; Check we have no allocated space from RMTidy op / previous init
LDR R2, [R12]
TEQ R2, #0
BNE skip_mem_allocate
; Okay, so claim a cosy little spot in the RMA
MOV R0, #6
MOV R3, #20 ; 20 bytes (count x2, tickstate, filestate,
SWI XOS_Module ; and GPIO mapped address)
LDRVS PC, [R13], #4 ; bomb out if V set
; Okay, remember our workspace pointer
STR R2, [R12] ; R12 points to private word; store memblk
MOV R12, R2 ; R12 now points directly to our memory
After the memory is allocated, we store the address to the word pointed to my R12. This word is our "private word", the address of which is passed to us in R12 on every module entry.
As R12 is the convention, we copy the address to R12...
The next task is to map GPIO5 into the logical memory map. We map it in permanently, to save on having to map/unmap each time we access the LEDs.
skip_mem_allocate
; Now map in GPIO5 (permanently)
LDR R0, =(1 << 17) ; Page access privilege Read/Write
ADD R0, R0, #13 ; OS_Memory 13, Map in I/O memory
LDR R1, GPIO5_BASE ; Physical address
MOV R2, #256 ; Map in 256 bytes
SWI XOS_Memory
BVS cant_map_memory ; die if it didn't work
STR R3, [R12, #GPIO_ADDRESS] ; remember logical address
As the LEDs are "on" when RISC OS starts, it might seem logical to assume they are set as outputs, but we force this just to be certain. Note the logic for handling the AND NOT, and that we keep a copy of the LED bitmask in R3 for later.
; Ensure the LEDs are "outputs"
MOV R0, R3 ; copy address
ADD R0, R0, #GPIO_OE ; add in offset to Output Enable register
MOV R3, #USERLED0 ; Make a bitmask of LEDs
ADD R3, R3, #USERLED1 ; (in R3, we'll need it later)
MVN R1, R3 ; invert bitmask (into R1)
LDR R2, [R0] ; read in word
AND R2, R2, R1 ; value = value AND NOT LED_bits
STR R2, [R0] ; write word back
Using the copy of R3 we retained (not the inverted one), we write this to the Clear Data Out register to force both LEDs off.
; Now force LEDs OFF
LDR R0, [R12, #GPIO_ADDRESS] ; load address
ADD R0, R0, #GPIO_CLRDATAOUT ; add in offset to Clear Data Output register
STR R3, [R0] ; write bitmask, turns off LEDs
Now the code to hook into the filesystem vectors. The code looks a little odd because if, for some reason, a vector claim fails, we must release claims made until that point (or it'll all go Bang!).
Our init is almost done. The final task is to set up the memory block. As the LED ticker counts down from 100, we initialise this to be 100. The other values initialise to be zero (fileop counter is zero, both LEDs flagged as off).
Here follows error message reporting, and tidying up from the vector claims.
; =====================================================
; ERROR THROWS FOR INITIALISE FAILS & OS_CLAIM RELEASES
; =====================================================
not_a_beagle
; set up an error block to flag if we're NOT running on a Beagle.
ADR R0, not_a_beagle_error
SetV
LDR PC, [R13], #4 ; assumes LR previously stacked
cant_map_memory
; set up an error block to flag if OS_Memory for GPIO failed.
; RISC OS should release our memory claim for us.
ADR R0, cant_map_memory_error
SetV
LDR PC, [R13], #4 ; assumes LR previously stacked
; The releases fall through backwards to error report. If a release
; fails, it is not trapped or handled (because the module is about
; to die on startup, not a lot we can do except clap twice and pray).
release_six
MOV R0, #FindV
SWI XOS_Release
release_five
MOV R0, #GBPBV
SWI XOS_Release
release_four
MOV R0, #BPutV
SWI XOS_Release
release_three
MOV R0, #BGetV
SWI XOS_Release
release_two
MOV R0, #ArgsV
SWI XOS_Release
release_one
MOV R0, #FileV
SWI XOS_Release
release_none
cant_claim_vectors
; set up an error block to flag if any of the OS_Claims failed.
; RISC OS should release our memory claim for us, and we don't
; need to release the GPIO mapping (as it is permanent) and we
; have been called AFTER previously OS_Claimed vectors (if any)
; have already been released.
ADR R0, cant_claim_vectors_error
SetV
LDR PC, [R13], #4 ; assumes LR previously stacked
; Error block messages for the above
;
; Is the error number (currently "123") relevant? If the module can't
; start, it can't start, and there isn't a hell of a lot the end user
; can do to rectify things, so nothing really needs to know an actual
; error number, just that an error occurred...
not_a_beagle_error
EQUD 123
EQUSZA "This module only works on a Beagleboard (original or xM)."
cant_map_memory_error
EQUD 123
EQUSZA "Unable to map GPIO memory."
cant_claim_vectors_error
EQUD 123
EQUSZA "Unable to claim necessary vectors."
This is the module finalisation routine. In a nutshell, we release the ticker, then the filesystem vectors, ensure both LEDs are off, then finally release our memory block.
The LDR of R12 from R12 is because R12 on entry is our private workspace word. So we load this into itself to turn it into a pointer to our array.
; ============
; FINALISATION
; ============
finalise
; Preserve return
STR R14, [R13, #-4]!
; Determine our true workspace address
LDR R12, [R12]
; Release ticker vector
MOV R0, #TickerV
ADR R1, ticker_handler
MOV R2, R12
SWI XOS_Release
; Release all FileOp vectors
ADR R1, fileop_handler
MOV R0, #FileV
SWI XOS_Release
MOV R0, #ArgsV
SWI XOS_Release
MOV R0, #BGetV
SWI XOS_Release
MOV R0, #BPutV
SWI XOS_Release
MOV R0, #GBPBV
SWI XOS_Release
MOV R0, #FindV
SWI XOS_Release
; Force the LEDs off
MOV R1, #USERLED0 ; Make a bitmask of LEDs
ADD R1, R1, #USERLED1
LDR R0, [R12, #GPIO_ADDRESS] ; load address
ADD R0, R0, #GPIO_CLRDATAOUT ; offset to Clear Data Output register
STR R1, [R0] ; write bitmask, turns off LEDs
; And now release the memory block
MOV R0, #7
MOV R2, R12
SWI XOS_Module
; That's it. Goodbye and thank you for watching.
LDR PC, [R13], #4
The commenting will guide you through the file op vector code. All you must remember at every point is all registers MUST be preserved (except our private R12) and we must be AS FAST AS POSSIBLE.
Note, also, that unlike module code, we specified for R12 to point directly to our array, so we don't need to do the LDR R12 from R12 thing.
; =====================
; FILEOP VECTOR HANDLER
; =====================
fileop_handler
; On entry, R12 is a pointer to our array, not our private workspace word.
;
; We are called upon FileV, ArgsV, BGetV, BPutV, GPBPV, and FindV. It is
; important to stress how IMPORTANT it is that we execute QUICKLY.
;
; Fastest case: LED is already on. Through in seven instructions.
;
; Otherwise, LED must be switched on. Through in fifteen instructions
; (two of which are conditionally not executed).
; Is the LED currently on?
; If it is, we can handle this and be out in seven instructions
STR R0, [R13, #-4]! ; preserve R0
MOV R0, #15 ; Set/reset ticker value
STR R0, [R12, #LED_FILEOPCOUNT]
LDR R0, [R12, #LEDSTATE_FILEOP] ; is LED already on?
CMP R0, #0 ; is zero if off
LDRNE R0, [R13], #4 ; restore R0
MOVNE PC, R14 ; done, get outta here
; The set/reset ticker value above is NOT conditional as ALL
; passes through this vector either reset the counter, or set
; it for the first time. In both cases, we want it set to its
; initial value.
; This means we can assume it done for below.
; At this point, we can assume LED is off, so switch it on.
; This adds a further eight instructions. We corrupt R12
; (our private word passed to us on entry) so we don't need
; to lose time stacking and restoring another register (which
; also means we lose two memory accesses).
MOV R0, #1 ; flag LED is on (here, as
STR R0, [R12, #LEDSTATE_FILEOP] ; R12 corrupted below)
MOV R0, #FILEOPLED ; set up for turning on LED
LDR R12, [R12, #GPIO_ADDRESS] ; << this corrupts R12
ADD R12, R12, #GPIO_SETDATAOUT
STR R0, [R12] ; do it
LDR R0, [R13], #4 ; restore R0 (from above)
MOV PC, R14 ; done
And here is the code for handling the ticker vector. It is a little more complicated, as the two LEDs can be in a variety of states: Off (disactivated), Counting (counting down to turn on or off), Switching (at zero, switching on or off). The ticker LED can switch on and off, the fileop LED can only switch off.
Here is a diagram of the ticker vector handler:
Note that we are entered in IRQ mode so we cannot call SWIs easily (we don't need to, but if you expand this code, keep this in mind).
; ==========================
; CENTISECOND TICKER HANDLER
; ==========================
ticker_handler
; We're called a hundred times per second. We'll be in IRQ mode, so
; there's no option to call SWIs or such without a lot of faffing.
;
; Case table:
; FileOpLED Ticker Instrs CondNOP Branches
;
; Quickest Off Counting 8 1 0
; Middle Off Switching 19 4 0
; Slow Counting Counting 12 0 2
; Slower Switching Counting 19 1 2
; Slowest Switching Switching 30 5 2
;
; Therefore, this routine takes from 8 to 30 instructions to
; execute.
STMFD R13!, {R0-R1, R14} ; breathing room
; Check the FileOp LED
LDR R0, [R12, #LEDSTATE_FILEOP]
CMP R0, #0 ; is it zero (off?)
BLNE ticker_handler_fileop
; Check the ticker LED
LDR R0, [R12, #LED_TICKERCOUNT] ; load ticker count value
SUBS R0, R0, #1 ; -1 it
STRNE R0, [R12, #LED_TICKERCOUNT] ; if not zero, just write it back
LDMNEFD R13!, {R0-R1, PC} ; and then leave
; still here? ticker is zero - so first reset it
MOV R0, #99
STR R0, [R12, #LED_TICKERCOUNT]
; now look to see what to do with the LED, we toggle
LDR R0, [R12, #LEDSTATE_TICKER]
EORS R0, R0, #1 ; and update flags
STR R0, [R12, #LEDSTATE_TICKER]
LDR R1, [R12, #GPIO_ADDRESS]
ADDEQ R1, R1, #GPIO_CLRDATAOUT ; if zero, select CLR register
ADDNE R1, R1, #GPIO_SETDATAOUT ; else, select SET register
MOV R0, #TICKERLED ; which LED
STR R0, [R1] ; write it
LDMFD R13!, {R0-R1, PC} ; done
ticker_handler_fileop
LDR R0, [R12, #LED_FILEOPCOUNT] ; load ticker count value
SUBS R0, R0, #1 ; -1 it
STR R0, [R12, #LED_FILEOPCOUNT] ; write it back (even if zero)
MOVNE PC, R14 ; branch back if non-zero
; still here? ticker is zero - so turn off LED
MOV R0, #0
STR R0, [R12, #LEDSTATE_FILEOP] ; set flag for LED off
MOV R0, #FILEOPLED ; which LED
LDR R1, [R12, #GPIO_ADDRESS]
ADD R1, R1, #GPIO_CLRDATAOUT ; CLR register
STR R0, [R1] ; write it
MOV PC, R14
All that remains is to embed a short message to pad out the file to be exactly 1024 bytes. No termination or alignment, as this is neither executed nor read, it's just something stuck at the end.
EQUS "Thanks to joe for the Beagle, Jeffrey for "
EQUS "s-video, and RISC OS for continuing.:)"
END
There you have it. ☺
Here, presented as one lump, should be all the macro definitions you'll need. Load this instead of the four GETs at the top, it should work.
; This should be enough to put into one header file to
; build BeagLEDs directly...
; EQUD <value>
MACRO
EQUD $var
DCD $var
MEND
; EQUS "<value>"
; For 'EQUS "something", 13, 0'
; you will need use DCB directly.
MACRO
EQUS $var
DCB "$var"
MEND
; EQUSZA "<value>"
MACRO
EQUSZA $var
DCB "$var", 0
ALIGN
MEND
; SETV : Set oVerflow flag (on 26 or 32 bit)
MACRO
$label SetV ; DOES NOT TAKE A CONDITION
$label CMP R0, #1<<31
CMNVC R0, #1<<31
MEND
; minimal SWI defs
; this is taken from the h.SWINames file supplied with the
; Norcroft/Acorn/Castle DDE examples; with the addition
; of a definition for OS_Hardware.
Auto_Error_SWI_bit_number * 17
Auto_Error_SWI_bit * 1 :SHL: Auto_Error_SWI_bit_number
GBLS SWIClass
MACRO
AddSWI $SWIName,$value
[ "$value" = ""
$SWIClass._$SWIName # 1
|
$SWIClass._$SWIName * $value
]
X$SWIClass._$SWIName * $SWIClass._$SWIName + Auto_Error_SWI_bit
MEND
SWIClass SETS "OS"
^ &1E
AddSWI Module ; &1E
AddSWI Claim ; &1F
AddSWI Release ; &20
^ &58
AddSWI ReadSysInfo ; &58
^ &68
AddSWI Memory ; &68
^ &7A
AddSWI Hardware ; &7A
END
Finally, the Makefile...
# Project: BeagLEDs
# Toolflags:
.SUFFIXES: .s .o
objasmflags = -throwback -apcs 3/32bit -desktop -IC:,
linkflags = -rmf -output BeagLEDs
# A list of assembler files, referred by the resultant object file
s_files = o.BeagLEDs
BeagLEDs: $(s_files)
@echo Linking...
@link $(s_files) $(linkflags)
@echo Finished.
# A macro for building the assembler code
.s.o:; objasm $(objasmflags) -o $@ $<
# Dynamic dependencies:
My Beagle is "dead" again
Something of a misnomer. It isn't dead dead, it just isn't booting. Hooking up a serial terminal, I can see:
And that's about as far as it gets. I was running from an iomega Zip power supply, I also tried the +5V line from a PC PSU. The accepted advice is that a meatier power supply is required, but if a PC's lump doesn't have enough oomph!, what would?
Now the interesting thing is that this happened hot on the heels of a Linux kernel panic (some sort of corruption on a replacement microSD while trying to remake an image of the original test card). It might be coincidence, but I can start up RISC OS dozens of times without incident, yet immediately following two kernel panics, the Beagle appears to fail at startup. The first time, as described last week, the board just started up again. This time? It doesn't appear to want to. What the hell does Linux do after reporting a kernel panic?
Am I going to have to UPS the thing back to America? The cheapest UPS were willing to offer me was €168.32 - which is a joke. I must have entered something wrong somewhere.
Your comments:
Please note that while I check this page every so often, I am not able to control what users write; therefore I disclaim all liability for unpleasant and/or infringing and/or defamatory material. Undesired content will be removed as soon as it is noticed. By leaving a comment, you agree not to post material that is illegal or in bad taste, and you should be aware that the time and your IP address are both recorded, should it be necessary to find out who you are. Oh, and don't bother trying to inline HTML. I'm not that stupid! ☺ ADDING COMMENTS DOES NOT WORK IF READING TRANSLATED VERSIONS.
You can now follow comment additions with the comment RSS feed. This is distinct from the b.log RSS feed, so you can subscribe to one or both as you wish.
joe, 29th May 2012, 04:24
Rick, I think that your micro SD card was corrupted somehow and this is your problem. When I was cloning different cards, some were corrupted and sometimes I had this kernel panic message, too. Sometimes there was maybe one blink and nothing at all, unit appeared to be dead. If there was something wrong, you wouldn't be able to run RISC, you have to try another, working card. If you power supply is not overheating, than don't worry too much, read the manual: http://beagleboard.org/static/BBxMSRM_latest.pdf
Rick, 9th June 2012, 04:55
Just a short note to say that up-and-coming SDFS on the Beagle BOTH LEDs (both flash for writes, one flashes for reads). When I get a working Beagle, I'll have to recode it to do something sexy with the PWM-able LED on the assist-chip. But a filing system hijacking BOTH LEDs? Pffft! There are so many things you could do - like a "you have mail" notification or a blinking "everything's working" (as BeagLEDs was to be originally) or... I'm sure you can think of your own ideas.
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted.
RIPA notice: No consent is given for interception of page transmission.