// =====================================================
//
//   miniLA_win - Hardware communication layer
//
//   (c) miniLA Team
//
// =====================================================
//
// This is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// This software is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this package; see the file COPYING.  If not, write to
// the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.


unit uCommunication;

interface

uses Dialogs, Forms, extctrls,classes,SysUtils,IniFiles,
     uUtils,
     uLATypes;

type TConnection = (none, lpt, usb);

type TAnalyzer=class(TObject)
     protected
       ctrl_reg		: byte;
       Freq         	: real;
       FVersion		: word;
       FStateAnalysis	: boolean;
       FSubVersion	: word;
       ConnectionUsed	: TConnection;
       CommError	: boolean;

       function    ReadReg(Addr: Integer): Byte;
       procedure   WriteReg(Addr: Integer; Val: Byte);

       procedure   SetFreq(value : real);
       procedure   SetSubversion(value : word);

       procedure   ReadSRAM;
       function    GetVersion : word;
       procedure   TimerTrig(Sender: TObject);
       function    GetTriggerPos : longint;

     public
       Timer   	      : TTimer;
       Timebase_sel   : byte;
       Timebase_freq  : array [0..31] of real;
       FallingEdge    : boolean;
       TriggerDelay   : byte;
       TriggerLength  : byte;
       TriggerMask    : cardinal;
       TriggerValue   : cardinal;
       TriggerEdge    : cardinal;
       TriggerSize    : byte;
       InvIntTrig     : boolean;
       ExtTrig_en     : boolean;
       ExtTrig_val    : integer;

       Connection      : TConnection;

       State          : TState;
       Old_State      : TState;
       DataArray      : TData;
       datalength     : longint;

       OnStateChange  : TNotifyEvent;

       constructor Create;
       destructor  Destroy; override;
       procedure   Init;
       function    PortValue: cardinal;
       procedure   Start;
       procedure   Stop;

       property    Version       : word        read FVersion;
       property    StateAnalysis : boolean     read FStateAnalysis;
       property    Frequency	 : real	       read freq         write SetFreq;
       property    Subversion    : word        read FSubVersion  write SetSubversion default 100;
       property    TriggerPos    : longint     read GetTriggerPos;

     end;


//******************************************************************************
//******************************************************************************
//******************************************************************************

implementation

uses dlgMain, uINIFile, dlgIO_LPT, dlgIO_USB, D2XXUnit;


const
   // miniLA control register
   mctByteSel	: byte = $03;
   mctStop	: byte = $08;
   mctAINC	: byte = $10;
   mctPCLK	: byte = $20;
   mctReset	: byte = $40;
   mctRun	: byte = $80;

   // miniLA status
   mstSCT	: byte = $08;
   mstTrig	: byte = $10;
   mstReset	: byte = $20;
   mstRun	: byte = $40;
   mstDone	: byte = $80;

type   
   tDivTable    = array [0..31] of longword;

const
   // timebase divider coeficients for version 100MHz
   DIVTABLE	: tDivtable =
   (      1,       2,       5,      10,      20,      50,
        100,     200,     500,    1000,    2000,    5000,
      10000,   20000,   50000,  100000,  200000,  500000,
    1000000, 2000000, 5000000,10000000,       2,       2,
          2, 	   2,       2,       2,       2,       2,
	  2,       2
   );

   // timebase divider coeficients for version 80MHz
   DIVTABLE_80	:  tDivtable =
   (      1,       2,       4,       8,      16,      40,
         80,     160,     400,     800,    1600,    4000,
       8000,   16000,   40000,   80000,  160000,  400000,
     800000, 1600000, 4000000, 8000000,       2,       2,
          2, 	   2,       2,       2,       2,       2,
	  2,       2
   );

   // timebase divider coeficients for version 40MHz
   DIVTABLE_40	:  tDivtable =
   (      1,       1,       2,       4,       8,      20,
         40,      80,     200,     400,     800,    2000,
       4000,    8000,   20000,   40000,   80000,  200000,
     400000,  800000, 2000000, 4000000,       2,       2,
          2, 	   2,       2,       2,       2,       2,
	  2,       2
   );

   // timebase divider coeficients for version 20MHz
   DIVTABLE_20	:  tDivtable =
   (      1,       1,       1,       2,       4,      10,
         20,      40,     100,     200,     400,    1000,
       2000,    4000,   10000,   20000,   40000,  100000,
     200000,  400000, 1000000, 2000000,       2,       2,
	  2, 	   2,       2,       2,       2,       2,
	  2,       2
   );


