Российский производитель и разработчик сертифицированного измерительного оборудования с 1987 года

Архитектура построения ПО для съема данных с двух АЦП LTR24

Вы не вошли.

 Поиск | Регистрация | Вход 

09.07.2019 11:25:37
#1

Участник
Здесь с 28.10.2015
Сообщений: 22

Архитектура построения ПО для съема данных с двух АЦП LTR24

Здравствуйте!
Хотел бы проконсультироваться по поводу выбора архитектуры построения ПО для доступа к АЦП LTR24 в крейте LTR-EU-2. Задача состоит в том чтобы установить определенный датчик, находящийся на опорно-поворотном устройстве в определенное положение, затем считать с помощью АЦП набор отсчетов с этого датчика и записать в файл. И так для всех заранее заданных позиций. То есть по сути у меня нет необходимости в непрерывном считывании с АЦП отсчетов. Вначале я хотел было делать просто. То есть стартовать АЦП в нужные моменты, считывать разово буфер отсчетов с помощью LTR24_Recv , преобразовывать данные с помощью LTR24_ProcessData ну и далее копировать с преобразованием в поканальный буфер (преобразование заключается в том чтобы последовательность каналов превратить в поканальные данные). После всего этого просто останавливать АЦП. Но выяснилось что операция останова АЦП может занимать заметное время. Поэтому пришел в голову другой вариант. Сделать поток в котором производить непрерывный съем данных с преобразованием, а копирование данных из этого потока в промежуточный буфер делать по требования основного потока в нужные моменты. Но тут начались странности в работе ПО.
Ниже привожу код потока. Прошу прощения за длинноты, стараюсь максимально подробно

 // Потокобезопасно получаем значение флага приостановки
  TInterlocked.Exchange(Pointer(bs), Pointer(bStop));
  if bs then // Если необходимо
  begin
    WriteLnProtocolIndirect(' Thread Suspended ');
    evResume.WaitFor(); // Приостанавливаем поток
  end;
  while not Terminated do
  begin
    csr.Acquire;// Входим в критическую секцию
    WriteLnProtocolIndirect(' Thread csr Acquire');
    if Assigned(adcR) then // Если инициализирован потоковый интерфейс с АЦП
    begin
//      WriteLnProtocolIndirect('Run');
      adcR.Run; // Вызываем потоковые функции АЦП (Вот здесь выполняются Recv и ProcessData)
    end;
    // Получаем значение флага необходимости копирования
    TInterlocked.Exchange(Pointer(bc), Pointer(bnc));
    if bc then // Если необходимо
    begin
      WriteLnProtocolIndirect('Copy Samples');
      // Здесь копирование в буфер если интерфейс с АЦП передан, указатель
      // на целевой буфер задан и количество данных в допустимых границах
      if Assigned(adc) And Assigned(pCopyData) And (nCopyCount > 0) And
        (nCopyCount <= nMaxCount) then
      begin
        // Копируем
        WriteLnProtocolIndirect('PArms OK'); // Эта функция сбрасывает сообщение в очередь потока - логгера, который записывает в  лог-файл
        adc.getData(pCopyData, nCopyCount);// Копирование в заданный буфер
        WriteLnProtocolIndirect('Copy OK');
        if Assigned(vBuffer) then // Если внутренний буфер инициализирован
        begin
          // Устанавливаем на него указатель копирования по умолчанию
          WriteLnProtocolIndirect('Stay Inner Copy Buffer');
          pCopyData := @vBuffer[0];
          nCopyCount := nMaxCount; // И длину данных сообразно размеру буфера
        end
        else // Иначе
        begin
          pCopyData := nil; // Устанавливаем указатель копирования в нуль
          nCopyCount := 0; // И количество копируемых данных в нуль
        end;


      end;
      // Сбрасываем флаг необходимости копирования (операция разовая)
      TInterlocked.Exchange(Pointer(bnc), Pointer(False));
      WriteLnProtocolIndirect('Clear Copy Flag');
      evCopyAll.SetEvent; // Сигнализируем об окончании копирования
      WriteLnProtocolIndirect('Set Event');
    end;
    csr.Release; // Выходим из критической секции
    WriteLnProtocolIndirect('Thread csr Release');
    // Потокобезопасно получаем значение флага приостановки
    TInterlocked.Exchange(Pointer(bs), Pointer(bStop));
    if bs then // Если необходимо
    begin
      WriteLnProtocolIndirect(' Thread Suspended ');
      evSuspend.SetEvent; // Уведомляем о приостановке потока
      evResume.WaitFor(); // Приостанавливаем поток
    end;
  end;
  WriteLnProtocolIndirect('Thread Terminated');

