Okay, this was a lot more annoying than I expected it to be and really humbling on how little I apparently understand designing ARM code... but I got something working.
In the OK04 code, the system timer device is introduced.
The introduction discusses two methods to use the timer: count the delay yourself, or setup the counter and watch the status register.
The OK04 lesson then goes on to count the delay yourself leaving the counter setup and status register as an exercise for the reader.
I replace the GetTimerBase and Wait functions with a file of equates, and two new functions:
- SetCounterDelay( integer counter, integer delay) where counter is the counter register to use 0-3, and delay is the microseconds of delay to count
- WaitCounter( integer counter) where counter is the counter register 0-3 you set to wait for with a previous SetCounterDelay() call
Below is an example of using the counter register and counter status for people to hopefully learn from and improve.
First, copy your ok04 folder to ok04_extension.
Next, remove all source/*.s files except gpio.s
Then create the files described below with the following content:
ok04_extension/source/systemTimer-equates.s
/******************************************************************************
* systemTimer.s
* by Alex Chadwick
* System Timer Equates
* by Exile In Paradise
* The system timer runs at 1MHz, and just counts always.
******************************************************************************/
/*
* Register equates for the System Timer
*/
.equ ST_CS, 0x20003000
.equ ST_CLO,0x20003004
.equ ST_CHI,0x20003008
.equ ST_C0, 0x2000300C
.equ ST_C1, 0x20003010
.equ ST_C2, 0x20003014
.equ ST_C3, 0x20003018
/*
* Masks for each bit of the System Timer Counter Status Register
*/
.equ MASK0,0b0000000000000001
.equ MASK1,0b0000000000000010
.equ MASK2,0b0000000000000100
.equ MASK3,0b0000000000001000
/*
* Error return values for functions
*/
.equ ERR_INPUT1_OUT_OF_RANGE, -1
.equ ERR_INPUT2_OUT_OF_RANGE, -2
.equ ERR_INPUT3_OUT_OF_RANGE, -3
.equ ERR_INPUT4_OUT_OF_RANGE, -4
ok04_extension/source/systemTimer-SetCounterDelay.s
/*****************************************************************************
* systemTimer.s
* by Alex Chadwick
* SetCounterDelay(int counter,int delay)
* by Exile In Paradise
* The system timer runs at 1MHz, and just counts always.
*****************************************************************************/
.include "systemTimer-equates.s"
/*****************************************************************************
* SetCounterDelay - sets counter to an amount to wait for
* r0 - counter to set 0 through 3 inclusive
* r1 - amount of microseconds to delay (32-bit, 1-4294967296)
* Destroys r0, r1, r2, r3
* Alters ST_CS and ST_C0, ST_C1, ST_C2, OR ST_C3 registers
* Returns nothing on success, ERR_INPUT1_OUT_OF_RANGE, ERR_INPUT2_OUT_OF_RANGE
*/
.global SetCounterDelay
SetCounterDelay:
@ first, check our inputs are in range
in_counter .req r0
in_delay .req r1
return .req r0
cmp in_counter,#3 @ r0 (counter) must be 0 to 3 inclusive
movhi return,#ERR_INPUT1_OUT_OF_RANGE
movhi pc,lr @ input 1 out of range, return from function
cmp in_delay,#0 @ r1 (delay in microsecond) must be 1 or more
moveq return,#ERR_INPUT2_OUT_OF_RANGE
moveq pc,lr @ input 2 out of range, return from function
.unreq return @ remove alias
@ now setup the function
push {lr} @ save LR onto stack for later pop to PC
counter .req r2 @ alias reg2 as "counter"
delay .req r3 @ alias reg3 as "delay"
mov counter,in_counter @ save input r0 into scratch register
mov delay,in_delay @ save input r1 into scratch register
.unreq in_counter @ remove alias
.unreq in_delay @ remove alias
@ find which bit mask to write to clear specific counter status bit
counterstatus .req r0
cs_mask .req r1
ldr counterstatus,=ST_CS @ setup r0 for Counter Status register
cmp counter,#0 @ do we need to clear counter 0?
moveq cs_mask,#MASK0 @ setup mask for bit 0
beq go_on$ @ branch to go on
cmp counter,#1 @ do we need to clear counter 1?
moveq cs_mask,#MASK1 @ setup mask for bit 1
beq go_on$ @ branch to go on
cmp counter,#2 @ do we need to clear counter 2?
moveq cs_mask,#MASK2 @ setup mask for bit 2
beq go_on$ @ branch to go on
cmp counter,#3 @ do we need to clear counter 3?
mov cs_mask,#MASK3 @ setup mask for bit 3 (no moveq for last option)
beq go_on$ @ fall through to go on
pop {pc} @ somehow we got here and should not have - bail
go_on$:
str cs_mask,[counterstatus] @ write r1 mask to ST_CS to clear counter bit
.unreq counterstatus
.unreq cs_mask
@ now to set timestamp to wait for
counterlo .req r0
counterhi .req r1
delaytime .req r1
ldr counterlo,=ST_CLO @ set r0 to counter low address
ldrd counterlo,counterhi,[counterlo] @ get current 64-bit counter value into r1/r0
add delaytime,counterlo,delay @ add the desired delay to CLO, overwrite CHI
@ FIXME need carry compare to check for 32-bit rollover
.unreq counterlo @ clean up counterlo alias
.unreq counterhi @ clean up counterhi alias
.unreq delay
@ Now setup the counter register to wait for
counterreg .req r0
loopidx .req r3
ldr counterreg,=ST_C0 @ load address of ST_C0 into register 0
mov loopidx,#0 @ setup a loop counter for 0 to 3
counterLoop$: @ figure out correct address of counter to set
cmp counter,loopidx @ does loop number match counter we want?
addlo counterreg,#4 @ add 4 to counter register address we care about
addlo loopidx,#1 @ not counter we want, add to to check next counter
blo counterLoop$ @ now loop to check again
str delaytime,[counterreg] @ store 32 bit time+delay to chosen count
@ return from function call
.unreq counterreg @ clean up counterreg alias
.unreq delaytime @ clean up delaytime alias
.unreq counter @ clean up counter alias
.unreq loopidx @ clean up alias loopidx
pop {pc} @ pop link register from stack into pc register
ok04_extension/source/systemTimer-WaitCounter.s
/*****************************************************************************
* systemTimer.s
* by Alex Chadwick
* WaitCounter(int counter)
* by Exile In Paradise
* The system timer runs at 1MHz, and just counts always.
*****************************************************************************/
.include "systemTimer-equates.s"
/*
* WaitCounter waits until the System Timer Counter Status register shows a
* preset counter has reached its timestamp and set the counter status high.
* Inputs: r0 = counter to wait for
* Returns: r0 = error if input out of range
* Outputs: none
* Uses: ST_CS
* Destroys: r0, r1, r2, r3
*/
.global WaitCounter
WaitCounter:
@ check inputs
in_counter .req r0
retval .req r0
cmp in_counter,#3 @ check which counter number we were asked to wait on
movhi retval,#ERR_INPUT1_OUT_OF_RANGE
movhi pc,lr @ return if requested counter to wait on is too high
.unreq retval
@ setup the function
push {lr} @ save the return link register on the stack
counter .req r2 @ alias register 2 as "counter"
mov counter,in_counter @ move input register 0 "counter" to alias register
.unreq in_counter
@ Start the wait polling loop
csreg .req r0
counterstatus .req r1
csmask .req r3
loop$:
ldr csreg,=ST_CS @ load counter status address into r0
ldr counterstatus,[csreg] @ load counter status value into r1
cmp counter,#0 @ check if we are waiting for counter 0
moveq csmask,#MASK0
beq test$ @ if matches, exit polling loop
cmp counter,#1 @ check if we are waiting for counter 1
moveq csmask,#MASK1
beq test$ @ if matches, exit polling loop
cmp counter,#2 @ check if we are waiting for counter 2
moveq csmask,#MASK2 @ if yes, check bitmask for counter 2 against r1
beq test$ @ if matches, exit polling loop
@cmp counter,#3 @ check if we are waiting for counter 3
moveq csmask,#MASK3 @ if yes, check bitmask for counter 3 against r1
@beq test$ @ if matches, exit polling loop
b loop$ @ no matches in counter status, go wait more
@ exit polling loop and return
test$:
tst counterstatus,csmask
beq loop$
@ if equal, timer done, exit
.unreq counter
.unreq csreg
.unreq csmask
.unreq counterstatus
pop {pc} @ return from function call
And, finally, ok04_extension/source/main.s
/*****************************************************************************
* main.s
* by Alex Chadwick
* Modified to use SetCounterDelay() and WaitCounter()
* by Exile In Paradise
* A sample assembly code implementation of the ok04 operating system, that
* simply turns the OK LED on and off repeatedly, synchronising using the
* system timer.
* Sections changed since ok03.s are marked with NEW.
*
* main.s contains the main operating system, and IVT code.
*****************************************************************************/
/*
* .globl is a directive to our assembler, that tells it to export this symbol
* to the elf file. Convention dictates that the symbol _start is used for the
* entry point, so this all has the net effect of setting the entry point here.
* Ultimately, this is useless as the elf itself is not used in the final
* result, and so the entry point really doesn't matter, but it aids clarity,
* allows simulators to run the elf, and also stops us getting a linker warning
* about having no entry point.
*/
.section .init
.globl _start
_start:
/*
* Branch to the actual main code.
*/
b main
/*
* This command tells the assembler to put this code with the rest.
*/
.section .text
/*
* main is what we shall call our main operating system method. It never
* returns, and takes no parameters.
* C++ Signature: void main(void)
*/
main:
/*
* Set the stack point to 0x8000.
*/
mov sp,#0x8000
/*
* Use our new SetGpioFunction function to set the function of GPIO port 16 (OK
* LED) to 001 (binary)
*/
mov r0,#16
mov r1,#1
bl SetGpioFunction
/*
* Use our new SetGpio function to set GPIO 16 to low, causing the LED to turn
* on.
*/
loop$:
mov r0,#16
mov r1,#0
bl SetGpio
/* NEW
* We wait using our new method. We use a value of 100000 micro seconds for the
* delay causing the light to flash 5 times per second.
*/
mov r0,#0 @ use ST_C0 counter 0
ldr r1,=100000 @ delay for 100,000 microseconds
bl SetCounterDelay
mov r0,#0 @ use ST_C0 counter 0 to wait on
bl WaitCounter
/*
* Use our new SetGpio function to set GPIO 16 to high, causing the LED to turn
* off.
*/
mov r0,#16
mov r1,#1
bl SetGpio
/* NEW
* We wait using our new method. We use a value of 100000 micro seconds for the
* delay causing the light to flash 5 times per second.
*/
mov r0,#0 @ use ST_C0 counter 0
ldr r1,=100000 @ delay for 100,000 microseconds
bl SetCounterDelay
mov r0,#0 @ use ST_C0 counter 0 to wait on
bl WaitCounter
/*
* Loop over this process forevermore
*/
b loop$
And that should be it - make, copy to your test Pi, and give it a whirl.
It should behave the same as ok04 - but in main.s you can change the r0 for counter 0 to 3, and change the delays, and the code uses the counter register requested for the delay requested.
Enjoy.
P.S. Please improve this anywhere possible - all criticism welcome in the interest of showing people better ways to get this done. I am just laying down a start here and am fully aware this code is probably horrible and atrocious and could be severely optimized. Go for it!