J. Feeney received a Bachelor of Science degree in Biomedical Engineering from Boston University. He has worked as an Engineer/Physicist at Hoechst Celanese's Research Division for the last six years. He specializes in computer interfacing and writing data acquisition and analysis programs for the PC under Windows and DOS. You may contact him at Hoechst Celanese, Robert L. Mitchell Technical Center, 86 Morris Avenue, Summit, New Jersey, 07901 or at jef@rlmtc.enet.hcc.com.
Introduction
Many scientific experiments or production processes require controlling equipment and collecting information. When large volumes of data are generated, it is desirable to interface the devices to a computer using a protocol such as RS-232C or IEEE-488. Using computers to control experiments and acquire data often reduces the experiment time, produces more reliable data (with a debugged program), and provides data that is ready for further computer analysis (e.g. spreadsheets, plotting, etc).Data acquisition can be done at fixed time intervals, often referred to as time-drive or conditionally when certain criteria are met, such as when a stepper motor completes spinning. Timing routines to synchronize these events are complicated by demands of programming for Microsoft Windows. Although programming is more complicated, a Windows application benefits the end-user by providing a "multi-tasking" environment and a graphical interface that changes little from program to program.
This article presents a specific example of a data acquisition and control program running under Windows 3.1. Discussing the entire program is beyond the scope of any one article. The entire code, most of which is data analysis and display, contains nearly 5,000 lines. Instead, my intent with this article is to demonstrate how data acquisition can be accomplished under Windows. The software was written using Microsoft QuickC for Windows, which offered excellent programming power and an easy-to-use environment.
The Application
This application was in a research laboratory controlling and acquiring data from several instruments. Collectively, we used the equipment to measure the optical attenuation of polymer optical fibers. The setup is shown in Figure 1. It consists of several stand-alone instruments linked to a PC by a GPIB IEEE-488 interface.The measurement method, shown conceptually in a flow chart in Figure 2, basically consists of using a monochromator (a light source with a known wavelength) and input optics to launch light into a fiber. The wavelength of light is chosen by using the stepper motors to control the internal optics of the monochromator. The optical power being launched into the fiber as well as the power transmitted through the fiber is then measured. This is done using optical detectors that generate a current linearly proportional to the intensity of the light measured. The currents are measured using lock-in amplifiers, which are stand-alone instruments with analog to digital converters (A/Ds), connected to a PC with a GPIB IEEE-488 interface.
The optical attenuation, a, is typically expressed in decibels per kilometer (dB/km) and is calculated as the log of the ratio of the output power, Pout, to the input power, Pin, divided by the fiber's length, L, in km as follows:
(eq. 1)
a = [10log10(Pout/Pin)]/LUsing equation 1, the measurements from the lock-in amplifiers establish the optical attenuation of the fiber at the wavelength of the monochromator. The experiments are typically run over a range of wavelengths. This is accomplished by scanning, or discretely stepping, the monochromator over the desired wavelength range and repeating the measurements. Following this procedure, the spectroscopic attenuation of the polymer optical fiber under test can be measured. A typical result is shown in Figure 3.The actual method and experimental setup are significantly more complex and rely upon a cutback method for determining the launched power, Pin. The above representation, however, provides a good general overview of what the software needs to accomplish.
Program Requirements
This application required the development of custom software. Each instrument was from a different manufacturer, and collectively all instruments were being used in a very specific and fairly unique way. The application mandated that the software control the lock-in amplifiers and stepper motor to collect and then process data. Furthermore, we desired real-time displays of the raw data and the result (similar to Figure 3) .Because of the nature of the research we were conducting, specialized applications such as this often required custom software. The program was originally developed for MS-DOS using Microsoft C v6.0 in 1990. It was menu driven and offered a real-time display of the final result. It might also have been possible to develop the application using a commercial toolkit such as Lab-View. However, because I have had no experience with such packages and did have an extensive programming background, I chose to directly code the program.
Throughout 1990 and 1991, the program was constantly upgraded but still lacked several features that make the current Windows version attractive. For example, data space was limited, the graphical displays were also limited and not as flexible or easily re-sized as the Windows version, and the program could not be suspended during acquisition or run in the background. Moving the program to the Windows operating system provided these advantages, as well as making the program easier to use by providing a graphical interface.
Data Acquisition
After the monochromator is set at a desired wavelength by moving the stepper motor, the light intensity (or power) is measured from a lock-in amplifier. It is a rather sophisticated instrument capable of digitizing currents or voltages, and has several desirable features such as frequency filtering and time averaging. Since the lock-in amplifier reported the averaged signal over a given time constant, the sampling speed required of the program was minimized. However, the signal was sampled several times during a measurement, typically three to four times per second over a two-second period. This provided an opportunity to1) periodically check the lock-in amplifier's status, for error conditions such as overload
2) to adjust the full-scale range of the A/Ds
3) to check the noise level.
If the standard deviation of the readings is too large at a given wavelength, the measurement cycle is repeated. This scheme involves using two timers. For example, one counts at a rate of 250 milliseconds (a sampling frequency of 4 Hz) and another ticks every 2 seconds to signal the end of the sampling period. These timers signal when the next sample should be read or when the sampling period expires.
The actual implementation was slightly more complex, since the sampling rate must be greater than the lock-in amplifier's time constant. Reading faster than the time constant would produce a set of numbers that are already statistically correlated. Any further statistical processing would therefore be invalid. Sampling with a rate at least half the time constant ensures that the data is statistically independent. Figure 4 shows the sampling scheme in a flow chart that corresponds to the program listings provided.
Writing for Windows
Most of the program code handles the user interface, display, and data analysis. For example, the program can display the three primary results, POUT, PIN, and the ratio of powers in dB 10*log10(POUT/PIN), as a function of wavelength in separate child windows that can be re-sized, minimized, or maximized. Since the focus of this article is to demonstrate the timing techniques used to automate data acquisition, only the pertinent sections of the source code are given in Listing 1 (prototype and declaration information) and Listing 2 (program body).The program uses a status variable to keep track of user requests or actions, such as re-sizing the window or aborting the last operation, and to record program options, such as displaying additional information useful for debugging the program or troubleshooting equipment problems. This is done by setting or clearing bits in the status variable. Individual Boolean variables for each status flag could also be used, but that would require an extra 15 variables. To improve the code's readability, I replaced the most commonly used statements involving the status variable with macro-substitution directives (#defines) as seen in Listing 3.
Several schemes could have been used to implement the timing functions. The first, shown in Listing 4, is simply to go to sleep and not return until time has elapsed. Since Windows is not a true real-time system it cannot actively preempt control from a program this hangs the system until the time has elapsed. An improved version is shown in Listing 5.
PeekMessage allows other messages to be processed while waiting for the time to expire, by yielding control back to Windows. The only problem with this approach is the possibility that another application might monopolize too much time. The while statement will loop until all waiting messages have been processed. An if statement could be substituted to process only one message at a time. This might improve the timing resolution of the function by limiting the time yielded back to Windows. Fortunately, the timing requirements of this application are non-critical. The routine only needs to guarantee that at least delay milliseconds have elapsed.
Each routine has some advantages. For example, if the delay is not long or needs to be precisely timed, and no other programs with critical timing demands are running in the background, then the sleep routine might be the most appropriate. If other programs or other processes in the application (such as an emergency shut-off option) need processor time, then the function should yield control back to Windows. Both routines delay for delay milliseconds, rely upon the clock function in the Standard C library for their precision (which ticks 18.2 times per seconds, or approximately every 54 milliseconds, on a PC), and perform no error checking in case of a date change.
Another approach was also implemented in this application. It relies solely upon the Windows operating system for notification of when a desired time has elapsed. This is done by using the SetTimer function in the Windows software development toolkit (SDK). For example, the expression
SetTimer(hWnd, IDM_READ, 250, OL)would cause Windows to post a WM_TIMER message (with wParam equal to ID-M_READ) to the main function associated with the window hWnd every 250 milliseconds until the timer is stopped using
KillTimer(hWnd, IDM_READ)The immediate response to this message (WM_TIMER)in my program is to kill the timer. This prevents time from elapsing while data is being acquired, processed, or displayed.Table 1 describes the timers used in the program. Only the sample, period, and time-drive timers are actually set. The start and stop timers are manually posted, via
SendMessage(hWnd, WM_TIMER, IDM_START, OL)when the user chooses appropriate menu items or when the programmed scan is completed. The IDM_TDRIVE timer controls data acquisition versus time instead of wavelength. At each tick, a point is acquired and the time is checked by calling time since the Windows timer only guarantees that the specified time has elapsed, not that it has just expired.
Summary
The source code presented here is intended to demonstrate how real-time data acquisition might be implemented under Microsoft Windows. Such an implementation is desirable because of the advantages offered by Windows over DOS-based programs, such as a graphical user interface, a "multi-tasking" environment, and device-independent displays, printing, and plotting. However, writing programs that most effectively utilize Windows' capabilities can be quite complicated. Fortunately, the timing requirements of this application and any applications I might run in the background were non-critical. Because of that, the schemes presented above were fairly easy to implement and the program has been functioning perfectly for over a year.