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


отсчеты по иксу и время(е14-140)

Вы не вошли.

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

МОТ
14.03.2011 19:15:09
#1

Гость

отсчеты по иксу и время(е14-140)

меня интересует пара вопросов
1) смотрел прогу GraphPower. Понравилось что она рисует данные получаемые с АЦП непрерывно и без тормозов.Выглядит очень красиво и без прерываний. Я ставлю таймер и принимаю данные. В таймере цикл еще. то есть я хочу читать 1000 значений например. Каналов 10, поэтому кадры по 10 значений будут. Минус в том что если у таймера ставить интервал большой , то потеря данных будет как бы. Можно ли это как-то по другому сделать? Как в poerGraph сделали?
2)Проблема определения отсчетов и времени.
Например  у меня 10 каналов. То есть на каждый отводится по 10 Кгц(1мс). Если я пытаюсь в том же таймере за тик принять 1000 значений. Так как каналов 10 , то 1000 делим на 10 получаем 100 отсчетов. 100*1мс = 100мс. Если я хочу принять 8192 значения, то это будет 819.2 мс. Правильно?

14.03.2011 21:07:46
#2

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

по 2)
Период частоты 10 кГц равен 100 мкс. Если общая частота дискретизации 100 кГц (по 10 каналам) и отсутствует межкадровая задержка, то частота на канал будет 10 кГц. Ну и так далее.
Но читается все равно весь поток - SamplingRate 16-битных слов в секунду без учета межкадровой задержки. Это уже потом его можно разобрать на каналы. Поэтому с точки зрения скорости поступления данных из модуля количество каналов можно не учитывать.

Не думаю, что хорошая идея принимать данные по таймеру. Лучше принимать данные постоянно, т.е. с той скоростью, с которой они поступают. Для этого и служат асинхронные запросы (overlapped I/O). Но можно подобрать размер одного буфера под нужную порцию данных, тогда сама последовательность событий (events) по завершению запросов на чтение образует своего рода «таймер» (когда сбор данных запущен, разумеется).

МОТ
14.03.2011 22:58:28
#3

Гость

Re: отсчеты по иксу и время(е14-140)

Александр Е как так  Период частоты 10 кГц равен 100 мкс.?
1// 10 000 = 0,0001 секунды= 0,1 мс

МОТ
14.03.2011 23:01:18
#4

Гость

Re: отсчеты по иксу и время(е14-140)

>Не думаю, что хорошая идея принимать данные по >таймеру. Лучше принимать данные постоянно, т.е. с >той скоростью, с которой они поступают. Для этого >и служат асинхронные запросы (overlapped I/O). Но >можно подобрать размер одного буфера под нужную >порцию данных, тогда сама последовательность >событий (events) по завершению запросов на чтение >образует своего рода «таймер» (когда сбор данных >запущен, разумеется).

Александр Е, можете продемонстрировать пример как это?  не совсем понял как это реализовать программно

МОТ
14.03.2011 23:27:52
#5

Гость

Re: отсчеты по иксу и время(е14-140)

WORD i;
    // сбросим флажок наличия ошибки
    ThreadError = false;
    // мы в потоке
    MainForm->IsSynchroThreadRunning = true;
    // дескриптор устройства
    ModuleHandle = MainForm->ModuleHandle;
    // кол-во акивных каналов
    ChannelsQuantity = MainForm->ChannelsQuantity;

    // общее кол-во собираемых данных
    PointsToRead = ChannelPoint * ChannelsQuantity;
    // попробуем выделить буфер под получаемые с платы данные
    ReadBuffer     = new SHORT[PointsToRead];
    if(!ReadBuffer) { Mes = "Не могу выделить память под получаемые данные!"; ShowErrorMessageBox(); return; }
    // структура параметров работы АЦП модуля