//===========================
constructor TAnalyzer.Create;
//===========================
var
  n	: longint;
  f	: double;
begin
  inherited Create;
  state := tsNotDetected;
  old_state := tsNotDetected;
  ConnectionUsed := none;
  CommError := false;
  datalength := MaxDataLength;
  FallingEdge := INIFile.ReadBool('HW', 'FALLING_EDGE', false);
  n:=INIFile.ReadInteger('HW', 'CONNECTION', 0);
  if n = 0 then
     connection := lpt
  else
     connection := usb;
  FSubVersion := INIFile.ReadInteger('HW', 'SUBVERSION', 100);
  FStateAnalysis := false;
  f := INIFile.ReadFloat('HW', 'FREQUENCY', 100e6);
  setfreq(f);

  Timer:= TTimer.Create(nil);
  with timer do begin
    Enabled := false;
    Interval:= 250;
    OnTimer := TimerTrig;
  end;
end;

//===========================
destructor TAnalyzer.Destroy;
//===========================
var
   n	: longint;
begin
  INIFile.WriteBool('HW', 'FALLING_EDGE', FallingEdge);
  if connection= lpt then
     n := 0
  else
     n := 1;
  INIFile.WriteInteger('HW', 'CONNECTION', n);
  INIFile.WriteInteger('HW', 'SUBVERSION', FSubVersion);
  INIFile.WriteFloat('HW', 'FREQUENCY', Frequency);

  Timer.Enabled:=false;
  Timer.Free;
  inherited Destroy;
end;

//******************************************************************************

//=================================================================
procedure TAnalyzer.Init;
//=================================================================
begin
  if ConnectionUsed=usb then				// close previous devices
     try
	frmIO_USB.Close;
     except
     end;

  CommError := false;

  try							// initialize new devices
     if connection = lpt then
	frmIO_LPT.Init
     else
	frmIO_USB.Init;

  except
     on E: Exception do
      begin
        CommError := true;
        MessageDlg(E.Message, mtError, [mbOk], 0);
      end;
  end;

  Timer.Enabled := True;
  ConnectionUsed := connection;
end;


//=================================================================
function TAnalyzer.ReadReg(Addr: Integer): Byte;
//=================================================================
begin
   result := 0;
   if CommError then
      exit;

   if connection = LPT then
      Result := frmIO_LPT.ReadReg(Addr)
   else
    begin
      frmIO_USB.ReadReg(Addr, true);
      Result := FT_In_Buffer[0];
    end;
end;

//=================================================================
procedure TAnalyzer.WriteReg(Addr: Integer; Val: Byte);
//=================================================================
begin
   if CommError then
      exit;

   if connection = LPT then
      frmIO_LPT.WriteReg(Addr, Val)
   else
      frmIO_USB.WriteReg(Addr, Val, true);
end;

//=================================================================
procedure TAnalyzer.SetSubversion (Value: word);
//=================================================================
begin
   FSubVersion := value;
   SetFreq(freq);
end;

//=================================================================
procedure TAnalyzer.SetFreq (Value: real);
//=================================================================
var
   i		: byte;
   pDivtable	: ^tDivtable;

begin
   freq := Value;

   case FSubVersion of
      0:begin // 100 MHz
	   pDivtable := addr(DIVTABLE);
        end;
      1:begin // 80 MHz
	   pDivtable := addr(DIVTABLE_80);
        end;
      2:begin // 40 MHz
	   pDivtable := addr(DIVTABLE_40);
	end;
      3:begin // 20 MHz
	   pDivtable := addr(DIVTABLE_20);
        end;
      else
	   pDivtable := addr(DIVTABLE);
   end;

   for i:=0 to 18 do
      Timebase_freq[i] := freq / pDivtable^[i];

   for i:=19 to 31 do
      Timebase_freq[i] := freq / pDivtable^[1];
end;

//=================================================================
function TAnalyzer.GetTriggerPos: longint;
//=================================================================
begin
   if TriggerSize < 15 then
      Result := TriggerStep * (TriggerSize+1)
   else
      Result := 0;

end;


//******************************************************************************


//=================================================================
function TAnalyzer.GetVersion: word;
//=================================================================
begin
   if CommError then
    begin
      result:=0;
      exit;
    end;

   try
      FVersion := ReadReg(1) and $7F;
      FStateAnalysis := ((FVersion and $70) = $20);
      Result := FVersion;    				// typ LA
   except
     on E: Exception do
      begin
        CommError := true;
        result := 0;
        MessageDlg(E.Message, mtError, [mbOk], 0);
      end;
  end;
