unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ExtCtrls;

const
  AM_OUTCOUNT = WM_USER + $99;
  AM_ERROR = WM_USER + $98;

type
  TMainForm = class(TForm)
    GroupBox1: TGroupBox;
    DeviceCombo: TComboBox;
    AmplitudeTrack: TTrackBar;
    AmplitudeLabel: TLabel;
    SampleRateLabel: TLabel;
    SampleRateTrack: TTrackBar;
    WaveSamplesLabel: TLabel;
    WaveSamplesTrack: TTrackBar;
    Panel1: TPanel;
    GoStopButton: TButton;
    OutCountLabel: TLabel;
    NumDACsLabel: TLabel;
    NumDACsTrack: TTrackBar;
    procedure FormCreate(Sender: TObject);
    procedure AmplitudeTrackChange(Sender: TObject);
    procedure SampleRateTrackChange(Sender: TObject);
    procedure WaveSamplesTrackChange(Sender: TObject);
    procedure GoButtonClick(Sender: TObject);
    procedure StopButtonClick(Sender: TObject);
    procedure NumDACsTrackChange(Sender: TObject);
  protected
    procedure AMOutCount(var Msg: TMessage); message AM_OUTCOUNT;
    procedure AMError(var Msg: TMessage); message AM_ERROR;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.DFM}

uses
  AIOAIO;

const
	DACFIFOOffset = $50;
        DACsPerPointOffset = $54;
        DACFIFOSizeOffset = $58;

const
	LTC2664Cmd_Data      = $3; //Write code to N, update N, power up.
	LTC2664Cmd_Range     = $6; //Write span to N.
	LTC2664Cmd_Data_All  = $A; //Write code to all, update all, power up.
	LTC2664Cmd_Range_All = $E; //Write span to all.

	RangeParam: array[0..4] of record
		bBipolar: Boolean;
		Span: Double;
	end =
	(
		(bBipolar: False; Span:  5),
		(bBipolar: False; Span: 10),
		(bBipolar: True;  Span: 10),
		(bBipolar: True;  Span: 20),
		(bBipolar: True;  Span:  5)
	);

type
  TWaveChunk = array of Word;

  TDACThread = class(TThread)
  protected
    procedure Execute; override;
  end;

var
  BoardIndex: TBoardIndex;
  DACThread: TDACThread;
  BaseClock, Amplitude, SampleRate, WaveSamples: LongWord;
  Theta: array[0..3] of Double;
  OutCount, NumDACs: Integer;
  FIFOSize: LongWord;



function LTC2664_BuildDAC(Command, Address, Data: TUInt32): TUInt32;
begin
	Result := (Data and $FFFF) or ((Address and $F) shl 16) or ((Command and $F) shl 20);
end;

function LTC2664_WriteDACFIFO(iBoard: TBoardIndex; Command, Address, Data: TUInt32): TWinErrCode;
begin
	Data := LTC2664_BuildDAC(Command, Address, Data);
	Result := PIO_Out32(iBoard, DACFIFOOffset, Data);
	{$IFDEF DEBUG}
		//AnsiMessageBox('LTC2664_WriteDACFIFO wrote ' + AnsiString(IntToHex(Data, 8)) + '.', 'AIOAIO', MB_ICONINFORMATION);
	{$ENDIF}
end;

function ValidateDACRange(var Range: TRangeCode): TWinErrCode;
begin
	Result := ERROR_SUCCESS;
	case Range of
		rcUnipolar5 : Range := rcRangeCode0; rcRangeCode0: ;
		rcUnipolar10: Range := rcRangeCode1; rcRangeCode1: ;
		rcBipolar5  : Range := rcRangeCode2; rcRangeCode2: ;
		rcBipolar10 : Range := rcRangeCode3; rcRangeCode3: ;
		rcBipolar2_5: Range := rcRangeCode4; rcRangeCode4: ;
		rcNoChange: Result := ERROR_HANDLE_EOF;
		else Result := ERROR_INVALID_PARAMETER;
	end;
end;

procedure WriteWave(Len: Integer);
const
  FullCircle = Pi*2;

var
  iCh, I: Integer;
  dTheta: array[0..3] of Double;
  WaveData: array of TUInt32;
  Status: TWinErrCode;

