ECE243
Andreas Moshovos
Spring 2008
The Timer Device
In this lecture we will discuss the timer device. The timer allows programs to measure absolute time and can be used in applications where real-time behavior is important. One such application would be a stop-watch. The timer is a 32-bit counter that counts down using a fixed clock. The counter also contains an indicator, think of it as a flag. Initially, you drop the flag, set the counter to some value, and start it. When it reaches zero, the timer raises the flag. When you see the flag raised, you can reset the counter to its initial value, drop the flag, and have it count again down to zero. A raised flag indicates that the desired time period has elapsed. The aforementioned process allows us to measure intervals of time with good accuracy. Alternatively, you can have the counter auto-reset after reaching zero. As soon as the counter reaches zero, it raises the flag, and automatically reloads the initial value you set earlier. You have to go and drop the flag before it reaches zero again. This way the timer can generate periodic signals without relying on us (the processor) to drop the flag and reset the counter at every interval.
So, here are the two ways the counter is meant to be used:
Count-Once
Mode:
Continuous-Count Mode:
The timer device is mapped onto memory starting at address 0x10002000. The timer contains six 32-bit registers, but only the lower 16 bits of each register are in use:
Offset |
Name |
31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
|
|
|
|
|
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
0 |
status |
Undefined / Use 0 when writing |
|
RUN |
TO |
||||||||||||||||||||||||||||
4 |
control |
Undefined / Use 0 when writing |
|
STOP |
START |
CONT |
ITO |
||||||||||||||||||||||||||
8 |
periodl |
Undefined / Use 0 when writing |
Lower 16 bits of counting period |
||||||||||||||||||||||||||||||
12 |
periodh |
Undefined / Use 0 when writing |
Upper 16 bits of counting period |
||||||||||||||||||||||||||||||
16 |
snapl |
Undefined / Use 0 when writing |
Lower 16 bits of current count |
||||||||||||||||||||||||||||||
20 |
snaph |
Undefined / Use 0 when writing |
Upper 16 bits of current count |
The timer is a 32-bit counter that counts downwards to zero using a 100Mhz clock. In a typical application, our program would initialize the counter to a specific count and then wait until the counter reaches zero. The control register can be used to start or stop the counter. The timer can be configured to restart over automatically or to stop and wait. In this former case, when the counter reaches zero, it re-loads the initial count we set before starting it for the first time and starts counting down to zero again without any program intervention. This is useful for measuring periodic intervals with great accuracy and for generating periodic signals such as pulses for audio or for driving motors. The status register can be used to check whether the counter is currently counting (RUN bit) and whether the counter has reached zero (TO = TimeOut bit). Our program can also inspect the SNAP registers in the timer device to determine how much time has elapsed since the countdown was initiated. The timer can also be configured to notify our programs through interrupts that zero has been reached. More on this later.
Let’s see how we can use the timer to generate a stop watch that counts down in seconds.
What we need is to set the timer so that it generates a period of one second. Since the clock used by the counter is 100Mhz or 100 x 10^6 = 10^7 Hz. We need to count 10^7 ticks for every second elapsed and we need to keep doing this until the number of seconds we are interested in have elapsed. So, we need to ask the counter to start counting down from 10^7.
Before we start initializing the timer we got to make sure it’s idle. This is done by sending the STOP command, which is done by setting the STOP bit (bit 3) of the control register to 1.
Assuming that we have made the following constant declarations:
.equ TIMER0_BASE, 0x10002000
.equ TIMER0_STATUS, 0
.equ TIMER0_CONTROL, 4
.equ TIMER0_PERIODL, 8
.equ TIMER0_PERIODH, 12
.equ TIMER0_SNAPL, 16
.equ TIMER0_SNAPH, 20
Here’s how to stop the timer:
movia r8, TIMER0_BASE
addi r9, r0, 0x8
stwio r9, TIMER0_CONTROL(r8)
Next we can initialize the period registers so that when the counter starts it will count down from 10^7.
This is done by writing to registers periodl and periodh. Each is 16-bits and together they form a 32-bit number. The code for initializing the period registers is:
.equ TICKSPERSEC, 100000000
# Set the period registers to 10^7
addi r9, r0, lo%(TICKSPERSEC)
stwio r9, TIMER0_PERIODL(r8)
addi r9, r0, %hi(TICKSPERSEC)
stwio r9, TIMER0_PERIODH(r8)
Where we used the assembly macros %lo() and %hi() to decompose the 32-bit period value into a 16-bit lower component and a 16-bit upper component.
For illustration purposes we will have the counter continue counting after a second has passed. This is done by setting bit CONT (bit 1) of the control register. At the same time we can tell the counter to start counting. This is done by setting bit START (bit 2) to 1.
# tell the counter to start over
automatically and start counting
addi r9,
r0, 0x6 # 0x6 = 0110 so
we write 1 to START and to CONT
stwio r9, TIMER0_CONTROL(r8)
To check whether zero has been reached we can poll the status register:
Bit 1, or RUN, tells us whether the counter is running. This should be 1 after the last store.
Bit 0, or bit TO for TimeOut, is 1 if the counter has reached zero. The TO bit remains stuck at 1 unless we explicitly clear it.
Here’s the complete code for a “stopwatch”. The function takes a single argument which is a number of seconds to wait.
It returns when this many seconds have elapsed. The argument is a 32 bit unsigned number.
.equ TIMER0_BASE, 0x10002000
.equ TIMER0_STATUS, 0
.equ TIMER0_CONTROL, 4
.equ TIMER0_PERIODL, 8
.equ TIMER0_PERIODH, 12
.equ TIMER0_SNAPL, 16
.equ TIMER0_SNAPH, 20
.equ TICKSPERSEC, 1000000000
.text
waitasec:
movia r8, TIMER0_BASE
addi r9, r0, 0x8 # stop the counter
stwio r9, TIMER0_CONTROL(r8)
# Set the period registers to 10^7
addi r9, r0, %lo (TICKSPERSEC)
stwio r9, TIMER0_PERIODL(r8)
addi r9, r0, %hi(TICKSPERSEC)
stwio r9, TIMER0_PERIODH(r8)
# tell the counter to start over
automatically and start counting
addi r9,
r0, 0x6 # 0x6 = 0110 so
we write 1 to START and to CONT
stwio r9, TIMER0_CONTROL(r8)
onesec:
ldwio r9, TIMER0_STATUS(r8) # check if the TO bit of the status register is 1
andi r9, r9, 0x1
beq r9, r0, onesec
movi r9, 0x0 # clear the TO bit
stwio r9, TIMER0_STATUS(r8)
# decrement the number of remaining seconds and repeat until this becomes zero
subi r4, r4, 1
bne r4, r0, onesec
# stop the counter before exiting
movi r9, 8
stwio r9, TIMER0_CONTROL(r8)
ret
And here’s a function that calls waitasec asking it to wait for 20 seconds:
.text
.global main
main:
addi r4, r0, 20
call waitasec
ret
Reading the
counter’s current value
We cannot and should not access the period registers to check what is the current value. Instead this can be done via the Snap registers. First we take a snapshot of the period registers and then we read it out. To take a snapshot of the period registers we have to write 0 to SnapL. At that moment, the timer copies the values of periodl and periodh to snapl and snaph respetively. We can then read the two snap registers. Here’s the code for that:
movia r8, TIMER0_BASE
stwio r0, TIMER0_SNAPL(r7) # Take a snapshot of the period registers
ldwio r9, TIMER0_SNAPL(r7) # Read snapshot bits 0..15
ldwio r10, TIMER0_SNAPH(r7) # Read snapshot bits 16...31
slli r10, r10, 16 # Shift the upper bits to positions 16 through 31 in register r10
or r9, r9, r10 # Combine bits 0..15 and 16...31 into one register
Other bits
The timer can also generate interrupts. Bit ITO of the control register instructs the timer to generate an interrupt when zero is reached. We’ll defer further discussion of interrupts and the timer for later. Timer0 is connected to IRQ3 and TIMER1 to IRQ4.