Advertisement
Guest User

Untitled

a guest
Sep 18th, 2014
47
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. {$G-,R-,S-,Q-}
  2. {{$DEFINE QUANTIZETIMER}
  3. {If defined, results from the timer method are adjusted by discarding the
  4. least-significant bits.  The faster a machine you have, the less this is
  5. necessary; if you have 12MHz or higher, don't use it at all.}
  6.  
  7. {
  8. Unit to read joystick values from a db15 analog joystick.
  9. 20060225, trixter@oldskool.org.  Some information by Lou Duchez; assembler
  10. implementations inspired by James P. McAdams and Bret Johnson
  11. 20090309: altered to use constants that make sense from TInterrupts;
  12. adding quantization for timer-based method
  13. 20110304: Cleaned up code and made sure channel 0 was initialized to
  14. mode 2 which provides consistent results.
  15. 20130501: Added digital directions and recalibration
  16.  
  17. Background:
  18.  
  19. The basic approach to reading a joystick is to monitor port 201h.  The eight
  20. bits at that port correspond to:
  21.  
  22. 01h - Joystick A, "X" position
  23. 02h - Joystick A, "Y" position
  24. 04h - Joystick B, "X" position
  25. 08h - Joystick B, "Y" position
  26. 10h - Joystick A, button 1
  27. 20h - Joystick A, button 2
  28. 40h - Joystick B, button 1
  29. 80h - Joystick B, button 2
  30.  
  31. The buttons are easy: a bit of "0" means "pressed" and "1" means "not
  32. pressed".  But how do you get a variable X and Y axis from a single bit?!
  33. Here's what you do:
  34.  
  35. 1. Write a value -- any value -- to port 201h.  The four lowest bits will then
  36. all assume a value of "1".
  37.  
  38. 2. Start a counter, and see how many iterations it takes for your desired bit
  39. to go to zero.  The number of iterations = the joystick position, with lower
  40. values corresponding to "left" or "up" and higher values corresponding to
  41. "right" or "down".
  42.  
  43. Now, what method to use to get the values?  You can do one of three things:
  44.  
  45. 1. For maximum accuracy, LOOP with CX.  The CX register, coupled with LOOPNZ,
  46. is the fastest method of doing this and results in the highest resolution.
  47. Unfortunately, it requires interrupts to be turned off during measuring.
  48.  
  49. 2. On a machine with a BIOS date after November 8th, 1982, call the
  50. Int 15h,84h BIOS routine.  On a real PC/XT, this employs method #1 for
  51. all 4 axis and buttons (other implementations may vary).
  52.  
  53. 3. For compatibility across varying CPU speeds and lightest system footprint,
  54. use the 8259 timer.  The returned values are constant across all PCs.
  55.  
  56. If you're writing a game, the system timer method is best.  If you're using
  57. the joystick as an all-points-addressable device (such as a drawing
  58. program), the CX/LOOPNZ method is best (assuming the speed of the machine is
  59. not variable).
  60.  
  61. Misc. notes:
  62.  
  63. - There is a bug in the implementation of some joysticks/adapters in that,
  64. after waiting for one axis to go to 0, you need to wait for the other one to
  65. go to 0 as well.  If you don't, and you fire the one-shots to read the stick a
  66. second time before the other axis bit has settled, your results are
  67. unpredictable (ie. wrong, stuck, always clear, etc.).
  68.  
  69. - You may be tempted to write a single routine that monitors all four bits and
  70. spits out all four values, does the waiting, etc. -- but since that takes
  71. time, on a slow machine all that processing can actually affect
  72. the results!  I've personally measured this on a 4.77MHz 8088; extended
  73. processing of the bits takes so long that the numbers returned by the
  74. timer-based routine start to become very coarse.  This unit intentionally
  75. treats each axis in a seperate block of code to ensure the least amount of
  76. processing per axis, resulting in the most granular results possible.
  77.  
  78. - Jordan Knight informed me that you can't write random data to the one-shots,
  79. you have to write all 1's or very early adapters won't work.
  80.  
  81. - If you want to use the BIOS, your machine must be made after November
  82. 8th, 1982, because that's when IBM started putting the code in there.
  83. Here is how:
  84.  
  85.                      INT 15,84 - Joy-Stick Support
  86.  
  87.         AH = 84h
  88.         DX = 0 to read the current switch settings
  89.            = 1 to read the joystick position (resistive inputs)
  90.  
  91.         on return (DX=0, read switch setting):
  92.         CF = 0 if successful
  93.            = 1 if error
  94.         AH = 80h error code if CF set on PC
  95.            = 86h error code if CF set on XT before 11/8/82
  96.         AL = switch settings in bits 7-4 (if read switch function)
  97.  
  98.         on return (DX=1, read joystick position):
  99.         AX = A(X)
  100.         BX = A(Y)
  101.         CX = B(X)
  102.         DX = B(Y)
  103. }
  104.  
  105. unit joystick;
  106.  
  107. interface
  108.  
  109. type
  110.   polltype=(p_timer,p_loop,usebios);
  111.   joyCoordType=record
  112.     x,y:word;
  113.   end;
  114.  
  115. const
  116.   JoyPortAddr:word=$201;
  117.   {bits for joystick_pos and buttons}
  118.   jax=$01; {joystick a, x axis}
  119.   jay=$02; {joystick a, y axis}
  120.   jbx=$04; {joystick b, x axis}
  121.   jby=$08; {joystick b, y axis}
  122.   ja1=$10; {joystick a, button 1}
  123.   ja2=$20; {joystick a, button 2}
  124.   jb1=$40; {joystick b, button 1}
  125.   jb2=$80; {joystick b, button 2}
  126.   {unit variables}
  127.   GotA:boolean=false;
  128.   GotB:boolean=false;
  129.   centerA:joyCoordType=(x:0;y:0);
  130.   centerB:joyCoordType=(x:0;y:0);
  131.   {digital directions:}
  132.   jau=$01; {up}
  133.   jad=$02; {down}
  134.   jal=$04; {left}
  135.   jar=$08; {right}
  136.   jbu=$10;
  137.   jbd=$20;
  138.   jbl=$40;
  139.   jbr=$80;
  140.  
  141.   pollmethod:polltype=p_timer; {This *must* be p_timer for this units's
  142.   start-up code to work on all machines.}
  143.  
  144. function joystick_position(which_bit:byte):word;
  145. function joystick_button(which_bit:byte):boolean;
  146. function joystick_buttons:byte;
  147. function joystick_direction:byte;
  148. procedure joystick_recenter;
  149.  
  150. implementation
  151.  
  152. uses
  153.   TInterrupts;
  154.  
  155. const
  156.   J_maxtimer=9999; {Maximum number of 0.8381 usec ticks}
  157.   j_maxpolls=$4ff; {Maximum number of times we're willing to loop polling the
  158.   joystick before we give up (cpu too fast, joystick not working, etc.)
  159.   This number was derived from the IBM PC/XT BIOS disassembly; it limits the
  160.   range from 0-1279.  The maximum number obtained from a 1.8 GHz Athlon was
  161.   less than 1100 so it seems this number is okay.  In fact, since BIOS code
  162.   runs faster than code in main memory, it is especially safe for us to use
  163.   this number.}
  164.  
  165. function joystick_button;assembler;
  166. {readport high-level:  result:=(port[$201] and mask)=0}
  167. asm
  168.   mov    bl,pollmethod
  169.   cmp    bl,usebios     {compare; BIOS method requested?}
  170.   jne    @readport      {no?  then jmp to port method, otherwise fall through}
  171.  
  172. @askbios:
  173.   mov     ax,8400h       {AH=function call; set AL=0 for later}
  174.   xor     dx,dx          {0 = read switch settings}
  175.   int     15h
  176.   jc      @buttonexit    {carry set?  if so, something went wrong}
  177.   jmp     @returnbutton  {if not, process our bits}
  178.  
  179. @readport:
  180.   mov     dx,JoyPortADDR {PorT ADDR of JOYSTICKS}
  181.   mov     ah,which_bit   {MASK For DESIRED 1-SHOT}
  182.   in      al,dx          {get joystick port bits}
  183.  
  184. @returnbutton:
  185.   not     al             {invert so that 1=pressed, 0=not pressed}
  186.   and     al,which_bit   {mask off our bit... al is our return}
  187. @buttonexit:
  188. end;
  189.  
  190. function joystick_buttons;assembler;
  191. {readport high-level:  result:=(port[$201] xor $FF) and $F0}
  192. asm
  193.   mov     dx,JoyPortADDR {port addr of joysticks}
  194.   in      al,dx          {get joystick port bits}
  195.   not     al             {invert so that 1=pressed, 0=not pressed}
  196.   and     al,$f0         {mask off axis bits, leaving button bits}
  197. end;
  198.  
  199. function joystick_position;assembler;
  200. {
  201. Returns:
  202.   ax=number of timer ticks or software loops as joystick position value.
  203. }
  204. asm
  205.   jmp    @start
  206. @masks:
  207.   {masks that match either stick A or B based on which_bit}
  208.          {0000 0001 0010 0011 0100 0101 0110 0111 1000}
  209.   db         0,   3,   3,   0,  12,   0,   0,   0,  12
  210. @start:
  211.   mov    dx,JoyPortADDR {used in both methods' inner loops}
  212.   mov    bl,pollmethod
  213.   cmp    bl,p_loop      {compare; loop method requested?}
  214.   je     @loop_method
  215.   cmp    bl,usebios     {BIOS method requested?}
  216.   je     @bios_method
  217.   {otherwise, fall through to timer method}
  218.  
  219. @timer_method:
  220.   mov    bl,which_bit   {mask for desired bit}
  221.                         {Channel 0, Latch Counter, Rate Generator, Binary}
  222.   mov    bh,iMC_Chan0+iMC_LatchCounter+iMC_OpMode2+iMC_BinaryMode
  223.   mov    cx,j_maxtimer  {maximum compare value for inner loop below}
  224.   mov    al,bh          {Begin building timer count}
  225.   mov    di,$FFFF       {value to init the one-shots with}
  226.   pushf                 {Save interrupt state}
  227.   cli                   {Disable interrupts so our operation is atomic}
  228.   out    43h,al         {Tell timer about it}
  229.   in     al,40h         {Get LSB of timer counter}
  230.   xchg   al,ah          {Save it in ah (xchg accum,reg is 3c 1b}
  231.   in     al,40h         {Get MSB of timer counter}
  232.   popf                  {Restore interrupt state}
  233.   xchg   al,ah          {Put things in the right order; AX:=starting timer}
  234.   xchg   di,ax          {load AX with 1's, while storing AX into DI for further comparison}
  235.   out    dx,al          {write all 1's to start the one-shots}
  236. @read:
  237.   mov    al,bh          {Use same Mode/Command as before (latch counter, etc.)}
  238.   pushf                 {Save interrupt state}
  239.   cli                   {Disable interrupts so our operation is atomic}
  240.   out    43h,AL         {Tell timer about it}
  241.   in     al,40h         {Get LSB of timer counter}
  242.   xchg   al,ah          {Save it in ah for a second}
  243.   in     al,40h         {Get MSB of timer counter}
  244.   popf                  {Restore interrupt state}
  245.   xchg   al,ah          {AX:=new timer value}
  246.   mov    si,di          {copy original value to scratch}
  247.   sub    si,ax          {subtract new value from old value}
  248.   cmp    si,cx          {compare si to maximum time allowed}
  249.   ja     @nostick       {if above, then we've waited too long -- blow doors}
  250.   in     al,dx          {if we're still under the limit, read all eight bits}
  251.   test   al,bl          {check axis bit we care about}
  252.   jnz    @read          {loop while the bit tested isn't zero yet}
  253.   {$IFDEF QUANTIZETIMER}{see top of file for explanation}
  254.   shr    si,1
  255.   shr    si,1
  256.   shr    si,1
  257.   {$ENDIF}
  258.   jmp    @joy_exit      {si holds number of timer ticks gone by}
  259.  
  260. @loop_method:
  261.   mov    cx,j_maxpolls  {number of ticks to count down}
  262.   mov    si,cx          {used later}
  263.   mov    ah,which_bit   {mask for desired bit}
  264.   mov    al,$FF         {set up al for all 1's to write to one-shots}
  265.   pushf                 {save interrupt flag state, in case we are being called from an interrupt-driven procedure ourselves}
  266.   cli                   {turn off interrupts so our timing loop isn't affected}
  267.   out    dx,al          {write all 1's to start the one-shots}
  268.  
  269. @readit:
  270.   in     al,dx          {read all eight bits}
  271.   test   al,ah          {check desired bit}
  272.   loopnz @readit        {loop while the bit tested isn't zero}
  273.   popf                  {turn interrupts back on}
  274.   jcxz   @nostick       {if cx is 0 then we timed out and should abort}
  275.   sub    si,cx          {si:=j_maxpolls - ticks counted}
  276.   jmp    @joy_exit
  277.  
  278. @bios_method:
  279.   mov    ax,8400h       {joystick function}
  280.   mov    dx,1           {read sticks}
  281.   xor    si,si          {assume the worst}
  282.   int    15h            {do it}
  283.   jc     @returnticks   {error?  si already 0, jmp to done}
  284.   {process int15 -- this isn't efficient if you ONLY want to use BIOS method}
  285.   mov    si,ax          {assume we wanted jax}
  286.   test   which_bit,jax  {were we right?}
  287.   jnz    @returnticks   {jump if desired AND jax <> 0}
  288.   mov    si,bx          {assume jay}
  289.   test   which_bit,jay
  290.   jnz    @returnticks
  291.   mov    si,cx          {assume jbx}
  292.   test   which_bit,jbx
  293.   jnz    @returnticks
  294.   mov    si,dx          {oh well, must have been jby}
  295.   jmp    @returnticks   {don't need to wait for bits to settle if using BIOS routine}
  296.  
  297. @nostick:
  298.   xor    si,si          {no stick?  return 0}
  299.  
  300. @joy_exit:
  301.   (*mov    ah,00001111b   {mask all four axis bits --
  302.   SLOW if other stick not present!  So we only pick mask we need:}*)
  303.   lea    bx,@masks      {point BX to our mask table}
  304.   mov    al,which_bit
  305.   segcs  xlat           {load proper mask (A or B) for our stick}
  306.   mov    ah,al          {(ie. mask for only joystick we care about)}
  307.   mov    cx,j_maxpolls
  308. @clearstick:            {This took me days to find.  You have to wait}
  309.   in     al,dx          {for all stick AXIS bits to go to 0 before you can}
  310.   test   al,ah          {reliably read stick again!  See Misc. notes above}
  311.   loopnz @clearstick
  312.  
  313. @returnticks:
  314.   mov    ax,si          {return # of ticks as the value}
  315. end;
  316.  
  317. function joystick_direction:byte;
  318. {
  319. Idea is to present user with a byte that contains bits showing if the stick
  320. is pointing up, down, left, or right -- like an atari joystick.  This routine
  321. assumes a 50% deadzone to cut down on the user involvement.
  322. }
  323. var
  324.   b:byte;
  325.   w:word;
  326.   newreada,newreadb:joyCoordType;
  327. begin
  328.   b:=0;
  329.   if gota then begin
  330.     newreada.x:=joystick_position(jax);
  331.     w:=centera.x shr 1;
  332.     if newreada.x < centera.x - w then b:=b OR jal;
  333.     if newreada.x > centera.x + w then b:=b OR jar;
  334.     newreada.y:=joystick_position(jay);
  335.     w:=centera.y shr 1;
  336.     if newreada.y < centera.y - w then b:=b OR jau;
  337.     if newreada.y > centera.y + w then b:=b OR jad;
  338.   end;
  339.   if gotb then begin
  340.     newreadb.x:=joystick_position(jbx);
  341.     w:=centerb.x shr 1;
  342.     if newreadb.x < centerb.x - w then b:=b OR jbl;
  343.     if newreadb.x > centerb.x + w then b:=b OR jbr;
  344.     newreadb.y:=joystick_position(jby);
  345.     w:=centerb.y shr 1;
  346.     if newreadb.y < centerb.y - w then b:=b OR jbu;
  347.     if newreadb.y > centerb.y + w then b:=b OR jbd;
  348.   end;
  349.   joystick_direction:=b;
  350. end;
  351.  
  352. Procedure joystick_recenter;
  353. {recalibrates digital joystick directions}
  354.  
  355. const
  356.   numavgs=5; {number of samples to gather before calcing center results}
  357.  
  358. var
  359.   avgw:word; {used for averaging initial center stick results}
  360.   avgb:byte;
  361.  
  362. begin
  363.   {Set initial center position (and hope user has calibrated stick)
  364.   for use with the "direction" function.}
  365.   if gota then begin
  366.     avgw:=0; for avgb:=0 to numavgs-1 do avgw:=avgw+joystick_position(jax); centera.x:=avgw div numavgs;
  367.     avgw:=0; for avgb:=0 to numavgs-1 do avgw:=avgw+joystick_position(jay); centera.y:=avgw div numavgs;
  368.   end;
  369.   if gotb then begin
  370.     avgw:=0; for avgb:=0 to numavgs-1 do avgw:=avgw+joystick_position(jbx); centerb.x:=avgw div numavgs;
  371.     avgw:=0; for avgb:=0 to numavgs-1 do avgw:=avgw+joystick_position(jby); centerb.y:=avgw div numavgs;
  372.   end;
  373. end;
  374.  
  375. begin
  376.   {init channel 0, 3=access mode lobyte/hibyte, mode 2, 16-bit binary}
  377.   {We do this so we can get a sensible countdown value from mode 2 instead
  378.   of the 2xspeedup var from mode 3.  I have no idea why old BIOSes init
  379.   mode 3; everything 486 and later inits mode 2.  Go figure.  This should
  380.   not damage anything in DOS or TSRs, in case you were wondering.}
  381.   InitChannel(0,3,2,$0000);
  382.  
  383.   {Set "GotA and "GotB" booleans by checking for "0" from the X axis
  384.   of both sticks.  This is "safe" because it uses the most compatible
  385.   method, timer-based polling.}
  386.   GotA:=(joystick_position(jax)<>0);
  387.   GotB:=(joystick_position(jbx)<>0);
  388.   joystick_recenter;
  389. end.
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement