The method of talking to the board is pretty standard. First, you write to the base register to tell it what you intend to do next. This is done because the registers are read/write for many possible data items; e.g. register BASE + 4 may be used to pass a byte off to a counter or to a A/D unit, so by writing to the BASE you tell it what the BASE + 4 destination is.
In creating this application we ran across a couple of interesting things. In the days of Turbo Pascal through Delphi 1.0, there was a built-in set of routines used strictly to talk to hardware: inportb, inport, outportb, and outport. The "b" suffix denoted 1 byte. In Delphi beyond 1.0, these arrays were missing. It seems as though the powers that be didn't want us to talk to hardware easily. No problem, a simple bit of assembler does the trick. Just use the OUT instruction and specify the port, as follows:
OUT $300, $FF { output FFh to I/O port 300h }
But this didn't work either! That's funny, it worked in all
of the previous compiler versions, and it's a standard instruction
on an Intel chip... and then it hit us --
we are not allowed to use a literal mode of addressing to talk to
hardware! (And the government is talking to Microsoft
about BROWSERS??) What to do? Microsoft would like us to use a driver,
obviously, but if one can't talk to the address, how are we supposed
to write a driver? Not a problem. The handy assembler references say
that you can also use indirect addressing:
Since we weren't writing this as a consumer app, this worked well
enough that we decided to bypass the driver. The following set of
routines show how we managed to:
Note that the assembler routines do not have an explicit return; the
return is implicit and whatever you place into the AX register before
terminating. Delphi's built-in assembler handles all of the stack
manipulation and RET handling for you, which makes assembler coding
pretty simple.
Also, note that we're manipulating the data quite a
bit before returning. This is because we're already accessing the
registers and the data needs to be manipulated anyway, so what we
do is force the compiler to do the work we need done using the least
number of clock cycles possible. For instance, we could have just as
easily returned the raw contents of the counter and used some basic
multiplication etc. in Delphi to format it to be useful. On the other
hand the basic XOR or SHR take a minimum number of
clocks and do the same job even faster. When in doubt, you should
always assume that the compiler is a general purpose device and is
not interested in optimizing clocks (or able to.)
Here then is the code for your enjoyment.
MOV AX, $FF { value in AX }
MOV DX, $300 { I/O port number }
OUT DX, AX ( same OUT as before but with register references }
procedure zerocounterzero;
begin
asm
MOV AL, 00110000b { write 2 bytes @ counter 0 }
MOV DX, 0307h {counter 0 is at BASE + 7 }
OUT DX, AL
MOV AL, 00h { our bytes will be 0 }
MOV DX, 0304h { counter 0 := $0304}
OUT DX, AL { write LSB = 0 }
OUT DX, AL { write MSB = 0 }
end;
end;
function readcounterzero: smallint; assembler;
asm
MOV AL, 00000000b { we want to latch }
MOV DX, 0307h
OUT DX, AL { do it }
MOV DX, 0304h { counter 0 }
IN AL, DX { read LSB }
MOV AH, AL { put LSB in AH }
IN AL, DX { read MSB }
{ AH = LSB, AL = MSB, so swap }
MOV DL, AH { put LSB in DL }
MOV AH, AL { put MSB in AH }
MOV AL, DL { put LSB back in AL }
{ the counter counts backwards, so now we have to normalize }
{ the registers... essentially this means subtracting 255 from }
{ each byte... the fast way to do this is to XOR each byte with
{ 255. Example: 38 counts would be 255 - 38 = 217. 217 = $D9, }
{ or 11011001b. XOR 217 with $FF = $26, or 38. }
XOR AH, 255
XOR AL, 255
end;
function readcounter: smallint;
var
returnval: smallint;
begin
if(testmode = TRUE) then begin
// if we are testing, and the loopback is chosen, then
// we want to read counter 1.
if(dc.testloopcheck.checked = TRUE) then returnval := readcounterone
else returnval := readcounterzero;
end else begin
// not test mode, so we're wanting counter 0 by default
returnval := readcounterzero;
end;
result := returnval;
end;
procedure ADZeroForceConvert; assembler;
asm
MOV AL, 00h { specify a/d 0 }
MOV DX, 0302h { status register = base + 2}
OUT DX, AL
MOV DX, 0301h
OUT DX, AL { write to 301h forces 12 bit a/d conversion}
end;
function ADZeroRead: smallint; assembler;
asm
MOV AL, 00h { specify a/d 0 }
MOV DX, 0302h { status register = base + 2}
OUT DX, AL
MOV DX, 0301h
IN AL, DX { read the MSB bits }
MOV AH, AL { put the MSB in AH so as to free AL }
MOV DX, 0300h
IN AL, DX { read the LSB bits }
{ This is a 12 bit conversion. the MSB is in AH, and the lower 4 }
{ bits are in the upper nibble of AL: ddddddddddddxxxx }
SHR AX, 4 { rotate 4 bits right: xxxxdddddddddddd }
AND AX, 0FFFh { mask off the upper nibble: 0000dddddddddddd }
end;