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;
    cbbCh1Signal: TComboBox;
    chkCh1En: TCheckBox;
    edtCh1Freq: TEdit;
    edtCh1Amp: TEdit;
    lblFreq: TLabel;
    lblAmp: TLabel;
    lblOffs: TLabel;
    cbbCh1Out: TComboBox;
    lblOutput: TLabel;
    chkCh2En: TCheckBox;
    cbbCh2Signal: TComboBox;
    edtCh2Freq: TEdit;
    edtCh2Amp: TEdit;
    cbbCh2Out: TComboBox;
    edtCh1Offs: TEdit;
    edtCh2Off: TEdit;
    chkCh3En: TCheckBox;
    cbbCh3Signal: TComboBox;
    edtCh3Freq: TEdit;
    edtCh3Amp: TEdit;
    edtCh3Offs: TEdit;
    cbbCh3Out: TComboBox;
    chkCh4En: TCheckBox;
    cbbCh4Signal: TComboBox;
    edtCh4Freq: TEdit;
    edtCh4Amp: TEdit;
    edtCh4Offs: TEdit;
    cbbCh4Out: TComboBox;
    chkCh5En: TCheckBox;
    cbbCh5Signal: TComboBox;
    edtCh5Freq: TEdit;
    edtCh5Amp: TEdit;
    edtCh5Offs: TEdit;
    cbbCh5Out: TComboBox;
    chkCh6En: TCheckBox;
    cbbCh6Signal: TComboBox;
    edtCh6Freq: TEdit;
    edtCh6Amp: TEdit;
    edtCh6Offs: TEdit;
    cbbCh6Out: TComboBox;
    chkCh7En: TCheckBox;
    cbbCh7Signal: TComboBox;
    edtCh7Freq: TEdit;
    edtCh7Amp: TEdit;
    edtCh7Offs: TEdit;
    cbbCh7Out: TComboBox;
    chkCh8En: TCheckBox;
    cbbCh8Signal: TComboBox;
    edtCh8Freq: TEdit;
    edtCh8Amp: TEdit;
    edtCh8Offs: TEdit;
    cbbCh8Out: TComboBox;
    btnSetSignal: TButton;
    btnStopGen: TButton;
    lbl1: 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;
    cbbChSignalList : array[0..LTR35_DAC_CHANNEL_CNT-1] of TComboBox;
    edtChFreqList   : array[0..LTR35_DAC_CHANNEL_CNT-1] of TEdit;
    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;


    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;

    { Параметры сигналов можно менять и "на лету" }
    cbbChSignalList[ch].Enabled := ch_en and sig_en;
    edtChFreqList[ch].Enabled := ch_en and sig_en;
    edtChAmpList[ch].Enabled := ch_en and sig_en;
    edtChOffsList[ch].Enabled := ch_en and sig_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;
  cbbChSignalList[0] := cbbCh1Signal;
  edtChFreqList[0]   := edtCh1Freq;
  edtChAmpList[0]    := edtCh1Amp;
  edtChOffsList[0]   := edtCh1Offs;
  cbbChOutList[0]    := cbbCh1Out;

  chkChEnableList[1] := chkCh2En;
  cbbChSignalList[1] := cbbCh2Signal;
  edtChFreqList[1]   := edtCh2Freq;
  edtChAmpList[1]    := edtCh2Amp;
  edtChOffsList[1]   := edtCh2Off;
  cbbChOutList[1]    := cbbCh2Out;

  chkChEnableList[2] := chkCh3En;
  cbbChSignalList[2] := cbbCh3Signal;
  edtChFreqList[2]   := edtCh3Freq;
  edtChAmpList[2]    := edtCh3Amp;
  edtChOffsList[2]   := edtCh3Offs;
  cbbChOutList[2]    := cbbCh3Out;

  chkChEnableList[3] := chkCh4En;
  cbbChSignalList[3] := cbbCh4Signal;
  edtChFreqList[3]   := edtCh4Freq;
  edtChAmpList[3]    := edtCh4Amp;
  edtChOffsList[3]   := edtCh4Offs;
  cbbChOutList[3]    := cbbCh4Out;

  chkChEnableList[4] := chkCh5En;
  cbbChSignalList[4] := cbbCh5Signal;
  edtChFreqList[4]   := edtCh5Freq;
  edtChAmpList[4]    := edtCh5Amp;
  edtChOffsList[4]   := edtCh5Offs;
  cbbChOutList[4]    := cbbCh5Out;

  chkChEnableList[5] := chkCh6En;
  cbbChSignalList[5] := cbbCh6Signal;
  edtChFreqList[5]   := edtCh6Freq;
  edtChAmpList[5]    := edtCh6Amp;
  edtChOffsList[5]   := edtCh6Offs;
  cbbChOutList[5]    := cbbCh6Out;

  chkChEnableList[6] := chkCh7En;
  cbbChSignalList[6] := cbbCh7Signal;
  edtChFreqList[6]   := edtCh7Freq;
  edtChAmpList[6]    := edtCh7Amp;
  edtChOffsList[6]   := edtCh7Offs;
  cbbChOutList[6]    := cbbCh7Out;

  chkChEnableList[7] := chkCh8En;
  cbbChSignalList[7] := cbbCh8Signal;
  edtChFreqList[7]   := edtCh8Freq;
  edtChAmpList[7]    := edtCh8Amp;
  edtChOffsList[7]   := edtCh8Offs;
  cbbChOutList[7]    := cbbCh8Out;

  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);