//    ap = MainForm->ap;

    // формируем необходимую структуру для сбора данных
    ZeroMemory(&ReadOv, sizeof(OVERLAPPED));
    // создаём событие для асинхронного запроса
    ReadOv.hEvent = CreateEvent(NULL, FALSE , FALSE, NULL);
    if(!ReadOv.hEvent) { Mes = "Не могу создать событие ReadEvent!"; ShowErrorMessageBox(); return; }
    // формируем структуру IoReq
    IoReq.Buffer = ReadBuffer;
    IoReq.NumberOfWordsToPass = PointsToRead;
    IoReq.NumberOfWordsPassed = 0x0;
    IoReq.Overlapped = &ReadOv;
    IoReq.TimeOut = PointsToRead/ap.AdcRate + 1000;

это пример кода инициализации ,тут Overlapped присваивается &ReadOv;

Мне бы пример какой -нибудь простой использования в асинхронном режиме

15.03.2011 13:23:01
#6

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

>как так Период частоты 10 кГц равен 100 мкс.?
>1// 10 000 = 0,0001 секунды= 0,1 мс
0.1 мс = 100 мкс smile

>не совсем понял как это реализовать программно
Что-нибудь типа примера ReadData.cpp

Или можете посмотреть bidir.cpp http://www.lcard.ru/download/e140-console-test.zip - там пример одновременной работы АЦП и ЦАП + передача команд между потоками.

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

Таким образом обеспечивается, чтобы в ядре всегда висел хотя бы один запрос на чтение, и данные забирались из модуля в компьютер. Иными словами, из трубы льются данные, Вы берете два ведра, наполняете первое, тут же подставляете второе пустое и спокойно идете выливать воду, например, в файл на диске. Тем временем второе ведро уже начало наполняться, данные не проливаются на пол, и у Вас есть время.

Про overlapped i/o советую прочитать в MSDN http://msdn.microsoft.com/en-us/library … 85%29.aspx и описания функций  ReadFile, WriteFile, WaitForSingleObject, WaitForMultipleObjects, GetOverlappedResult, CancelIo.

Поток чтения может быть чем-то вроде:
HANDLE hStopEvent; /* Инициализируется в основной программе до потока */
SHORT ADC_Buf[2][BUF_SIZE];

DWORD WINAPI ADC_Thread(LPVOID param)
    {
    int idx;
    HANDLE hEvent[2];
    OVERLAPPED ov[2];
    DWORD TransferSize;
    HANDLE WaitList[2];

    hEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
    hEvent[1] = CreateEvent(NULL, TRUE, FALSE, NULL);

    /* Ставим в очередь первый запрос чтения данных */
    ZeroMemory(&ov[0], sizeof(OVERLAPPED));
    ov[0].hEvent = hEvent[0];
    if (!ReadFile(hDevice, ADC_Buf[0], sizeof(ADC_Buf[0]), NULL, &ov[0])
        && (GetLastError() != ERROR_IO_PENDING))
        {
        /* Ошибка */
        return 0;
        }
    idx = 1;

    for (;;)
        {
        /* Ставим в очередь следующий буфер */
        ZeroMemory(&ov[idx], sizeof(OVERLAPPED));
        ov[idx].hEvent = hEvent[idx];
        if (!ReadFile(hDevice, ADC_Buf[idx], sizeof(ADC_Buf[idx]), NULL, &ov[idx])
            && (GetLastError() != ERROR_IO_PENDING))
            {
            /* Ошибка */
            break;
            }

        idx ^= 1;
        /* Ждем окончания предыдущего чтения или прихода команды stop */
        WaitList[0] = hStopEvent;
        WaitList[1] = hEvent[idx];
        if (WaitForMultipleObjects(2, WaitList, FALSE, INFINITE) != WAIT_OBJECT_0 + 1)
            {
            /* Пришла команда stop */
            break;
            }

        if (!GetOverlappedResult(hDevice, &ov[idx], &TransferSize, TRUE))
            {
            /* Ошибка */
            break;
            }
        /* тут обработка данных ADC_Buf[idx] */

        } /* for (;;) */

    CancelIo(hDevice);
    GetOverlappedResult(hDevice, &ov[idx], &TransferSize, TRUE);
    GetOverlappedResult(hDevice, &ov[idx ^ 1], &TransferSize, TRUE);
    CloseHandle(hEvent[1]);
    CloseHandle(hEvent[0]);

    return 0;
    }

