PJRC.COM Offline Archive, February 07, 2004 Visit this page on the live site |
| ||
Shopping Cart Checkout Shipping Cost Download Website |
Home | MP3 Player | 8051 Tools | All Projects | PJRC Store | Site Map |
You are here: 8051 Tools Development Board Example Code Timers | Search PJRC |
|
No time to read all this?? Jump directly to the downloadable example code.
The TF0 bit is automatically set to 1 when Timer 0 overflows. Your code can read the TF0 bit at any time to find out if the timer has overflowed. You can also configure the Timer 0 interrupt to automatically execute code when the timer overflows. When the interrupt code starts, the 8051 automatically clears TF0 back to zero. If you do not use an interrupt, your code must clear TF0 in order to be able to detect the next time Timer 0 overflows.
Timer can be started and stopped with the TR0 bit. When this bit is set to 1, the timer runs, and when its cleared to zero the timer is stopped. This allows you to create one-shot delays easily, or measure elapsed time between two events. Applicatons that need regular intervals usually just set this bit and never stop the timer.
Mode | Description | Typical Application |
---|---|---|
0 | 8 Bit + div by 32 | Repetitive Timeout, 225 Hz |
1 | 16 Bit | Longer Delay or Elapsed Time Measurement |
2 | 8 Bit Auto-Reload | Repetitive Timeout, 7200 Hz and faster |
3 | Two 8 Bit | Limited timer and counter (not commonly used) |
Mode 0 is the simplest to use. A single 8 bit value is incremented at 57600 Hz, which results in 225 Timer 0 overflows per second (if the value is not modified by your code). Mode 0 is commonly used to execute an interrupt routine at a regular interval.
Mode 1 provides the greatest precision and longest timeout. The timer value is 16 bits and it increments at 1.8432 MHz. Mode 1 is useful for measuring elapsed time between events. It also provides the flexability for generating delays and timeouts with more precision, at the cost of additional complexity of handling 16 bits instead of 8.
Mode 2 provides short repetitive intervals. When the timer overflows, the 8 bit value is automatically reloaded with a value you define, rather than rolling over to zero. Because the value increments at 1.8432 MHz, the timeout interval is adjustable from 0.543 µs to 139 µs. Timer 1 is commonly used in mode 2 to configure the serial port baud rate. Timer 0 is not usually used in mode 2 because the timeout interval is so quick.
Mode 3 turns timer 0 into two 8 bit timers. One is incremented at 1.8432 MHz (no auto-reload), and the other is incremented by external pulses. Mode 3 is not commonly used.
The first step is to clear the TR0 and TF0 bits. This can be done directly on the bits, or by modifying the TCON register which contains both of them. Writing directly to TCON also effects Timer 1. If Timer 1 is already in use generating baud rates, clearing TR1 will stop all serial port communication. In assembly, "ANL TCON, #0x5F" will clear TR0 and TF0 safely. In C, "TCON &= 0x5F" has the same effect. Manipulating the two bits directly takes one extra byte of code and makes the source code more readable. If you do this, clear TR0 first.
Next, you will usually set the Timer 0 mode using TMOD. Writing directly to TMOD also effects Timer 1. If Timer 1 is already generating baud rates, you should AND it with 0xF0 to set Timer 0 to mode 0, and then OR it with 1, 2 or 3 if one of those modes is desired instead of mode 0. TMOD also can configure counter options to the timer, which we won't discuss further to keep things simple.
To set the timer's initial value, you will write to TH0 and TL0 as necessary. For mode 0, TH0 is the 8-bit value. In mode 1, both are used to hold the 16 bits. In mode 2, TL0 holds the 8 bit value, and TH1 holds the automatic reload value.
If you intend to use interrupts, you will usually enable the interrupt (discussed below in the interrupt section), and then to complete the timer setup you would set TR0 to start it running. What you do with the timer after it is running depends on your application. The next section will look at some common applications and how to make use of the Timer 0 in each.
Polling is simpler and easier than using interrupts for applications that essentially wait for the timer to complete or do not need to perform other non-related tasks while the timer is running. It is possible to perform some work while the timer is running by placing it inside the polling loop. Generally, when the polling loop does not need to contain code to preform other tasks unrelated to the timer activity, polling is usually the best approach to using Timer 0. The examples in this section show how polling is done in a few useful Timer 0 applications. In each case, we will briefly look at the limitations of the simpler polling approach.
This example uses the serial port's RI bit (reception of a byte) as the event. It can easily be modified to test port pins to measure time between hardware-level events.
Most of the time, the two conditional branches at the top of the polling loop will execute over and over. So in most cases, Timer 0 will be stopped within 4 cycles (2 µs) of the occurance of the second event. However, if the RI bit becomes set while the 16 bit increment code is executing, several mode cycles can elapse, leading to an additional error in the measurement.
begin: clr tr0 ;make sure timer 0 is stopped clr tf0 ;clear the overflow flag anl tmod, #0xF0 orl tmod, #0x01 ;set to mode 1 (without touching timer 1) mov tl0, #0 mov th0, #0 ;clear the timer 0 value mov r3, #0 mov r2, #0 ;clear the overflow count mov dptr, #msg_begin lcall pstr lcall cin ;wait for the user to start the test setb tr0 ;start the timing waiting_loop: jb ri, done_waiting ;did we receive another byte ?? jnb tf0, waiting_loop ;did timer 0 overflow? mov a, r2 add a, #1 ;increment the overflow count mov r2, a mov a, r3 addc a, #0 mov r3, a mov a, #'.' lcall cout ;print a dot, so the user sees something clr tf0 sjmp waiting_loop ;keep waiting until the press another key done_waiting: clr tr0 ;stop timer 0Complete code is available in the download section at the bottom of this page.
The worst case error is the longest path executed by the polling loop between tests of the stop condtition. Though this error is small (and can be made smaller with some careful re-writing of the polling loop), it can not be avoided. Worse yet, if some other work must be done within the polling loop, the normal and longest execution paths between tests of the stop event will grow, thereby increasing the typical and worst-case measurement errors.
Later, we will revisit this elapsed time measurement using interrupts. The interrupt approach will allow this potential error to be eliminated (and allow other code unrelated code to execute while the timer is counting), but at the cost of a much more complex and difficult implementation.
TODO: explain how this code works
begin: mov r0, #0 ;r0 counts number of bytes received clr tr0 ;make sure timer 0 is stopped clr tf0 ;clear the overflow flag anl tmod, #0xF0 orl tmod, #0x01 ;set to mode 1 (without touching timer 1) mov tl0, #0 mov th0, #0 ;clear the timer 0 value mov r2, #140 ;140 overflows is 5 seconds mov dptr, #msg_begin lcall pstr setb tr0 ;start the timing waiting_loop: jb ri, get_input ;did we receive another byte ?? jnb tf0, waiting_loop ;did timer 0 overflow? clr tf0 djnz r2, waiting_loop timeout: mov dptr, #msg_timeout lcall pstr sjmp ending get_input: lcall cin ;get the user input lcall cout ;echo it back to their screen mov r2, #28 ;reset timeout to 1 second inc r0 cjne r0, #10, waiting_loop got_all_input: mov dptr, #msg_ok lcall pstr ending:Complete code is available in the download section at the bottom of this page.
TODO: polling vs interrupts....
begin: mov r7, #0 ;zero hours, minutes, seconds mov r6, #0 mov r5, #0 clr tr0 ;make sure timer 0 is stopped clr tf0 ;clear the overflow flag anl tmod, #0xF0 ;set to mode 0 (without touching timer 1) mov th0, #0 ;clear the timer 0 value mov tl0, #0 mov r2, #225 ;start overflow countdown (1 second) mov dptr, #msg_begin lcall pstr setb tr0 ;start the timing lcall print_time timekeeping_loop: jnb tf0, timekeeping_loop ;did timer 0 overflow? clr tf0 ;reset overflow flag djnz r2, timekeeping_loop ;is this 225 overflows (exactly 1 second) lcall inc_time lcall print_time mov r2, #225 ;reset countdown for another second sjmp timekeeping_loopComplete code is available in the download section at the bottom of this page.
TODO: this code restructured as subroutines
TODO: pitfalls of the polling approach
However, these advantages do not come for free. The intrrupt service routine code must be written very carefully to avoid interfering with the main program, and great care must be used when exchanging data between the interrupt routine and the main program. Interrupts should be avoided when simple polling is adaquete.
To define a timer0 interrupt routine in assembly, simply using an ORG directive to place the code at location 0x200B. The 8051 will jump to 0x000B, and the code inside PAULMON2 has an LJMP instruction which jumps to 0x200B in your code. In some cases, another LJMP is placed at 0x200B to jump to your code, if it is not possible to place the interrupt service routine at 0x200B.
.org 0x200B timer0_isr: ; interrupt service routine code retiDefining an ISR in Assembly (AS31)
Using C language, an interrupt service routine is declared using a special "interrupt" keyword. SDCC requires that the interrupt routine or its function prototype must be included in the same file as the main() function.
void timer0_isr(void) interrupt 1 { /* interrupt service routine code */ }Defining an ISR in C Language (SDCC)
TODO: Properly saving context.
TODO: Enabling, disabling, priority, and latency
TODO: Atomic access to shared data issues
begin: mov ie, #0 ;turn off all interrupts mov sp, #stack-1 mov hours, #0 ;zero hours, minutes, seconds mov minutes, #0 mov seconds, #0 mov ov_countdown, #225 clr tr0 ;make sure timer 0 is stopped clr tf0 ;clear the overflow flag anl tmod, #0xF0 ;set to mode 0 (without touching timer 1) mov th0, #0 ;clear the timer 0 value mov tl0, #0 mov dptr, #msg_begin lcall pstr clr time_changed_flag setb tr0 ;start the timing mov ip, #0 ;set interrupt priorities (all low) mov ie, #0x82 ;enable timer0 interrupt lcall print_time timekeeping_loop: lcall esc jc abort jnb time_changed_flag, timekeeping_loop clr time_changed_flag lcall print_time sjmp timekeeping_loop ;this interrupt service routine will run every time timer0 sets ;the TF0 flag. The 8051 hardware automatically clears TF0 for ;us. As with all interrupts, we must be very careful to save ;any registers that get changed. timer0_isr: djnz ov_countdown, timer0_end ;have 225 interrupts (1 second) elapsed? mov ov_countdown, #225 push psw ;save psw (cjne changes status bits) push acc ;save accumulator, used inside inc_time acall inc_time ;actually increment the time setb time_changed_flag ;set a flag to alert the main program to change pop acc pop psw timer0_end: retiComplete code is available in the download section at the bottom of this page (also in C language).