home *** CD-ROM | disk | FTP | other *** search
/ Collection of Hack-Phreak Scene Programs / cleanhpvac.zip / cleanhpvac / PCGPEV10.ZIP / PIT.TXT < prev    next >
Text File  |  1994-05-10  |  12KB  |  298 lines

  1.          ┌────────────────────────────────────────────────────────┐
  2.          │ Programming the Intel 8253 Programmable Interval Timer │
  3.          └────────────────────────────────────────────────────────┘
  4.  
  5.               Written for the PC-GPE by Mark Feldman
  6.               e-mail address : u914097@student.canberra.edu.au
  7.                                myndale@cairo.anu.edu.au
  8.  
  9.                 ┌───────────────────────────────────────────┐
  10.                 │      THIS FILE MAY NOT BE DISTRIBUTED     │
  11.                 │ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. │
  12.                 └───────────────────────────────────────────┘
  13.  
  14.  
  15. ┌────────────┬───────────────────────────────────────────────────────────────
  16. │ Disclaimer │
  17. └────────────┘
  18.  
  19. I assume no responsibility whatsoever for any effect that this file, the
  20. information contained therein or the use thereof has on you, your sanity,
  21. computer, spouse, children, pets or anything else related to you or your
  22. existance. No warranty is provided nor implied with this information.
  23.  
  24. ┌──────────────┬─────────────────────────────────────────────────────────────
  25. │ Introduction │
  26. └──────────────┘
  27.  
  28. The PIT chip has 3 channels, each of which are responsible for a different
  29. task on the PC:
  30.  
  31. Channel 0 is responsible for updating the system clock. It is usually
  32. programmed to generate around 18.2 clock ticks a second. An interrupt 8 is
  33. generated for every clock tick.
  34.  
  35. Channel 1 controls DMA memory refreshing. DRAM is cheap, but it's memory
  36. cells must be periodically refreshed or they quickly lose their charge. The
  37. PIT chip is responsible for sending signals to the DMA chip to refresh
  38. memory. Most machines are refreshed at a higher rate than neccesary, and
  39. reprogramming channel 1 to refresh memory at a slower rate can sometime speed
  40. up system performance. I got a 2.5 MHz speed-up when I did it to my 286, but
  41. it didn't seem to work on my 486SUX33.
  42.  
  43. Channel 2 is connected to the speaker. It's normally programmed to generate
  44. a square wave so a continuous tone is heard. Reprogramming it for "Interrupt
  45. on Terminal Count" mode is a nifty trick which can be used to play 8-bit
  46. samples from the PC speaker.
  47.  
  48. Each channel has a counter which counts down. The PIT input frequency is
  49. 1193181 ($1234DD) Hz. Each counter decrements once for every input clock
  50. cycle. "Terminal Count", mentioned several times below, is when the counter
  51. reaches 0.
  52.  
  53. Loading the counters with 0 has the same effect as loading them with 10000h,
  54. and is the highest count possible (approx 18.2 Hz).
  55.  
  56. ┌───────────────┬────────────────────────────────────────────────────────────
  57. │ The PIT Ports │
  58. └───────────────┘
  59.  
  60. The PIT chip is hooked up to the Intel CPU through the following ports:
  61.  
  62.                 ┌───────────────────────────────────────┐
  63.                 │ Port   Description                    │
  64.                 ├───────────────────────────────────────┤
  65.                 │ 40h    Channel 0 counter (read/write) │
  66.                 │ 41h    Channel 1 counter (read/write) │
  67.                 │ 42h    Channel 2 counter (read/write) │
  68.                 │ 43h    Control Word (write only)      │
  69.                 └───────────────────────────────────────┘
  70.  
  71. ┌──────────────────┬─────────────────────────────────────────────────────────
  72. │ The Control Word │
  73. └──────────────────┘
  74.  
  75.               ┌───┬───┬───┬───┬───┬───┬───┬───┐
  76.               │ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
  77.               └───┴───┴───┴───┴───┴───┴───┴───┘
  78.                 └─┬─┘   └─┬─┘   └───┬───┘   └── BCD 0 - Binary 16 bit
  79.                   │       │         │               1 - BCD 4 decades
  80. ┌─────────────────┴────┐  │         │
  81. │ Select Counter       │  │         └────────── Mode Number 0 - 5
  82. │ 0 - Select Counter 0 │  │
  83. │ 1 - Select Counter 1 │  │         ┌────────────────────────────┐
  84. │ 2 - Select Counter 2 │  │         │ Read/Load                  │
  85. └──────────────────────┘  │         │ 0 - Counter Latching       │
  86.                           └─────────┤ 1 - Read/Load LSB only     │
  87.                                     │ 2 - Read/Load MSB only     │
  88.                                     │ 3 - Read/Load LSB then MSB │
  89.                                     └────────────────────────────┘
  90.  
  91. ┌───────────────┬────────────────────────────────────────────────────────────
  92. │ The PIT Modes │
  93. └───────────────┘
  94.  
  95. The PIT is capable of operating in 6 different modes:
  96.  
  97. MODE 0 - Interrupt on Terminal Count
  98. ────────────────────────────────────
  99. When this mode is set the output will be low. Loading the count register
  100. with a value will cause the output to remain low and the counter will start
  101. counting down. When the counter reaches 0 the output will go high and remain
  102. high until the counter is reprogrammed. The counter will continue to count
  103. down after terminal count is reached. Writing a value to the count register
  104. during counting will stop the counter, writing a second byte starts the
  105. new count.
  106.  
  107. MODE 1 - Programmable One-Shot
  108. ──────────────────────────────
  109. The output will go low once the counter has been loaded, and will go high
  110. once terminal count has been reached. Once terminal count has been reached
  111. it can be triggered again.
  112.  
  113. MODE 2 - Rate Generator
  114. ───────────────────────
  115. A standard divide-by-N counter. The output will be low for one period of the
  116. input clock then it will remain high for the time in the counter. This cycle
  117. will keep repeating.
  118.  
  119. MODE 3 - Square Wave Rate Generator
  120. ───────────────────────────────────
  121. Similar to mode 2, except the ouput will remain high until one half of the
  122. count has been completed and then low for the other half.
  123.  
  124. MODE 4 - Software Triggered Strobe
  125. ──────────────────────────────────
  126. After the mode is set the output will be high. Once the count is loaded it
  127. will start counting, and will go low once terminal count is reached.
  128.  
  129. MODE 5 - Hardware Triggered Strobe
  130. ──────────────────────────────────
  131. Hardware triggered strobe. Similar to mode 5, but it waits for a hardware
  132. trigger signal before starting to count.
  133.  
  134. Modes 1 and 5 require the PIT gate pin to go high in order to start
  135. counting. I'm not sure if this has been implemented in the PC.
  136.  
  137. ┌──────────────────┬─────────────────────────────────────────────────────────
  138. │ Counter Latching │
  139. └──────────────────┘
  140.  
  141. Setting the Read/Load field in the Control Word to 0 (Counter Latch) causes
  142. the appropriate channel to go into a sort of "lap" mode, the counter keeps
  143. counting down internally but it appears to have stopped if you read it's
  144. values through the channel's counter port. In this way you get a stable count
  145. value when you read the counter. Once you send a counter latch command you
  146. *must* then read the counter.
  147.  
  148. ┌────────────────────────┬───────────────────────────────────────────────────
  149. │ Doing Something Useful │
  150. └────────────────────────┘
  151.  
  152. Ok, so let's say we are writing a game and we need to have a certain
  153. routine called 100 times a second and we want to use channel 0 to do all
  154. this timing in the background while the main program is busy doing other
  155. stuff.
  156.  
  157. The first thing we have to realise is that BIOS usually uses channel 0 to
  158. keep track of the time, so we have 3 options:
  159.  
  160. 1) Have our own routine handle all timer interrupts. This will effectively
  161.    stop the PC clock and the system time will be wrong from that point on.
  162.    The clock will be reset to the proper time the next time the computer
  163.    is turned off and on again, but it's not a nice thing to do to someone
  164.    unless you really have to.
  165.  
  166. 2) Have our routine do whatever it has to do and then call the BIOS handler.
  167.    This would be fine if our program was receiving the usual 18.2 ticks
  168.    a second, but we need 100 a second and calling the BIOS handler for every
  169.    tick will speed up the system time. Same net result as case 1.
  170.  
  171. 3) Have our routine do the interrupt handling and call the BIOS handler only
  172.    when it needs to be updated! BINGO!
  173.  
  174. The PIT chip runs at a freqency of 1234DDh Hz, and normally the BIOS timer 
  175. interrupt handler is called for every 10000h cycles of this clock. First we 
  176. need to reprogram channel 0 to generate an interrupt 100 times a second, ie 
  177. every 1234DDh / 100 = 11931 cycles. The best thing to do is keep a running 
  178. total of the number of clock ticks which have occurred. For every interrupt 
  179. generated we will add 11931 to this total. When it reaches 10000h our handler 
  180. will know it's time to tell BIOS about it and do so.
  181.  
  182. So let's get into some good old Pascal code. First we'll define a few
  183. constants and variables our program will need:
  184.  
  185. ──────────────────────────────────────────────────────────────────────┐
  186. Uses Crt, Dos;
  187.  
  188. {$F+} { Force far mode, a good idea when mucking around with interrupts }
  189.  
  190. const TIMERINTR = 8;
  191.        PIT_FREQ = $1234DD;
  192.  
  193. var BIOSTimerHandler : procedure;
  194.     clock_ticks, counter : longint;
  195. ──────────────────────────────────────────────────────────────────────┘
  196.  
  197. The clock_ticks variable will keep track of how many cycles the PIT has
  198. had, it'll be intialised to 0. The counter variable will hold the new
  199. channel 0 counter value. We'll also be adding this number to clock_ticks
  200. every time our handler is called.
  201.  
  202. Next we need to do some initialization:
  203.  
  204. ──────────────────────────────────────────────────────────────────────┐
  205. procedure SetTimer(TimerHandler : pointer; frequency : word);
  206. begin
  207.  
  208.   { Do some initialization }
  209.   clock_ticks := 0;
  210.   counter := $1234DD div frequency;
  211.  
  212.   { Store the current BIOS handler and set up our own }
  213.   GetIntVec(TIMERINTR, @BIOSTimerHandler);
  214.   SetIntVec(TIMERINTR, TimerHandler);
  215.  
  216.   { Set the PIT channel 0 frequency }
  217.   Port[$43] := $34;
  218.   Port[$40] := counter mod 256;
  219.   Port[$40] := counter div 256;
  220. end;
  221. ──────────────────────────────────────────────────────────────────────┘
  222.  
  223. Pretty straightforward stuff. We save the address of the BIOS handler,
  224. install our own, set up the variables we'll use and program PIT channel 0
  225. for the divide-by-N mode at the frequency we need.
  226.  
  227. This next bit is what we need to do once our program is finished. It just
  228. resets everything back to normal.
  229.  
  230. ──────────────────────────────────────────────────────────────────────┐
  231. procedure CleanUpTimer;
  232. begin
  233.   { Restore the normal clock frequency }
  234.   Port[$43] := $34;
  235.   Port[$40] := 0;
  236.   Port[$40] := 0;
  237.  
  238.   { Restore the normal ticker handler }
  239.   SetIntVec(TIMERINTR, @BIOSTimerHandler);
  240. end;
  241. ──────────────────────────────────────────────────────────────────────┘
  242.  
  243.  
  244. Ok, here's our actual handler. This particular handler just writes an
  245. asterix (*) to the screen. Then it does the checks to see if the BIOS
  246. handler should be called. If so it calls it, if not it acknowledges the
  247. interrupt itself.
  248.  
  249. ──────────────────────────────────────────────────────────────────────┐
  250.  
  251. procedure Handler; Interrupt;
  252. begin
  253.  
  254.   { DO WHATEVER WE WANT TO DO IN HERE }
  255.   Write('*');
  256.  
  257.   { Adjust the count of clock ticks }
  258.   clock_ticks := clock_ticks + counter;
  259.  
  260.   { Is it time for the BIOS handler to do it's thang? }
  261.   if clock_ticks >= $10000 then
  262.     begin
  263.  
  264.       { Yep! So adjust the count and call the BIOS handler }
  265.       clock_ticks := clock_ticks - $10000;
  266.  
  267.       asm pushf end;
  268.       BIOSTimerHandler;
  269.     end
  270.  
  271.   { If not then just acknowledge the interrupt }
  272.   else
  273.     Port[$20] := $20;
  274. end;
  275.  
  276. ──────────────────────────────────────────────────────────────────────┘
  277.  
  278. And finally our calling program. What follows is just an example program
  279. which sets everything up, waits for us to press a key and then cleans up
  280. after itself.
  281.  
  282. ──────────────────────────────────────────────────────────────────────┐
  283. begin
  284.   SetTimer(Addr(Handler), 100);
  285.   ReadKey;
  286.   CleanUpTimer;
  287. end.
  288. ──────────────────────────────────────────────────────────────────────┘
  289.  
  290.  
  291. ┌────────────┬───────────────────────────────────────────────────────────────
  292. │ References │
  293. └────────────┘
  294.  
  295. Title : "Peripheral Components"
  296. Publisher : Intel Corporation
  297. ISBN : 1-55512-127-6
  298.