15.03.2011 13:30:00
#7

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

При написании многопоточных программ надо учитывать особенности синхронизации доступа к переменным.
Еще важный момент, который иногда забывают: время жизни структуры OVERLAPPED должно быть до полного окончания операции. Если используете CancelIo, то это только *запрос на прекращение*, после него обязательно должен быть GetOverlappedResult с параметром bWait=TRUE.

МОТ
15.03.2011 19:31:38
#8

Гость

Re: отсчеты по иксу и время(е14-140)

мда, мало что понял, ну на этом спасибо, посмотрю.
Неплохо было бы если на эту тему статья какая- то была.

15.03.2011 20:10:43
#9

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

ЬЩЕ
12.04.2011 14:29:02
#10

Гость

Re: отсчеты по иксу и время(е14-140)

1) А как вызвать поток этот?
       thread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ADC_Thread, NULL, 0, &thread_id);
Так делал че то ноль реакции

2) в том месте где у вас написано 
/* тут обработка данных ADC_Buf[idx] */

к данным обращаться так?

for (int i = 0; i < 100; i++) {
Form1->Chart1->Series[0]->AddY((double &)ADC_Buf[i]);
}

ЬЩЕ
12.04.2011 14:36:53
#11

Гость

Re: отсчеты по иксу и время(е14-140)

Александр Е, на builder c++ сделал. Посмотрите пожалуйста, правильно ли делаю? 
http://narod.ru/disk/9968781001/DataRecieve.zip.html

12.04.2011 17:00:38
#12

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

- Не надо ставить на C точки с запятой после }, это лишнее.
- Интерфейсов для OpenLDevice() достаточно перебрать 127.
- Переменная BreakFlag не используется. RunFlag тоже не ставится.
- в таблице каналов задано не то, что написано в комментариях (в коде - 2 канала, XY10 и XY12, усиление x1)
- Когда из потока пишете в элемент GUI
Form1->Chart1->Series[0]->AddY((double &)ADC_Buf[i]);
Возможны проблемы с thread-safety библиотеки VCL.
То есть, например, если в это время другой поток обращается к Chart1, что-то может рухнуть. Не забудьте о синхронизации. Почитайте http://rxlib.ru/WinLesson/bles2_1.htm
Возможно, придется переделать с WINAPI thread на борландовский класс TThread, так даже красивее получится.
- Естественно, дальше надо дописывать обработку ошибок и т.п.
- Забыли про hStopEvent, который я поставил специально для того, чтобы можно было мягко остановить поток чтения. И не надо жестко выносить поток по TerminateThread() - ему же больно.

Один раз в начале работы: hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

Перед каждым запуском потока:
ResetEvent(hStopEvent);

По команде остановки:
SetEvent(hStopEvent);
WaitForSignleObject(thread, INFINTE /*но лучше конечный таймаут*/ );

То есть мы сигнализируем событие, по которому WaitForMultipleObjects() внутри потока мгновенно вывалится с WAIT_OBJECT_0 (см. описание WINAPI), и поток корректно завершится (выйдет из своей ACD_Thread()).

А в это время основная программа, выставившая Event, этого и ждет (WaitFor... с параметром хендл потока - это ожидание завершения потока).
Если перейдете на TThread, то то же самое можно записать как thread->WaitFor();

Еще чтение: http://forum.ixbt.com/topic.cgi?id=26:4468

