Reprogramming time interrupt under DOS

L

Thread Starter

Luca Gallina

Hi all,

I have a small, single task application to be ran on a PC system (currently
an old Pentium 75MHz).
I need to call a function at fast and precise time intervals, the data will
be output to LPT port and then converted to analog by using an external
DAC. Running good old DOS v3.3, I tried to reprogram the 1CH timer
interrupt by modifying the frequency divider but unfortunately it seems
that I cannot achieve anything like a rough microsecond interval.
I would like to get such a timer event so that I can then run the
application on different PCs with different CPU speed without having to
tune my application's loop.

My questions:

1: am I doing anything wrong with the timer reprogramming?

2: is there any alternate, simple and **tiny** solution? Everything (OS and
application) must fit on a 1.44Mb floppy.


I attached below some code written in old Turbo Pascal v5.5 :)

thanks in advance
Luca Gallina

{-------------------------------------------------------------------------}
{$F+,S-}
procedure TimerISR; interrupt;
begin
do_my_task; { <-- my task goes here}
end;
{$F-,S+}


{-------------------------------------------------------------------------}
begin { main }

{--- reprogram the Timer0 interval ---}
RegDivider:= $0001; { <-- assign here timer divider, timer should
hopefully overflow just after counting "1"}
port[$43]:= $36;
port[$40]:= RegDivider and $00FF;
port[$40]:= RegDivider shr 8 and $0FF;

{--- replace the Timer0 interrupt service routine ---}
getintvec($1C, ISR_OldAddress);
setintvec($1C, @TimerISR);

{--- main loop ---}
while not KeyPressed do
begin
end;

{--- restore the default Timer0 interrupt service routine before
terminating---}
RegDivider:= $FFFF; { <-- back to the usual 65535 divider}
port[$43]:= $36;
port[$40]:= RegDivider and $00FF;
port[$40]:= RegDivider shr 8;
setintvec($1C, ISR_OldAddress);
end.
 
I saw that the source code I reported has been messed up (I don't know
why), so I'm sending it again removing brackets on comments


//-------------------------------------------------------------------------
{$F+,S-}
procedure TimerISR; interrupt;
begin
do_my_task; { <-- my task goes here}
end;
{$F-,S+}

//---------------------------------
begin

//--- reprogram the Timer0 interval ---

// assign here timer divider, timer should hopefully overflow just after
counting "1"
RegDivider:= $0001;

port[$43]:= $36;
port[$40]:= RegDivider and $00FF;
port[$40]:= RegDivider shr 8 and $0FF;

//--- replace the Timer0 interrupt service routine ---
getintvec($1C, ISR_OldAddress);
setintvec($1C, @TimerISR);

// --- main loop ---
while not KeyPressed do
begin
end;

//---- restore the default Timer0 interrupt service routine before
terminating
RegDivider:= $FFFF; // <-- back to the usual 65535 divider
port[$43]:= $36;
port[$40]:= RegDivider and $00FF;
port[$40]:= RegDivider shr 8;
setintvec($1C, ISR_OldAddress);
end.
 
M

Michael Griffin

You are apparently looking to output a fairly accurate waveform at a rate of 1 mega-samples per second. This is not easy, the reason for which I will explain below.

The 8253 timer chip is fed by a 1,193,180 Hz clock. This frequency is approximate, and will vary slightly between computers and with temperature and the age of the clock crystal. This means that under ideal conditions, the closest you can get to 1 Mz is either 0.838 MHz or 1.676 MHz. This assumes that the PC chip set is doing a very good job of 8253 emulation, as there aren't any 8253 chips in PCs anymore.

The famous 55 ms timing resolution in DOS and Windows 95/98/ME by the way, came from running this chip with the largest possible preset to get the slowest possible interrupt rate to minimise time keeping overhead (1/(1,193,180/65536) = 0.0549).

When the chip counts out, it triggers an interrupt (8, if I recall correctly), following which the BIOS calls software interrupt 1C (which you are hooking). Next, your ISR gets called (because it was hooked into 1C). After this your application needs to do some additional work (which you haven't specified) before outputting this to LPT.

By the way, when you play with the timer preset, you have to make sure you fix up the side effects. Various MS-DOS functions rely on this for things like time of day or time outs (e.g. floppy drive time out). The usual way is to hook int 1C, but to keep track of the intervals with a software counter and then pass the interrupt through to the other functions at the correct interval.

You wish to do this with a 75MHz PC. Let us be optimistic, and assume that on average we can execute one machine code instruction in 5 clock cycles (this is probably overly optimistic). This means that we have to do all this work with less than 15 machine code instructions. We are also assuming that nothing else will happen in this time span, and that interrupts will never be masked by another function. On top of this, you have the question of how to physically strobe the signal out of the LPT port at that rate.

I think you can see the difficulties involved. If you said you wished to do this at 1 milli-second intervals, then it might (with careful design) be possible. I don't believe that 1 MHz is realistic though.

I had an application running on PCs of about the speed you are using which ran a polling loop at a rate of about 5 ms (as measured with an oscilloscope). This application by the way, needed a high resolution timer base (just reading the time, not via an ISR) which was derived by reading the 8253 timer chip which I had reprogrammed to high resolution mode. That should give you an idea of the amount of software overhead you could easily be dealing with if you have a moderately complex application.

If you really need an accurate 1 mega-samples per second output, then you need to do this with hardware (which might include a DSP). If you are trying to output an arbitrary waveform, then the normal way of doing this with a PC is to generate the waveform ahead of time, store it in RAM, and then to use a data acquisition board to output the stored waveform (the waveform is usually stored in an on-board buffer, or large chunks are moved to an on board ring buffer at regular intervals).

For data acquisition (DAQ) boards, a low end board operates at up to 50 thousand samples per second (ksps). A typical mid-range board runs at 200 ksps. High end boards run at 1 to 5 Msps (mega-sps). These boards of course have a lot more functions than you require, but I think you can get a general idea of the level of difficulty.

The "easy" answer is to just buy a DAQ board. There are also other possibly cheaper and more convenient possibilities, but it is difficult to give any useful suggestions without knowing what the actual application is.
 
R

Robert Scott

Do you really expect to service a timer interrupt every microsecond? I don't think your 75 MHz Pentium can do that. Even if you do succeed in establishing a faster timer interrupt, don't forget that the old timer interrupt had a purpose. You can't just ignore it during your application. For one thing, the timer interrupt maintains time-of-day. Maybe you don't care if time-of-day gets messed up. Another function of the old timer interrupt was to time-out the floppy drive. If you disable the old timer interrupt code, the floppy will continue to turn (if it was turning to begin with). But the biggest problem is that you just can't make a PC running DOS respond to an interrupt at the "rough microsecond" rate.

Robert Scott
Real-Time Specialties
Embedded Systems Consulting
 
R

Rokicki, Andrew (Andrew)

I have looked at what you are doing and it seems OK. One suggestion is that TimerISR need to be very short since your interrupts will happen often and at short time intervals. Also try changing frequency to a bigger numbers and see what happens, this might give you a clue what might be the problem.
 
Top