Timing is an important consideration when acquiring analog signals using PC-compatible boards. To minimize errors when calculating signal parameters from the acquired data (rms, average value, period, etc.), the sample rate must be known and constant. This also is critical when analyzing a signal in the frequency domain using FFTs.
Today, several techniques are able to achieve stable sampling periods. One is direct memory access (DMA), with a limited number of channels available to users (some channels are used by other peripherals). Another uses FIFO memory on the acquisition board to store data at a sample rate fixed by its clock.
The easiest method is polling, where the data input is under application (software) control. Generally, the application reads the analog-to-digital converter output when a delay loop ends. However, the time elapsed depends on the the PC’s internal processor clock. Therefore, polling typically isn’t used when precise sampling is required. Here, the polling technique is adapted to achieve stable platform-independent programmable sampling.
Two special purpose ICs are in the PC’s chip set. One of them, in addition to providing control of several common system functions, includes two 8254 programmable interval timer ICs, offering six 16-bit programmable timers. All of the timers can work as a counter, with a master clock (1.19317 MHz) common to all. This frequency and the programming register addresses don’t vary among different platforms, assuring compatibility. Three timers are available to the user, allowing time generation from 0.8381 µs (1/1.19317 MHz) up to 55 ms (0.8381 µs × 65535). In this adaptation, timer 2, commonly used by the PC speaker for alarms or signaling, generates the time between samples. The timer, an 8254, is independent of the processor clock (see the figure).
The goal is to create a very simple but precise polling mode, usable with any commercial or experimental dataacquisition board. The sample time, ST, is software-generated by the function SetDelay(ST). The application uses the function Acquire(Ch_Num) to call Set-Delay(ST) between consecutive inputs from the A/D port address. Ch_Num identifies the channel number selected. This procedure can be repeated for each analog channel available on the acquisition board. The C++ source code listings for both functions can be found on Electronic Design’s web site, located at www.elecdesign.com (click on the Ideas for Design icon).
Each programmable counter can be programmed in six different ways by loading the Control Register at port 43h and controlling its associated enable gate. Counter 2 is enabled by setting bit 0 at port 61h. Counter 2’s value can be read at port 42h.
The SetDelay() function sets up Counter 2 as a pulse generator by writing b4h to the Control Register. Counting from an initial value (ffffh), the elapsed time can be determined by reading the real counter state in variable c_time and resolving the variable lap_time (µs) = (ffffh − c_time) × 0.8381. The value of lap_time is evaluated inside a loop. When lap_time = time_out (the desired time interval), the loop ends and SetDelay() returns.
The Acquire() function first selects the analog channel on which the samples are acquired by writing its number to port 301h. It then executes a for(;;) loop to acquire the desired number of data samples. The function SetDelay() is inserted between the beginning of a conversion and the reading of the digital value to meet the minimum conversion time requirement of the converter.
Sampling times used by this method can be programmed over a wide range, from 10 µs up to 55 ms. An additional advantage of this method is the timer doesn’t interfere with the WM_TIMER message, a characteristic of Windows applications.
As an example, we could use the Windows Timer to program the time the channel number should change and the function SetDelay() should adjust the sampling rate on that channel. Both timers act independently. To use WM_TIMER, one timer must be initialized by means of the API function SetTimer() in WinMain():
SetTimer(hwnd, nTimer, 100, NULL ); // programs nTimer to generate a WM_TIMER message every 100 ms.
and add the following code to Window-Func():
case WM_TIMER: Acquire( channel_number, sample_rate ); Plot( data ); break;
When the 100-ms interval has elapsed, the application calls the function Acquire(), specifying the active channel_number and the sample_rate. Then the function Plot() is invoked to draw the time-series data.
It’s important to point out that Windows timers aren’t exact because, like mouse and keyboard messages, the timer messages are queued. This is necessary since an interrupt-driven timer message would conflict with the non-preemptive nature of Windows. The usefulness of a timer is that it lets you know that a minimum amount of time has passed. Thus, such a timer can’t be used to get a stable and constant sample period. Our function achieves this goal and can resolve smaller time intervals than allowed by the Windows timer.