unit MainUnit;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,
  ltrapi, ltrapitypes, ltrapidefine, ltr35api, StdCtrls, Spin;

{ Информация, необходимая для установления соединения с модулем }
type TLTR_MODULE_LOCATION = record
  csn : string; //серийный номер крейта
  slot : Word; //номер слота
end;






type
  TMainForm = class(TForm)
    cbbModulesList: TComboBox;
    grpDevInfo: TGroupBox;
    lblDevSerial: TLabel;
    lblVerPld: TLabel;
    edtDevSerial: TEdit;
    edtModification: TEdit;
    btnRefreshDevList: TButton;
    btnOpen: TButton;
    lblPldVer: TLabel;
    edtVerPld: TEdit;
    lblVerFPGA: TLabel;
    edtVerFPGA: TEdit;
    cbbCh1Src: TComboBox;
    chkCh1En: TCheckBox;
    edtSrc1Freq: TEdit;
    edtCh1Amp: TEdit;
    lblFreq: TLabel;
    lblAmp: TLabel;
    lblOffs: TLabel;
    cbbCh1Out: TComboBox;
    lblOutput: TLabel;
    chkCh2En: TCheckBox;
    edtSrc1Pha: TEdit;
    edtCh2Amp: TEdit;
    cbbCh2Out: TComboBox;
    edtCh1Offs: TEdit;
    edtCh2Off: TEdit;
    chkCh3En: TCheckBox;
    edtSrc2Freq: TEdit;
    edtCh3Amp: TEdit;
    edtCh3Offs: TEdit;
    cbbCh3Out: TComboBox;
    chkCh4En: TCheckBox;
    edtSrc3Freq: TEdit;
    edtCh4Amp: TEdit;
    edtCh4Offs: TEdit;
    cbbCh4Out: TComboBox;
    chkCh5En: TCheckBox;
    edtCh5Amp: TEdit;
    edtCh5Offs: TEdit;
    cbbCh5Out: TComboBox;
    chkCh6En: TCheckBox;
    edtCh6Amp: TEdit;
    edtCh6Offs: TEdit;
    cbbCh6Out: TComboBox;
    chkCh7En: TCheckBox;
    edtCh7Amp: TEdit;
    edtCh7Offs: TEdit;
    cbbCh7Out: TComboBox;
    chkCh8En: TCheckBox;
    edtCh8Amp: TEdit;
    edtCh8Offs: TEdit;
    cbbCh8Out: TComboBox;
    btnSetSignal: TButton;
    btnStopGen: TButton;
    cbbCh2Src: TComboBox;
    cbbCh3Src: TComboBox;
    cbbCh4Src: TComboBox;
    cbbCh5Src: TComboBox;
    cbbCh6Src: TComboBox;
    cbbCh7Src: TComboBox;
    cbbCh8Src: TComboBox;
    Label1: TLabel;
    edtSrc4Freq: TEdit;
    lbl1: TLabel;
    edtSrc2Pha: TEdit;
    edtSrc3Pha: TEdit;
    edtSrc4Pha: TEdit;
    lbl2: TLabel;
    lbl3: TLabel;
    lbl4: TLabel;
    lbl5: TLabel;
    procedure btnRefreshDevListClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure btnSetSignalClick(Sender: TObject);
    procedure btnStopGenClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    { Элементы управления для каналов и арифметических генераторов
      сохраняем в массивы (заполняются в функции создания формы) для
      удобного обращения в дальнейшем по индексу }
    chkChEnableList : array[0..LTR35_DAC_CHANNEL_CNT-1] of TCheckBox;
    cbbChSrcList    : array[0..LTR35_DAC_CHANNEL_CNT-1] of TComboBox;
    edtChAmpList    : array[0..LTR35_DAC_CHANNEL_CNT-1] of TEdit;
    edtChOffsList   : array[0..LTR35_DAC_CHANNEL_CNT-1] of TEdit;
    cbbChOutList    : array[0..LTR35_DAC_CHANNEL_CNT-1] of TComboBox;

    edtSrcFreqList  : array[0..LTR35_ARITH_SRC_CNT-1] of TEdit;
    edtSrcPhaList   : array[0..LTR35_ARITH_SRC_CNT-1] of TEdit;


    ltr35_list: array of TLTR_MODULE_LOCATION; //список найденных модулей
    genRunning : Boolean;
    hltr35 : TLTR35; // Описатель модуля, с которым идет работа


    procedure updateControls();
    procedure refreshDeviceList();
    procedure stopGen();
    procedure closeDevice();
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

function gcd( a, b : integer ) : Integer  ;
begin
  if b = 0 then
    gcd:= a
  else
    gcd:= gcd(b, a mod b);
end;