end;


//=================================================================
function TAnalyzer.PortValue: cardinal;
//=================================================================
begin
   if CommError then
      exit;
   try
      if connection = LPT then
       begin
         WriteReg(0,(ctrl_reg and $FC));
         result := ReadReg(0);
         result := (ReadReg(0) shl 8) + result;
         result := (ReadReg(0) shl 16) + result;
         result := (ReadReg(0) shl 24) + result;
       end
      else
       begin
         frmIO_USB.WriteReg(0,(ctrl_reg and $FC), false);
         frmIO_USB.ReadReg(0, false);
         frmIO_USB.ReadReg(0, false);
         frmIO_USB.ReadReg(0, false);
	 frmIO_USB.ReadReg(0, true);
         result := (FT_In_Buffer[0] shl  0) +
                   (FT_In_Buffer[1] shl  8) +
                   (FT_In_Buffer[2] shl 16) +
                   (FT_In_Buffer[3] shl 24);
       end;
  except
     on E: Exception do
      begin
        CommError := true;
        MessageDlg(E.Message, mtError, [mbOk], 0);
      end;
  end;

end;

//=================================================================
// Read miniLA status
//=================================================================
procedure TAnalyzer.TimerTrig;
var
   status_reg		: byte;
   old_version		: word;
   do_changeevent	: boolean;
begin

   old_state := state;
   do_changeevent := false;
   old_version := FVersion;

   GetVersion;

   // read miniLA status register
   try
      status_reg := ReadReg(3);
   except
     on E: Exception do
      begin
	CommError := true;
	state := tsNotDetected;
	MessageDlg(E.Message, mtError, [mbOk], 0);
      end;
   end;

   // get new state
   if (state = tsNotDetected) then
    begin
      if ((FVersion and $70) <> 0) and ((FVersion and $70) <> $70) and not CommError then	// HW was connected
	 state := tsReady;
    end
   else if ((FVersion and $70) = 0) or ((FVersion and $70) = $70) or CommError then	// HW was disconected
      state := tsNotDetected
   else if (old_version <> FVersion) then		// New HW connected
    begin
      state := tsReady;
      do_changeevent := true;
    end
   // if correct version detected, do ..
   else if old_state <> tsNotDetected then
    begin

      if (   ((status_reg and mstReset) <> 0)
	  or ((status_reg and mstRun) = 0)) then
       begin
	 state := tsReady;
       end
      else if (    ((status_reg and mstReset) = 0)
	       and ((status_reg and mstDone) <> 0)
	       and ((ctrl_reg and mctRun) <> 0)) then
       begin
	 state := tsReading;
       end
      else if (    ((status_reg and mstTrig) <> 0)
	       and ((ctrl_reg and mctRun) <> 0)) then
       begin
	 state := tsPosttrigger;
       end
      else if (ctrl_reg and mctRun) <> 0 then
       begin
	 state := tsWaitForTrigger;
       end;

      if state = tsReading then
       begin
	 if assigned (OnStateChange) then
	    OnStateChange(self);
	 ReadSRAM;
	 state := tsReady;
       end;
    end;


   if ((old_state <> state) or do_changeevent) and assigned (OnStateChange) then
      OnStateChange(self);

end;

//=================================================================
// Start sampling
//=================================================================
procedure TAnalyzer.Start;
var
   i	: byte;