А вот код функции которая вызывается из главного потока:

  Result := 0; // По умолчанию ничего не считано
  // Проверка параметров
  // Если задан внешний буфер необходимо задать и размер копирования
  if Assigned(pData) then
  begin
    if nCount < 1 then
    begin
      Exit;
    end;

    if nCount > nMaxCount then
    begin
      Exit;
    end;
  end;


  csf.Acquire; // Входим в критическую секцию потокобезопасного вызова функции

  // Потокобезопасно получаем значение флага приостановки потока
  TInterlocked.Exchange(Pointer(bs), Pointer(bStop));
  if (not Suspended) And (not bs) then // Если поток выполнения запущен
  begin




      // Входим в критическую секцию атомарного выполнения съема плюс
      // копирование
      WriteLnProtocolIndirect(' READ ADC Try Acquire csr');
      csr.Acquire;
      WriteLnProtocolIndirect('ReadADC Acquire csr');
      // Устанавливаем параметры копирования
      if Assigned(pData) then
      begin
        TInterlocked.Exchange(Pointer(pCopyData), Pointer(pData));
        TInterlocked.Exchange(nCopyCount, nCount);
      end;

      // Сбрасываем предварительно событие окончания съема плюс копирование
      evCopyAll.ResetEvent;
      WriteLnProtocolIndirect('Reset Event OK');
      // Устанавливаем флаг необходимости копирования данных в следующем цикле
      // съема
      TInterlocked.Exchange(Pointer(bnc), Pointer(True));
      WriteLnProtocolIndirect('Set NeedCopy Flag To True');
      csr.Release; // Выходим из критической секции атомарного выполнения
      WriteLnProtocolIndirect('Release csr');
      WriteLnProtocolIndirect(' Wait Copy Event ');
      evCopyAll.WaitFor(); // Ожидаем окончания операции съем плюс копирования

  end;
  csf.Release; // Заканчиваем вызов функции выходом из критической секции

Возникает странная вещь. Критическая секция csr используется для того чтобы гарантировать непрерывность цикла съема плюс копирование относительно функции из главного потока. Но лог файл показывает что при вызове функции главного потока возникает задержка то есть поток съема успевает несколько раз занять/освободить csr прежде чем основной поток таки войдет в эту критическую секцию и установит все необходимые атрибуты для копирования. Я понимаю что у меня в потоке съема задержка составляет порядка 70 мс (накапливается 4096 отсчетов при частоте дискретизации LTR24_FREQ_58K). Может кто - нибудь сталкивался с похожей задачей и можно как то более элегантно выйти из положения чем городить то что я нагородил? Или может быть проблема в потоке логгирования? Каждый вызов WriteLnProtocolIndirect содержит внутри вход в критическую секцию и ее освобождение в конце функции. Прошу прощения за длинну кода, буду рад Любой Информации. Может быть есть какие - то примеры кода как правльно делать подобные вещи для данного АЦП ???

09.07.2019 11:51:35
#2

Участник
Здесь с 28.10.2015
Сообщений: 22

Re: Архитектура построения ПО для съема данных с двух АЦП LTR24