// функция вычисляет НОК, используя НОД
function lcm( a, b : integer ) : Integer  ;
begin
  lcm:= (a div gcd( a, b )) * b;
end;


procedure TMainForm.refreshDeviceList();
var
  srv : TLTR; //Описатель для управляющего соединения с LTR-сервером
  crate: TLTR; //Описатель для соединения с крейтом
  res, crates_cnt, crate_ind, module_ind, modules_cnt : integer;
  serial_list : array [0..LTR_CRATES_MAX-1] of string; //список номеров керйтов
  mids : array [0..LTR_MODULES_PER_CRATE_MAX-1] of Word; //список идентификаторов модулей для текущего крейта
begin
  //обнуляем список ранее найденных модулей
  modules_cnt:=0;
  cbbModulesList.Items.Clear;
  SetLength(ltr35_list, 0);

  // устанавливаем связь с управляющим каналом сервера, чтобы получить список крейтов
  LTR_Init(srv);
  res:=LTR_OpenSvcControl(srv, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT);
  if res <> LTR_OK then
    MessageDlg('Не удалось установить связь с сервером: ' + LTR_GetErrorString(res), mtError, [mbOK], 0)
  else
  begin
    //получаем список серийных номеров всех подключенных крейтов
    res:=LTR_GetCrates(srv, serial_list, crates_cnt);
    //серверное соединение больше не нужно - можно закрыть
    LTR_Close(srv);

    if (res <> LTR_OK) then
      MessageDlg('Не удалось получить список крейтов: ' + LTR_GetErrorString(res), mtError, [mbOK], 0)
    else
    begin
      for crate_ind:=0 to crates_cnt-1 do
      begin
        //устанавливаем связь с каждым крейтом, чтобы получить список модулей
        LTR_Init(crate);
        res:=LTR_OpenCrate(crate, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT,
                           LTR_CRATE_IFACE_UNKNOWN, serial_list[crate_ind]);
        if res=LTR_OK then
        begin
          //получаем список модулей
          res:=LTR_GetCrateModules(crate, mids);
          if res = LTR_OK then
          begin
              for module_ind:=0 to LTR_MODULES_PER_CRATE_MAX-1 do
              begin
                //ищем модули LTR35
                if mids[module_ind]=LTR_MID_LTR35 then
                begin
                    // сохраняем информацию о найденном модуле, необходимую для
                    // последующего установления соединения с ним, в список
                    modules_cnt:=modules_cnt+1;
                    SetLength(ltr35_list, modules_cnt);
                    ltr35_list[modules_cnt-1].csn := serial_list[crate_ind];
                    ltr35_list[modules_cnt-1].slot := module_ind+LTR_CC_CHNUM_MODULE1;
                    // и добавляем в ComboBox для возможности выбора нужного
                    cbbModulesList.Items.Add('Крейт ' + ltr35_list[modules_cnt-1].csn +
                                            ', Слот ' + IntToStr(ltr35_list[modules_cnt-1].slot));
                end;
              end;
          end;
          //закрываем соединение с крейтом
          LTR_Close(crate);
        end;
      end;
    end;

    cbbModulesList.ItemIndex := 0;
    updateControls;

  end;
end;

procedure TMainForm.updateControls();
var
  module_opened, devsel, cfg_en, sig_en: Boolean;
  ch : Integer;
  ch_en : Boolean;
begin
  module_opened:=LTR35_IsOpened(hltr35)=LTR_OK;
  devsel := (Length(ltr35_list) > 0) and (cbbModulesList.ItemIndex >= 0);

   //обновление списка устройств и выбор можно делать только пока не открыто конкретное устройство
  btnRefreshDevList.Enabled := not module_opened;
  cbbModulesList.Enabled := not module_opened;

  //установить связь можно только если выбрано устройство
  btnOpen.Enabled := devsel;
  if module_opened then
    btnOpen.Caption := 'Закрыть соединение'
  else
    btnOpen.Caption := 'Установить соединение';

  //изменение настроек возможно только при открытом устройстве и не запущенной генерации сигнала
  cfg_en:= module_opened and not genRunning;

  sig_en:= module_opened;

  btnSetSignal.Enabled := sig_en;
  btnStopGen.Enabled := genRunning;

  for ch:=0 to LTR35_DAC_CHANNEL_CNT-1 do
  begin
    ch_en := module_opened and (ch < hltr35.ModuleInfo.DacChCnt);

    { Набор разрешенных каналов и используемый выход можно изменять только
      с остановкой генерации сигналов }  
    chkChEnableList[ch].Enabled := ch_en and cfg_en;
    cbbChOutList[ch].Enabled := ch_en and cfg_en;
    cbbChSrcList[ch].Enabled := ch_en and cfg_en;

    { Параметры сигналов можно менять и "на лету" }
    edtChAmpList[ch].Enabled := ch_en and sig_en;
    edtChOffsList[ch].Enabled := ch_en and sig_en;
  end;

  for ch:=0 to LTR35_ARITH_SRC_CNT-1 do
  begin
    edtSrcFreqList[ch].Enabled := sig_en;
    { Фаза задается начальная и имеет значение только при запуске после останова,
      на лету можем изменить только приращение фазы, т.е. частоту сигнала }
    edtSrcPhaList[ch].Enabled  := cfg_en;
  end;