begin
  if Len <= 0 then Exit; 

  SetLength(WaveData, Len);
  for iCh := 0 to NumDACs-1 do
    dTheta[iCh] := FullCircle / WaveSamples / (1 + iCh)
  ;
  iCh := 0;
  for I := 0 to Len-1 do begin
    WaveData[I] := LTC2664_BuildDAC(0, iCh, Round($8000 + Amplitude * Cos(Theta[iCh])));
    //LTC2664_WriteDACFIFO(BoardIndex, 0, iCh, Round($8000 + Amplitude * Cos(Theta[iCh])));

    Theta[iCh] := Theta[iCh] + dTheta[iCh];
    if Theta[iCh] >= FullCircle then Theta[iCh] := Theta[iCh] - FullCircle;
    iCh := (iCh + 1) mod NumDACs;
  end;
  Status := PIO_OUTS32(BoardIndex, DACFIFOOffset, Length(WaveData), @WaveData[0]);
  SetLength(WaveData, 0);
  if Status <> ERROR_SUCCESS then PostMessage(MainForm.Handle, AM_ERROR, Len, Status);
end;

{ TDACThread }

procedure TDACThread.Execute;

  function bIsPAE: Boolean;
  begin
    Result := (PIO_In32(BoardIndex, $4) and (1 shl 28)) <> 0;
  end;

var
  Status: TWinErrCode;
  iCh: Integer;
  FIFORoom: LongWord;

begin
  SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_TIME_CRITICAL);

  PIO_Out32(BoardIndex, $40, PIO_In32(BoardIndex, $40) and not (1 shl 9)); //Disable DAC IRQ.

  for iCh := 0 to 3 do
    Theta[iCh] := 0
  ;
  FIFORoom := FIFOSize - PIO_In32(BoardIndex, DACFIFOOffset);
  WriteWave(FIFORoom);
  OutCount := FIFORoom;
  PostMessage(MainForm.Handle, AM_OUTCOUNT, 0, 0);

  Status := ERROR_SUCCESS;

  PIO_PollForIRQ(BoardIndex);
  PIO_Out32(BoardIndex, $40, PIO_In32(BoardIndex, $40) or (1 shl 9)); //Enable DAC IRQ.
  PIO_Out32(BoardIndex, DACsPerPointOffset, NumDACs); // set DACs per update
  PIO_Out32(BoardIndex, $8, Round(BaseClock / SampleRate));
  repeat
    if bIsPAE then begin
      repeat
        if Terminated then Break; //Can't double-break, so let the next IF handle it.

        FIFORoom := FIFOSize - PIO_In32(BoardIndex, DACFIFOOffset);
        WriteWave(FIFORoom);
        Inc(OutCount, FIFORoom);
      until not bIsPAE;
      
      PostMessage(MainForm.Handle, AM_OUTCOUNT, 0, 0);
    end;

    if Terminated then begin
      Status := ERROR_OPERATION_ABORTED;
      Break;
    end;

    Status := PIO_WaitForIRQ(BoardIndex);
    if Status <> ERROR_SUCCESS then Break;

    if Terminated then begin
      Status := ERROR_OPERATION_ABORTED;
      Break;
    end;
  until False;

  PIO_Out32(BoardIndex, $40, PIO_In32(BoardIndex, $40) and not (1 shl 9)); //Disable DAC IRQ.
  PIO_PollForIRQ(BoardIndex);

  ReturnValue := Status;
end;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);
var
  I: Integer;
  DeviceStr: AnsiString;
  DeviceStrSize: TUInt32;
  Status: TWinErrCode;