Привожу участок лог-файла после вызова функции основного потока
4036 >> [ 11:38:16.591]  READ ADC Try Acquire csr
7024 >> [ 11:38:16.621] Thread csr Release
7024 >> [ 11:38:16.621]  Thread csr Acquire
7024 >> [ 11:38:16.691] Thread csr Release
7024 >> [ 11:38:16.691]  Thread csr Acquire
7024 >> [ 11:38:16.761] Thread csr Release
7024 >> [ 11:38:16.761]  Thread csr Acquire
7024 >> [ 11:38:16.831] Thread csr Release
7024 >> [ 11:38:16.831]  Thread csr Acquire
7024 >> [ 11:38:16.901] Thread csr Release
7024 >> [ 11:38:16.901]  Thread csr Acquire
7024 >> [ 11:38:16.971] Thread csr Release
7024 >> [ 11:38:16.971]  Thread csr Acquire
7024 >> [ 11:38:17.041] Thread csr Release
4036 >> [ 11:38:17.041] ReadADC Acquire csr
4036 >> [ 11:38:17.041] Reset Event OK
4036 >> [ 11:38:17.041] Set NeedCopy Flag To True
4036 >> [ 11:38:17.041] Release csr
4036 >> [ 11:38:17.041]  Wait Copy Event
7024 >> [ 11:38:17.041]  Thread csr Acquire
7024 >> [ 11:38:17.111] Copy Samples
7024 >> [ 11:38:17.111] PArms OK
7024 >> [ 11:38:17.111] Copy OK
7024 >> [ 11:38:17.111] Stay Inner Copy Buffer
7024 >> [ 11:38:17.111] Clear Copy Flag
7024 >> [ 11:38:17.111] Set Event

Хорошо видно что поток съема успевает несколько раз занять и осовободить критическую секцию csr прежде чем основной поток наконец в нее войдет. Как это понимать? И что с этим можно сделать ?

09.07.2019 12:49:25
#3

Сотрудник "Л Кард"
Здесь с 17.04.2014
Сообщений: 1,293

Re: Архитектура построения ПО для съема данных с двух АЦП LTR24

Добрый день.

Ну полностью Вашу программу разобрать нет возможности, это несколько выходит за рамки техподдержки.
Но если на первый взгляд, то в принципе это поведение в общем соответствует коду. Насколько я понял Вы используете именно критические секции, при этом одна занимает по сути весь цикл  потока сбора. Критические секции только разделяют доступ, автоматическое пробуждение ждущих критическую секцию потоков не происходит, поэтому после заверения критической секции поток продолжает выполняться и следующей командой ее снова захватит и только если в этот очень небольшой момент от освобождения секции до нового захвата произойдет переключение потоков, то будет выполняться основной, что не такая большая вероятность.

Вообще говоря, при работе с несколькими потоками, всегда стараются минимизировать время, когда будет захвачен примитив синхронизации. Так что у Вас все выполнение цикла (включая прием данных с АЦП) захвачена критическая секция делать не очень правильно, по сути Вы почти полностью теряете всю пользу от второго потока. Защищать примитивами синхронизации имеет смысл только момент обращения к общим ресурсам - посылку/проверку запроса на копирование и перекладывание данных, и уж точно исключить из этого момент сбора данных с самого модуля.

А сам подход к организации такого сбора может быть очень разный. Я бы, если правильно понял Вашу задачу, вообще бы скорее всего не использовал критические секции, а ограничился бы двумя событиями - запрос на сохранения данных от основного потока к потоку сбора и указание о завершении сохранения блока от потока сбора к главному.
В этом случае поток сбора может выглядеть так (если исключить запросы на останов, что может быть отдельным событием):
1. Запуск сбора с АЦП
2. Прием блока данных во внутренний буфер потока (неразделяемый с основным потоком)
3. Проверка события запроса от основного потока. Если событие не установлено, переход к пункту 2. Если событие установлено, то сброс его и переход к пункту 4.
4. Копирование принятых данных из буфера потока в общий буфер
5. Установка события завершения копирования
6. Переход к пункту 2.