begin
   if (state = tsNotDetected) or (CommError) then
      exit;

   try
      WriteReg(0, mctReset);                       	// clear all regs
      WriteReg(1, TriggerDelay);  			        // number of trigger events
      WriteReg(4, TriggerSize);				        // pre/posttrigger buffer size
      WriteReg(5, (TriggerValue and $00FF));		// trigger level
      WriteReg(6, (TriggerValue and $FF00) shr 8);
      WriteReg(7, (TriggerEdge and TriggerMask and $00FF));	// trigger edge
      WriteReg(9, (TriggerMask and $00FF));		    // trigger mask
      WriteReg(10, (TriggerMask and $FF00) shr 8);

      // trigger control register
      i:=$0;
      if ExtTrig_en then
         i:=i+$2;		// enable external trigger

      if ExtTrig_val <> 0 then
         i:=i+$1;		// external trigger level

      if InvIntTrig then
	        i:=i+$80;		// invert internal trigger

      WriteReg(13, i);

      // other registers
      if not(FStateAnalysis) then
      begin
         // TimeAnalysis
         WriteReg(2, TriggerLength);			// min. length of trigger event

         // Timebase register
         i := Timebase_sel;

         if i=19 then  					// external clock inpu
            i:=30;

         if FallingEdge then
            i:=i+$20;

         WriteReg(3, i);
      end else
      begin
         // StateAnalysis
         WriteReg(2, TriggerLength-1);			// min. length of trigger event
         WriteReg(8, (TriggerEdge and TriggerMask and $FF00) shr 8);
      end;

      // start minila
      ctrl_reg := mctRun;
      WriteReg(0, ctrl_reg);

      State := tsWaitForTrigger;
   except
        on E: Exception do
        begin
	        CommError := true;
            state := tsNotDetected;
            MessageDlg(E.Message, mtError, [mbOk], 0);
        end;
   end;

   if assigned (OnStateChange) then
      OnStateChange(self);

end;


//=================================================================
// Stop sampling
//=================================================================
procedure TAnalyzer.Stop;
begin
   if (state = tsNotDetected) or CommError then
      exit;

   // write STOP
   try
      ctrl_reg := ctrl_reg or mctStop;
      WriteReg(0, ctrl_reg);
   except
     on E: Exception do
      begin
        CommError := true;
        state := tsNotDetected;
        MessageDlg(E.Message, mtError, [mbOk], 0);
      end;
   end;

end;

//=================================================================
// Read stored samples
//=================================================================
procedure TAnalyzer.ReadSRAM;
var
   i			: word;
   d_i, r_i 		: longint;
   s, p, p_max, n, s_max, dn	: longint;
   status_reg		: byte;
   sample		: longword;
   p_size		: longint;

   skip_n		: byte;
   skip_on		: boolean;
   data_step		: byte;