Удачи! И советую уделить особое внимание изучению многопоточной синхронизации, если Вы раньше не писали программы с потоками. Это очень хитрая штука - потоки выполняются параллельно, ОС в любой момент (по таймеру) может прервать один поток после ближайшей машинной инструкции и дать управление другому потоку. Доступ к общим переменным и прочим объектам надо синхронизировать. Если Вы когда-нибудь писали программы с обработчиками аппаратных прерываний (хотя бы под DOS), то очень похоже. Только тут нельзя запретить прерывания, но можно использовать mutex-ы и критические секции.
(WINAPI: CreateMutex, InitializeCriticalSection и связанные с ними функции)

ЬЩЕ
12.04.2011 20:47:26
#13

Гость

Re: отсчеты по иксу и время(е14-140)

Александр Е,
цитата "- Когда из потока пишете в элемент GUI
Form1->Chart1->Series[0]->AddY((double &)ADC_Buf[i]);
Возможны проблемы с thread-safety библиотеки VCL."

А что если в этом месте  заполнять например массив какой нибудь, включать таймер и в таймере по тику выводить синхронизированно по времени обработанный сигнал? Так  будет правильней?

14.04.2011 11:25:52
#14

Сотрудник "Л Кард"
Здесь с 18.04.2014
Сообщений: 810

Re: отсчеты по иксу и время(е14-140)

>если в этом месте заполнять например массив какой нибудь, включать таймер и в таймере по тику выводить синхронизированно

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

2) Однако действие «включить таймер» - это разве не обращение к объекту VCL? Тогда все равно нужен Synchronize.

Я бы посоветовал использовать класс TThread и метод Synchronize, а в более общем случае (если нужно не только VCL) - да, можно сделать промежуточный буфер, а о поступлении данных лучше сигнализировать событиями (Event). А можно всю обработку сделать в том же потоке и даже в файл писать - чтение же уже само по себе асинхронное.

P.S. Я Вас так упорно пугаю сложностью многопоточного программирования, потому что в нем наивный, как будто бы безобидный код может привести к сумасшедшим с точки зрения отладки (случайным и с трудом воспроизводимым) ошибкам.

Например:

/* thread A */
count++; /* счетчик считает что-то */

/* thread B */
count = 0; /* асинхронный сброс счетчика */

В зависимости от процессора и компилятора код count++ может состоять из нескольких машинных инструкций: условно запишем
(1) register tmp = count;
(2) tmp = tmp + 1;
(3) count = tmp;

Допустим, что count = 100500, и после (2) операционная система прерывает выполнение потока A и передает потоку B, где выполняется count = 0. После чего управление вновь получает поток A и радостно пишет в count значение tmp = 100501.
То есть в такой простейшей программе уже необходима сериализация доступа, т.е. надо сделать так, чтобы операция count++ была АТОМАРНОЙ, т.е. непрерываемой по отношению к другим попыткам доступа к переменной count. Для этого и существуют критические секции.

Другой пример.
Программист решил сообщить о каком-то событии другому потоку при помощи переменной int flag:

/* thread A */
if (something_happened) flag = 1;

/* thread B */
flag = 0;
start_thread_A();
while (!flag) sleep(1);
stop_thread_A();

И оно никогда не выходит из цикла while, хоть убей. Оказывается, оптимизирующий компилятор (он не знает про многопоточность) проанализировал код потока B и увидел, что переменная flag всегда равна 0 (в теле цикла нет обращений к коду, меняющему flag). Он убирает лишний код - получается while (1) sleep(1);
Эта проблема лечится объявлением переменной как
volatile int flag;

В общем, для этого примера я бы посоветовал засунуть поток в TThread (это даже красиво: поток выглядит как экземпляр класса, код ThreadProc потока записывается как виртуальный метод Execute, ожидание завершения через WaitFor, доступ к элементам VCL через Synchronize...)
Очевидно, Synchronize() будет ждать, пока другие потоки освободят VCL. То есть может быть неопределенная задержка. В это время у нас запущено два запроса на асинхронное чтение, соответственно надо оценить BUF_SIZE.