After the Project Trailblazer engineers resolve the average interrupt latency question, they can design the race timer. All the Project Trailblazer target boards have the input/output (I/O) and speed capability to act as the race timer. For this design, the engineers decide to use the MZ104 as the controller. Figure 11.7 shows a schematic of the race timer.
The race timer should have the following functional features:
It must have 1ms accuracy.
It should display the current race time at the finish line.
It should provide a status display to officials operating the timer.
It should use the racer's pass ID in race information.
It should provide the racer with a race start indicator.
It should log race results.
It should provide access to status, race time, and racer number through /proc directory entries.
Figure 11.8 shows the racer controller state map.
The operational race scenario consists of four states:
Ready— On power-up, the race controller enters the ready state and awaits a racer. The racer gets ready at the starting gate. The radio frequency identification (RFID) tag reader reads the racer's lift pass ID and sends it via the RS-232 serial link. This places the controller in the set state.
Set— The Status LED and Racer Go LEDs turn on. The racer proceeds through the starting gate, which sends a rising-edge signal to the ACK line and generates interrupt 7. The interrupt handler routine executes, records the race start time with do_gettimeofday, schedules the bottom-half tasklet for execution, and puts the controller in the timing state.
Timing— The Status LED and Racer Go LEDs blink at 1-second intervals. When the racer crosses the finish line, the photo detector sends a rising-edge signal to the ACK line and generates interrupt 7. The interrupt handler routine executes, records the race finish time with do_gettimeofday, schedules the bottom-half tasklet for execution, and puts the controller in the done state.
Done— The Status LED and Racer Go LEDs turn off. The controller waits for another racer to enter the starting gate.
The Project Trailblazer engineers have already completed several pieces of this design. Chapter 6, “Asynchronous Serial Communication Interfacing,” addresses asynchronous serial communications for the RFID tag reader. Chapter 7, “Parallel Port Interfacing,” addresses parallel port operations. Chapter 10, “Synchronous Serial Communication Interfacing,” addresses synchronous communications with the Philips SAA1064 I2C LED controller. Earlier in this chapter, the engineers developed the interrupt and timing routines. Chapter 12, “System Integration,” addresses system integration with the Project Trailblazer server. Therefore, these details are not covered here. For the remainder of this chapter, we'll concentrate on two areas: interrupt bottom-half processing using tasklets and kernel timers.
Complex or lengthy interrupt processing tasks are often split into two sections. The top-half routine executes at interrupt time, performs the minimal amount of work, and schedules the remainder of work, called the bottom half routine, to be performed at a later or safer time. The top-half routine can request execution with interrupts disabled; therefore, it should execute and terminate as quickly as possible. The kernel executes bottom-half routines with interrupts enabled. With Linux kernel 2.4 and above, the preferred way to implement bottom-half routine uses tasklets.
Tasklets are declared by using the DECLARE_TASKLET macro. This macro also associates a handler function to the tasklet. The top-half interrupt handler routine should schedule an already declared tasklet for later execution. The kernel executes the scheduled tasklet handler functions after all the interrupt handler routines complete but before execution of the scheduler.
In the race timer design, the interrupt handler, racetimer_interrupt, schedules the race timer tasklet's handler routine, racetimer_do_tasklet, for later execution. The racetimer_do_tasklet function prints race status information to the system log, which is a somewhat lengthy process, and starts a system timer.
The race timer Status and Racer Go LEDs inform officials, fans, and racers of race activity. A solid on signal means the system is ready for a race to begin. Blinking LEDs mean a race is in progress. When the LEDs are off, a race is complete and the system is ready for another racer to enter the starting gate. The Project Trailblazer engineers need to figure out how to blink the LEDs at a constant 1-second rate.
As you saw at the beginning of this chapter, Linux offers five timing mechanisms, and the engineers chose to use do_gettimeofday for race timing. The race timer driver could sit in a loop calling do_gettimeofday and wait for 1 second to elapse. The race timer driver could also call a system sleep function (usleep, msleep, or sleep) and wait for 1 second to elapse. Both approaches would work, but neither is desirable. The kernel scheduler does not drive code that is executing in the kernel space. A 1-second sleep in the kernel would completely occupy the processor for 1 second, and other processing would not occur during this time. Device drivers that use sleep functions for long delays definitely affect system performance.
Kernel timers solve long time delay problems. By using a timer, device drivers can schedule a function for execution at a future time. A device driver creates a timer, populates the timer's data, function and expires fields and then adds the timer to the kernel's timer list. The x86 kernel scans the timer list approximately 100 times per second. If the kernel's jiffies value is greater than a timer's expires field value, the kernel executes the timer's function handler. Timers operate in a one-shot mode. The kernel executes timer function handlers only once. Drivers that need periodic function execution need to reschedule their kernel timers in the timer's function handler.
In the race timer, the bottom-half tasklet handler, racetimer_do_tasklet, starts the kernel timer called status_timer. One second later, the status_timer expires and the kernel executes the status_timer's function handler, status_timer_timed_out. If the race is in progress (the timing state where the LEDs blink at 1 second intervals), status_timer_timed_out toggles the Status and Racer Go LEDs, sets the status_timer.expires value, and then reschedules (via the add_timer function) status_timer for execution 1 second later. If the race has completed, status_timer_timed_out turns off the LEDs.
Let's look at how the racetimer_x86.c device driver, shown in Listing 11.4, implements bottom-half interrupt processing using a scheduled tasklet and 1-second timing of the Status and Racer GO LEDs.
You can download, compile, and test the racetimer_x86 device driver by using tbdev1. Follow these steps:
1. |
Download the racetimer_x86.c source by using wget: root@tbdev1[509]: cd /root root@tbdev1[510]: wget http://www.embeddedlinuxinterfacing.com/ chapters/11/racetimer_x86.c |
2. |
Compile the source code by using gcc: root@tbdev1[511]: gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/linux/include -c racetimer_x86 .c -o racetimer_x86.o |
3. |
Insert the device driver by using insmod: root@tbdev1[512]: insmod racetimer_x86.o
|
4. |
Look for interrupt registration on Interrupt 7 and the /proc directory entries by using these commands: root@tbdev1[513]: cat /proc/interrupts CPU0 0: 8163519 XT-PIC timer 1: 2195 XT-PIC keyboard 2: 0 XT-PIC cascade 4: 6016 XT-PIC serial 7: 0 XT-PIC racetimer 10: 23132 XT-PIC usb-uhci 12: 314011 XT-PIC eth0 14: 54205 XT-PIC ide0 15: 8457 XT-PIC ide1 NMI: 0 ERR: 0 root@tbdev1[514]: ls /proc/trailblazer/ racer racestatus racetime The race timer is registered on Interrupt 7 and the /proc/trailblazer directory contains the race timer files. |
5. |
Check the internal state of the race timer status: root@tbdev1[515]: cat /proc/trailblazer/racestatus
R
The output R means that the timer is in the ready state. |
6. |
Start a race by simulating a racer entering the starting gate with this command: root@tbdev1[516]: echo -n "1234" > /proc/trailblazer/racer |
7. |
Again check the internal state of the race timer status: root@tbdev1[517]: cat /proc/trailblazer/racestatus
S
The output S means that the timer is in the Set state. You can tell from this output that setting the racer number occurred correctly, and it changed the internal state to S, or set. The status LED also came on. Measure the voltage of the parallel port's D0 signal, pin 2 on the DB-25 connector. Your voltmeter should read between +3V and +5V. |
8. |
Now check the race time by using this command: root@tbdev1[518]: cat /proc/trailblazer/racetime
0.000000
|
9. |
The racer number is entered in the racer file, the race time is 0.00000 and the timer is in the set state. You are set to start the race. Simulate the racer proceeding through the starting gate by generating the interrupt signal on the parallel port ACK line, pin 10 on the DB-25 connector. Use a debounced switch to generate a positive going signal on the ACK line. TIP With interrupt latencies in the microsecond range, Linux interrupt routines can easily count mechanical switch bounces. If you would like to see this for yourself, use a toggle switch to generate the interrupt signal on the parallel port's ACK line. Then examine the interrupt count in the /proc/interrupts file. You should see that one switch closure results in more than one interrupt. Mechanical switches alone should not be used to generate interrupt signals in interrupt-based designs or during interrupt driver testing. For testing, use a switch debounce circuit or write a device driver, again for the parallel port, that drives the ACK line from another parallel port pin. The gate_x86 device driver at www.embeddedlinuxinterfacing.com/chapters/11 generates signals for interrupt device driver testing. |
10. |
Again check the internal state of the race timer status: root@tbdev1[519]: cat /proc/trailblazer/racestatus
T
The output T means that the timer is in the timing state. |
11. |
Check the race time by using this command: root@tbdev1[520]: cat /proc/trailblazer/racetime
6.331849
You are timing a race, and 6.331849 seconds have elapsed. |
12. |
Continue checking the race time: root@tbdev1[521]: cat /proc/trailblazer/racetime
58.101223
You are still timing the race. The status LEDs are blinking at 1-second intervals. Figure 11.9 shows an oscilloscope capture of the parallel port's D0 signal. This confirms the Status LED blink rate. As you can see, the system timer works perfectly. Figure 11.9. An oscilloscope reading that confirms the 1-second interval timer performance. |
13. |
Simulate the racer crossing the finish line by using your debounced switch. Again, generate an interrupt signal on the parallel port's ACK line. |
14. |
Check the internal state of the race timer status: root@tbdev1[522]: cat /proc/trailblazer/racestatus
D
The output D means the race is done. |
15. |
Check the race time and the system log by using these commands: root@tbdev1[523]: cat /proc/trailblazer/racetime 88.141655 root@tbdev1[524]: grep RaceTime /var/log/messages Nov 26 22:54:14 tbdev1 kernel: RaceTimer: Start 1234 1006840454.722554 Nov 26 22:55:42 tbdev1 kernel: RaceTimer: Finish 1234 1006840542.864209 The race time of your first simulated race was 88.141655 seconds. |
16. |
One last check of /proc/interrupts shows that tbdev1 received two interrupts—from the race start and finish: root@tbdev1[525]: cat /proc/interrupts
CPU0
0: 8262755 XT-PIC timer
1: 2195 XT-PIC keyboard
2: 0 XT-PIC cascade
4: 6016 XT-PIC serial
7: 2 XT-PIC racetimer
10: 23132 XT-PIC usb-uhci
12: 314556 XT-PIC eth0
14: 54257 XT-PIC ide0
15: 8457 XT-PIC ide1
NMI: 0
ERR: 0
|
The Project Trailblazer race timer just timed its first race!
18.218.93.169