begin
    ctrl_reg := 0;
    n := 0;

    try
        //  ---=== LPT Interface ===---
        if connection = LPT then
        begin
	        // skip garbage data (expected)
	        if (TriggerSize>15) and (TriggerSize<31) then
            begin
	            for s:=1 to (31-TriggerSize)*$2000-8 do
	            begin
	                inc(n);
	                WriteReg(0, mctPCLK);           		// incrementation of address
	            end;
            end;

	        // Write to Pre/Posttrigger buffer size register
	        WriteReg(4, $FF);				// used in stateanalysis for SCT bit

	        // skip garbage data
	        status_reg := ReadReg(3);
	        while ((status_reg and mstSCT) = 0) and (n<=MaxDataLength) do
	        begin
	            inc(n);
	            WriteReg(0, mctPCLK);             		// incrementation of address
	            status_reg := ReadReg(3);
	        end;

	        if (n <> 0) and not(FStateAnalysis) then	// generate 3 more clock pulses if sampling not 128K of samples or stateanalysis
	        begin
	            n:=n+3;
	            for i:=1 to 3 do
                begin
	                WriteReg(0, mctPCLK);         		// incrementation of address
	            end;
            end else
                if FStateAnalysis then
	            begin
	                WriteReg(0, mctPCLK);         		// incrementation of address
	                n:=n+1;
	            end;

	        if n > MaxDataLength-1 then	     		// range checking
	        begin
	            datalength := 1;
	            exit;
	        end;

	        WriteReg(0,mctAINC);                		// address autoincrement on

	        // read samples
	        for s:=0 to (MaxDataLength - n - 1) do
	        begin
	            sample := ReadReg(0);
	            sample := (ReadReg(0) shl 8) + sample;
	            sample := (ReadReg(0) shl 16) + sample;
	            DataArray[s] := (ReadReg(0) shl 24) + sample;
	        end;

	        datalength := MaxDataLength - n - 1;


	        // reset miniLA
	        WriteReg(0, mctReset);
	        WriteReg(0, 0);
        end else
        //  ---=== USB Interface ===---
        begin
            if not(FStateAnalysis) then
            begin
                // SCT + 3 additional clock cycles
	            skip_n := 4
	        end else
            begin
                // SCT + 1 additional clock cycle
	            skip_n := 2;
            end;

            skip_on := false;

	        // ************* skip garbage data (expected) *********************
	        if (TriggerSize>15) and (TriggerSize<31) then
	        begin
	            s := 1;
	            s_max := (31-TriggerSize)*$2000 - 7;

	            while (s <= s_max) do
	            begin
                    // packet size
	                p_max := $1000;
	                if p_max > (s_max - s) then
		                p_max := s_max - s + 1;

	                for p:=1 to p_max do
		            begin
		                inc(n);
                        // incrementation of address
		                frmIO_USB.WriteReg(0, mctPCLK, (p=p_max));
                    end;
	                inc(s, p_max);
	            end;
	        end;

	        // ********* Write to Pre/Posttrigger buffer size register ********

	        // used in stateanalysis for SCT bit
            WriteReg(4, $1F);

	        // no further skipping needed?
	        status_reg := ReadReg(3);
	        if ((status_reg and mstSCT) <> 0) then
	        begin
                // ** timeanalysis **
                if not(FStateAnalysis) then
                begin
                    // disable furher read of status register
                    skip_n := 0;

                    if n<>0 then
                    begin
                        // additional 3 read clocks
                        for p:=1 to 3 do
                        begin
                            // incrementation of address
                            frmIO_USB.WriteReg(0, mctPCLK, true);
                            inc(n);
                        end;
                    end;

	            end else
	            begin
                    // ** stateanalysis **

                    // disable furher read of status register
	                skip_n := 0;
	                // additional 1 read clock
	                frmIO_USB.WriteReg(0, mctPCLK, true);
                    // incrementation of address
	                inc(n);
	            end;
	        end;

            // address autoincrement on
	        WriteReg(0,mctAINC);

	        // ** skip additional garbage data and read samples **

            // data index
	        d_i := 0;
	        while (n < MaxDataLength-1) do
	        begin
	            if skip_n <> 0 then
                begin
	                data_step := 5
	            end else
                begin
	                data_step := 4;
                end;

                // number of words in packed
	            p_size := $600;

	            if (MaxDataLength-n-1) < p_size then
                begin
                    // adjust size of last data packet
	                p_size := MaxDataLength-n-1;
                end;

                // send read commands to USB
	            dn := 0;

	            for s:=0 to p_size-1 do
	            begin
	                frmIO_USB.ReadReg(0, false);
	                frmIO_USB.ReadReg(0, false);
	                frmIO_USB.ReadReg(0, false);

                    // skipping data, read also status register
	                if data_step = 5 then
		            begin
		                frmIO_USB.ReadReg(0, false);

		                if s = (p_size-1) then
                        begin
                            // last command, start sending
		                    frmIO_USB.ReadReg(3, true);
                            dn := FT_Q_Bytes;
                        end else
                        begin
		                    frmIO_USB.ReadReg(3, false);
                        end;

		            end else
		            begin
                        // read sampled data
		                if s = (p_size-1) then
                        begin
                            // last command, start sending
		                    frmIO_USB.ReadReg(0, true);
                            dn := FT_Q_Bytes;
                        end else
                        begin
		                    frmIO_USB.ReadReg(0, false);
                        end;
		            end;
	            end;

	            // ** check number of received data **

                // data
	            if (dn <> (p_size*data_step)) then
	            begin
	                if (d_i<>0) then
                    begin
		                datalength := d_i
	                end else
                    begin
                        // no data received
		                datalength := 1;
                    end;
	                exit;
	            end;

	            //  read input buffer
	            r_i:=0;
	            for s:=0 to p_size-1 do
	            begin
	                inc(n);
	                if (skip_n > 0) and (skip_on or ((FT_In_Buffer[r_i+4] and mstSCT) <> 0)) then
                    begin
                        // end of garbage data
		                dec(skip_n);
                        skip_on := true;
                    end else
                    begin
                        if (skip_n = 0) then
		                begin
                            // valid data
		                    DataArray[d_i] := (FT_In_Buffer[r_i+0] shl  0) +
				                    (FT_In_Buffer[r_i+1] shl  8) +
				                    (FT_In_Buffer[r_i+2] shl 16) +
				                    (FT_In_Buffer[r_i+3] shl 24);
		                    inc(d_i);
		                end;
                    end;
                    inc(r_i, data_step);
                end;
	        end;

	        if (d_i<>0) then
            begin
	            datalength := d_i
            end else
            begin
                // no data received
	            datalength := 1;
            end;

	        // reset miniLA
	        WriteReg(0, mctReset);
	        WriteReg(0, 0);
        end;
    except
        on E: Exception do
        begin
	        CommError := true;
	        state := tsNotDetected;
	        DataLength := 1;
	        MessageDlg(E.Message, mtError, [mbOk], 0);
        end;
    end;
end;


end.