var
  Ch, ch_idx, pt: integer;
  res : integer;
  cur_freq, cur_amp, cur_offs : double;
  ch_sig_size, cur_ch_pts : LongWord;
  snd_size : LongWord;
  sent : Integer;

  dac_data : array of Double;
  out_wrds : array of LongWord;

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

    for ch:=0 to LTR35_DAC_CHANNEL_CNT-1 do
    begin

      hltr35.Cfg.Ch[Ch].Enabled := chkChEnableList[Ch].Checked and (ch < hltr35.ModuleInfo.DacChCnt);
      if hltr35.Cfg.Ch[Ch].Enabled then
      begin
        hltr35.Cfg.Ch[Ch].Source := LTR35_CH_SRC_SDRAM;
        if cbbChOutList[Ch].ItemIndex = 0 then
          hltr35.Cfg.Ch[Ch].Output := LTR35_DAC_OUT_FULL_RANGE
        else
          hltr35.Cfg.Ch[Ch].Output := LTR35_DAC_OUT_DIV_RANGE;
      end;

    end;

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

    { Если не было разрешено ни одного канала, то эта конфигурация может быть
      правильная, если мы хотим выводить только на цифровые линии. Однако в
      данном примере используем только каналы ЦАП, поэтому считаем такую конфигурацию
      ошибкой => выводим сообщение об ошибке. }
    if (res = LTR_OK) and (hltr35.State.SDRAMChCnt = 0) then
    begin
      res := LTR35_ERR_UNSUPPORTED_CONFIG;
      MessageDlg('Не было не разрешено ни одного канала ЦАП!', mtError, [mbOK], 0);
    end;
  end;


  {--------------- Загрузка нового сигнала -----------------------------------}
  if res = LTR_OK then
  begin
    { определяем количество необходимых точек на канал исходя из частот сигналов.
      Для простоты устанавливаем только частоты, которые являются частотй ЦАП,
      деленной на целвый коэффициент, т.е. когда период сигнала занимает целое
      кол-во отсчетов }
    ch_sig_size := 1;
    for ch:=0 to LTR35_DAC_CHANNEL_CNT-1 do
    begin
      if hltr35.Cfg.Ch[Ch].Enabled then
      begin
        cur_freq := StrToFloat(edtChFreqList[ch].Text);
        cur_ch_pts := Round(hltr35.State.DacFreq/cur_freq);
        { используем не меньше 2-х точек на период }
        if cur_ch_pts < 2 then
          cur_ch_pts := 2;
        { если текущер размер буфера не кратен нужному, то в качестве
          нового размера используем наименьшее общее кратное из требуемого
          размера и текущего }
        if (ch_sig_size mod cur_ch_pts) <> 0 then
          ch_sig_size := lcm(ch_sig_size, cur_ch_pts);

        { Обновляем частоту интерфейсе на реально установленную }
        cur_freq :=   hltr35.State.DacFreq/ cur_ch_pts;
        edtChFreqList[ch].Text := FloatToStrF(cur_freq, ffFixed, 10, 2)
      end;
    end;

    { устанавливаем размер выходного буфера исходя из рассчитанного кол-ва точек на
      канал и разрешенного числа каналов }
    SetLength(dac_data, ch_sig_size * hltr35.State.SDRAMChCnt);

    { Генерация данных в буфера. Сперва идет первый отсчет первого разрешенного канала,
      затем первый отсчет второго и т.д., затем вторые отсчеты. Т.е. i-ый отчет резрешенного
      канала ch (от 0) из n находится в массиве по индексу (i*n + ch) }
    ch_idx := 0;
    for ch:=0 to LTR35_DAC_CHANNEL_CNT-1 do
    begin
      if hltr35.Cfg.Ch[Ch].Enabled then
      begin
        cur_freq := StrToFloat(edtChFreqList[ch].Text);
        cur_amp  := StrToFloat(edtChAmpList[ch].Text);
        cur_offs := StrToFloat(edtChOffsList[ch].Text);
        for pt:=0 to ch_sig_size-1 do
        begin
          dac_data[pt*hltr35.State.SDRAMChCnt + ch_idx]:= cur_amp*Cos(2*Pi*pt*cur_freq/hltr35.State.DacFreq) + cur_offs;
        end;
        ch_idx:=ch_idx+1;
      end;
    end;

    { массив передваемых слов в 24-битном (в отличие от 20-битного) режиме должен быть в 2 раза больше
      исходного массива, так как каждый отсчет передается за 2 слова }
    snd_size :=  2 * ch_sig_size * hltr35.State.SDRAMChCnt;
    SetLength(out_wrds, snd_size);
    { подготовка отсчетов для передачи. LTR35_PREP_FLAGS_VOLT указывает, что исходные
      отчеты представлены в Вольтах }
    res := LTR35_PrepareDacData(hltr35, dac_data, ch_sig_size * hltr35.State.SDRAMChCnt,
                               LTR35_PREP_FLAGS_VOLT,
                               out_wrds, snd_size);
    if res <> LTR_OK then
      MessageDlg('Не подготовить данные для передачи: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);
  end;      

  if res = LTR_OK then
  begin
    { Передаем слова в модуль }
    sent:= LTR35_Send(hltr35, out_wrds, snd_size, 10*1000);
    if sent < 0 then
    begin
      res := sent;
      MessageDlg('Ошибка передачи данных: ' + LTR35_GetErrorString(res), mtError, [mbOK], 0);
    end
    else if sent < snd_size then
    begin
      res := LTR_ERROR_SEND_INSUFFICIENT_DATA;
      MessageDlg('Передано меньше данных, чем запрашивали: запрашивали ' +
                IntToStr(snd_size) + ', передали ' + IntToStr(sent), mtError, [mbOK], 0);
    end;
  end;


  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;

  updateControls
end;

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

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

end.