end;

procedure TMainForm.closeDevice();
begin
  if genRunning then
    stopGen;

  LTR35_Close(hltr35);
end;

procedure TMainForm.stopGen();
var
  res : integer;
begin
  res := LTR35_Stop(hltr35, 0);
  if res <> LTR_OK then
     MessageDlg('Ошибка остановки генерации сигнала: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);
  genRunning := False;
end;



procedure TMainForm.btnRefreshDevListClick(Sender: TObject);
begin
  refreshDeviceList
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  chkChEnableList[0] := chkCh1En;
  cbbChSrcList[0]    := cbbCh1Src;
  edtChAmpList[0]    := edtCh1Amp;
  edtChOffsList[0]   := edtCh1Offs;
  cbbChOutList[0]    := cbbCh1Out;

  chkChEnableList[1] := chkCh2En;
  cbbChSrcList[1]    := cbbCh2Src;
  edtChAmpList[1]    := edtCh2Amp;
  edtChOffsList[1]   := edtCh2Off;
  cbbChOutList[1]    := cbbCh2Out;

  chkChEnableList[2] := chkCh3En;
  cbbChSrcList[2]    := cbbCh3Src;
  edtChAmpList[2]    := edtCh3Amp;
  edtChOffsList[2]   := edtCh3Offs;
  cbbChOutList[2]    := cbbCh3Out;

  chkChEnableList[3] := chkCh4En;
  cbbChSrcList[3]    := cbbCh4Src;
  edtChAmpList[3]    := edtCh4Amp;
  edtChOffsList[3]   := edtCh4Offs;
  cbbChOutList[3]    := cbbCh4Out;

  chkChEnableList[4] := chkCh5En;
  cbbChSrcList[4]    := cbbCh5Src;
  edtChAmpList[4]    := edtCh5Amp;
  edtChOffsList[4]   := edtCh5Offs;
  cbbChOutList[4]    := cbbCh5Out;

  chkChEnableList[5] := chkCh6En;
  cbbChSrcList[5]    := cbbCh6Src;
  edtChAmpList[5]    := edtCh6Amp;
  edtChOffsList[5]   := edtCh6Offs;
  cbbChOutList[5]    := cbbCh6Out;

  chkChEnableList[6] := chkCh7En;
  cbbChSrcList[6]    := cbbCh7Src;
  edtChAmpList[6]    := edtCh7Amp;
  edtChOffsList[6]   := edtCh7Offs;
  cbbChOutList[6]    := cbbCh7Out;

  chkChEnableList[7] := chkCh8En;
  cbbChSrcList[7]    := cbbCh8Src;
  edtChAmpList[7]    := edtCh8Amp;
  edtChOffsList[7]   := edtCh8Offs;
  cbbChOutList[7]    := cbbCh8Out;


  edtSrcFreqList[0]  := edtSrc1Freq;
  edtSrcPhaList[0]   := edtSrc1Pha;
  edtSrcFreqList[1]  := edtSrc2Freq;
  edtSrcPhaList[1]   := edtSrc2Pha;
  edtSrcFreqList[2]  := edtSrc3Freq;
  edtSrcPhaList[2]   := edtSrc3Pha;
  edtSrcFreqList[3]  := edtSrc4Freq;
  edtSrcPhaList[3]   := edtSrc4Pha;


  genRunning := false;
  LTR35_Init(hltr35);
  refreshDeviceList();
end;

procedure TMainForm.btnOpenClick(Sender: TObject);
var
  location :  TLTR_MODULE_LOCATION;
  res : Integer;
begin
   // если соединение с модулем закрыто - то открываем новое
  if LTR35_IsOpened(hltr35)<>LTR_OK then
  begin
    // информацию о крейте и слоте берем из сохраненного списка по индексу
    // текущей выбранной записи
    location := ltr35_list[ cbbModulesList.ItemIndex ];
    LTR35_Init(hltr35);
    res:=LTR35_Open(hltr35, LTRD_ADDR_DEFAULT, LTRD_PORT_DEFAULT, location.csn, location.slot);
    if res<>LTR_OK then
      MessageDlg('Не удалось установить связь с модулем: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);


    if res=LTR_OK then
    begin
      edtDevSerial.Text := String(hltr35.ModuleInfo.Serial);
      edtModification.Text := 'LTR35-' + IntToStr(hltr35.ModuleInfo.Modification);
      edtVerPld.Text := IntToStr(hltr35.ModuleInfo.VerPLD);
      edtVerFPGA.Text := IntToStr(hltr35.ModuleInfo.VerFPGA);
    end
    else
    begin
      LTR35_Close(hltr35);
    end;
  end
  else
  begin
    closeDevice;
  end;

  updateControls;
end;

procedure TMainForm.btnSetSignalClick(Sender: TObject);
const
  { таблица соответствия индексов в ComboBox и кодов источника }
  src_tbl: array[0..7] of LongWord = (LTR35_CH_SRC_SIN1, LTR35_CH_SRC_COS1,
                                       LTR35_CH_SRC_SIN2, LTR35_CH_SRC_COS2,
                                       LTR35_CH_SRC_SIN3, LTR35_CH_SRC_COS3,
                                       LTR35_CH_SRC_SIN4, LTR35_CH_SRC_COS4);

var
  i, res : Integer;

begin
   { Внимание! Для простоты в данном примере не проверяются значения параметров
     в элементах ввода, которое необходимо в реальных программах. }
   res := LTR_OK;
  { если выдача сигнала не была настроена, то сперва конфигурируем модуль.}
  if not genRunning then
  begin
    { если все каналы арифметические, то режим c одной стороны не имеет большого значение,
      однако рекомендуется использовать циклический, так как потоковый
      приводит к генерации лишних статусов с признаком отсутствия подкачиваемых данных
      и требует также поддержки этого режима от ltrd/LtrServer }
    hltr35.Cfg.Mode := LTR35_MODE_CYCLE;
    hltr35.Cfg.DataFmt := LTR35_FORMAT_24;
    { используем стандартную частоту ЦАП 192 КГц }
    LTR35_FillFreq(hltr35.Cfg, LTR35_DAC_FREQ_DEFAULT, hltr35.State.DacFreq);

    for i:=0 to LTR35_DAC_CHANNEL_CNT-1 do
    begin
      hltr35.Cfg.Ch[i].Enabled := chkChEnableList[i].Checked and (i < hltr35.ModuleInfo.DacChCnt);
      if hltr35.Cfg.Ch[i].Enabled then
      begin
        hltr35.Cfg.Ch[i].Source := src_tbl[cbbChSrcList[i].ItemIndex];
        hltr35.Cfg.Ch[i].ArithAmp :=  StrToFloat(edtChAmpList[i].Text);
        hltr35.Cfg.Ch[i].ArithOffs :=  StrToFloat(edtChOffsList[i].Text);

        if cbbChOutList[i].ItemIndex = 0 then
          hltr35.Cfg.Ch[i].Output := LTR35_DAC_OUT_FULL_RANGE
        else
          hltr35.Cfg.Ch[i].Output := LTR35_DAC_OUT_DIV_RANGE;
      end;
    end;

    for i:=0 to LTR35_ARITH_SRC_CNT-1 do
    begin
      hltr35.Cfg.ArithSrc[i].Delta := 2 * Pi * StrToFloat(edtSrcFreqList[i].Text)/hltr35.State.DacFreq;
      hltr35.Cfg.ArithSrc[i].Phase := 2 * Pi * StrToFloat(edtSrcPhaList[i].Text)/360;
    end;


    { Записываем конфигурацию в модуль }
    res := LTR35_Configure(hltr35);
    if res<>LTR_OK then
       MessageDlg('Не удалось установить конфигурацию модуля: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);

    if res=LTR_OK then
    begin
      { Для начала вывода после конфигурации необходимо активировать первую страницу,
        пусть даже и пустую }
      res:= LTR35_SwitchCyclePage(hltr35, 0, 10000);
      if res<>LTR_OK then
         MessageDlg('Не удалось сделать активной первую страницу памяти: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);
    end;

    if res = LTR_OK then
      genRunning:= True;
  end
  else
  begin
    { Если сигнал уже установлен, то только изменяем амплитуду и частоту }
    for i:=0 to LTR35_DAC_CHANNEL_CNT-1 do
    begin
      res := LTR35_SetArithAmp(hltr35, i, StrToFloat(edtChAmpList[i].Text), StrToFloat(edtChOffsList[i].Text));
      if res <> LTR_OK then

    end;

    for i:=0 to LTR35_ARITH_SRC_CNT-1 do
      res := LTR35_SetArithSrcDelta(hltr35, i,  2 * Pi * StrToFloat(edtSrcFreqList[i].Text)/hltr35.State.DacFreq);
  end;



  


  updateControls
end;

procedure TMainForm.btnStopGenClick(Sender: TObject);
begin
  stopGen;
  updateControls
end;

procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  closeDevice;
end;

end.