begin
  IsMultiThread := True;

  DeviceCombo.Align := alTop;
  DeviceCombo.Items.Clear;
  for I := 0 to BRD_GetHighestBoardIndex do begin
    DeviceStrSize := MAX_PATH;
    DeviceStr := StringOfChar(#0, DeviceStrSize);
    Status := BRD_GetName(I, DeviceStrSize, TPAnsiChar(DeviceStr), @DeviceStrSize);
    if Status <> ERROR_SUCCESS then
      DeviceStr := '<Error ' + IntToStr(Status) + ' from BRD_GetName>'
    else
      SetLength(DeviceStr, DeviceStrSize)
    ;
    DeviceCombo.Items.Add('[' + IntToStr(I) + '] ' + DeviceStr);
  end;
  if DeviceCombo.Items.Count <> 0 then DeviceCombo.ItemIndex := 0;
  //DeviceComboChange(nil);
  AmplitudeTrack.OnChange(AmplitudeTrack);
  SampleRateTrack.OnChange(SampleRateTrack);
  WaveSamplesTrack.OnChange(WaveSamplesTrack);
  NumDACsTrack.OnChange(NumDACsTrack);
end;

procedure TMainForm.AmplitudeTrackChange(Sender: TObject);
var
  Pct: Integer;
  fPct: Extended;

begin
  Amplitude := AmplitudeTrack.Position;
  fPct := 100/$7FFF * Amplitude;
  if fPct <= 0 then
    Pct := 0
  else if fPct >= 100 then
    Pct := 100
  else begin
    Pct := Round(fPct);
    //It's not exactly 0% or 100%, so don't report those values.
    if Pct < 1 then Pct := 1;
    if Pct > 99 then Pct := 99;
  end;
  AmplitudeLabel.Caption := 'Amplitude: ' + IntToStr(Pct) + '% pk-pk';
end;

procedure TMainForm.SampleRateTrackChange(Sender: TObject);
begin
  SampleRate := SampleRateTrack.Position;
  SampleRateLabel.Caption := 'Samples/Sec: ' + IntToStr(SampleRate) + 'k';
  SampleRate := 1000 * SampleRate;

  if DACThread <> nil then PIO_Out32(BoardIndex, $8, Round(BaseClock / SampleRate));
end;

procedure TMainForm.WaveSamplesTrackChange(Sender: TObject);
begin
  WaveSamples := WaveSamplesTrack.Position;
  WaveSamplesLabel.Caption := 'Samples/Wave: ' + IntToStr(WaveSamples);
end;

procedure TMainForm.GoButtonClick(Sender: TObject);
var
  ItemIndex: Integer;
  DummyData: array of LongWord;

begin
  ItemIndex := DeviceCombo.ItemIndex;
  if ItemIndex < 0 then Exit;

  DeviceCombo.Enabled := False;

  BoardIndex := ItemIndex;

  BaseClock := PIO_In32(BoardIndex, $C);

  PIO_Out32(BoardIndex, $8, 0); //Stop clock.
  PIO_Out32(BoardIndex, $0, $8); //Reset DAC FIFO.
  FIFOSize := PIO_In32(BoardIndex, DACFIFOSizeOffset);

  //Fill FIFO with zeroes in order to measure size. Only works while clock is stopped.
  SetLength(DummyData, FIFOSize);
  ZeroMemory(@DummyData[0], Length(DummyData)*SizeOf(DummyData[0]));
  PIO_Out32(BoardIndex, DACFIFOOffset, 0); //A single sample to make the FIFO used space odd(bit 0 high).
  //The FIFO size is even, so once it fills, bit 0 will go low.
  repeat
    PIO_OUTS32(BoardIndex, DACFIFOOffset, Length(DummyData), @DummyData[0]);
  until (PIO_In32(BoardIndex, DACFIFOOffset) and 1) = 0;

  SetLength(DummyData, 0);
  PIO_Out32(BoardIndex, $0, $8); //Reset DAC FIFO.

  DACThread := TDACThread.Create(False);

  GoStopButton.Caption := 'Stop';
  GoStopButton.OnClick := StopButtonClick;
end;

procedure TMainForm.StopButtonClick(Sender: TObject);
var
  TicksPerSec, EndTime, dT: Int64;

begin
  DACThread.Terminate;

  PIO_AbortIRQWait(BoardIndex);

  DACThread.Free;

  QueryPerformanceFrequency(TicksPerSec);
  QueryPerformanceCounter(EndTime);
  EndTime := EndTime + TicksPerSec; //1sec timeout.
  while (PIO_In32(BoardIndex, $4) and (1 shl 29)) = 0 do begin
    QueryPerformanceCounter(dT);
    dT := dT - EndTime;
    if dT > 0 then Break;

    Sleep(1);
  end;

  PIO_Out32(BoardIndex, $8, 0); //Stop clock.
  
  DeviceCombo.Enabled := True;

  GoStopButton.Caption := 'Go';
  GoStopButton.OnClick := GoButtonClick;
end;

procedure TMainForm.AMOutCount(var Msg: TMessage);
begin
  OutCountLabel.Caption := IntToStr(OutCount);
end;

procedure TMainForm.AMError(var Msg: TMessage);
begin
  Caption := 'Writing ' + IntToStr(Msg.WParam) + ' DWords, error ' + IntToStr(Msg.LParam) + '.';
end;

procedure TMainForm.NumDACsTrackChange(Sender: TObject);
begin
  NumDACs := NumDACsTrack.Position;
  SampleRateTrack.Max := 1000 div NumDACs;
  NumDACsLabel.Caption := 'Number of DACs per Point: ' + IntToStr(NumDACs);
end;

end.