В основном же потоке:
1. Сброс обоих событий. Запуск потока сбора данных
2. Если возникла необходимость получить блок данных, то переход к пункту 3, иначе выполнение своих задачу
3. Установка события запроса блока данных от основного потока
4. Ожидание установки события завершения копирования данных со сбросом события
5. Обработка данных из общего буфера
6. Переход к пункту 2.


Т.к. поток сбора использует общий буфер только после события от основного и до установки своего события, а основной поток использует его только после установки события потока сбора о завершении копирования и не устанавливает свое событие до завершения обработки, то в данном случае уже гарантируется разделение времени доступа к буферу. Основной поток при этом блокируется (хотя он тоже на пункте 4 может выполнять и другую работу, просто проверяя событие) на время ожидания перекладывания одного буфера при запросе, а поток сбора данных вообще не блокируется.

09.07.2019 13:53:19
#4

Участник
Здесь с 28.10.2015
Сообщений: 22

Re: Архитектура построения ПО для съема данных с двух АЦП LTR24

Здравствуйте, Алексей!
Огромное Вам спасибо за подробные разъяснения, все стало понятно! Я действительно не учел что никакая многоядерность здесь не спасает. Алексей по поводу проверки установки события - это просто ожидание с некоторым таймаутом ? И дальше анализ, если таймаут значит не было установлено ? И вот еще такой вопрос. Мне по сути задачи надо получить буфер отсчетов с АЦП именно после того как датчик будет установлен в нужное уголовое положение и зафиксирован. То есть все операции от LTR24_Recv до копирования в общий буфер должны быть именно после установки датчика и фиксации. Можно ли воспользоваться Вашим алгоритмом исходя из этого условия ? Не получится ли так что я считаю данные до полной фиксации датчика ?

09.07.2019 15:18:41
#5

Сотрудник "Л Кард"
Здесь с 17.04.2014
Сообщений: 1,293

Re: Архитектура построения ПО для съема данных с двух АЦП LTR24

По событиям - да. Вы либо если не нужно делать никаких действий вызываете WaitFor с большим таймаутом, либо, если нужно только проверить, и выполнять свою работу, то делате WaitFor с минимальным таймаутом (как правило для этого может быть использован нулевой таймаут, чтобы только проверить и не ждать cовсем). В любом случае нужно проверить результат на равенство wrSignaled, чтобы убедиться, что событие было установлено.

По поводу после, тогда во первых по пункту 3, если событие установлено, то после сброса принять блок и уже его сохранять, а не предыдущий, тогда будет скопирован блок, принятый уже после события.
Другой вопрос, что, строго говоря, принятый после и с сигналом, измеренным после, не совсем одно и то же, т.к. есть время на передачу данных и время задержки фильтров самого АЦП. Не совсем понятно, как в принципе Вы узнаете в своей программе о том, что датчик установился. Если нет аппаратного признака (например импульса, который можно было бы подать свободный канал LTR24 или сигнал синхронизации крейта) и задержка от установки до определения этого программой может быть меньше указанных задержек LTR24, то придется добавлять задержку либо при посылке сигнала от главного потока, либо отбрасывать больше данные при приеме сигнала синхронизации.

09.07.2019 17:33:29
#6

Участник
Здесь с 28.10.2015
Сообщений: 22

Re: Архитектура построения ПО для съема данных с двух АЦП LTR24

Алексей, Спасибо! Все понял, буду переделывать.

Контакты

Адрес: 117105, Москва, Варшавское шоссе, д. 5, корп. 4, стр. 2

Многоканальный телефон:
+7 (495) 785-95-25

Отдел продаж: sale@lcard.ru
Техническая поддержка: support@lcard.ru

Время работы: с 9-00 до 19-00 мск