Конференция "Основная" » Параллельный порт. Асинхронная запись/чтение
 
  • nicestep (20.05.08 20:12) [0]
    Необходимо провести асинхронную запись / асинхронное чтение параллельного порта COM.
    Подключено устройство, работающее по принципу "запрос - ответ". Но дело в том, что ответ от него приходит с порядочной задержкой ввиду специфики работы самого устройства (до ~3 секунд).

    Раньше все было реализовано в синхронном варианте (если WriteFile, то ReadFile и все), и для того, чтобы программа не "замирала", чтение с порта было вынесено в поток, а задержки регулировались через таймаут чтения порта (те самые 3 секунды). Но в итоге возникла проблема - иногда получалось так, что устройство начинало отвечать на, к примеру, 2,5 секунде, а на 3 секунде срабатывал таймаут ReadFile и входной пакет "обрубался". При дальнейшей обработке пакет естественно принимался неверным, после чего программа снова пыталась сделать запрос к устройству, которое на данный момент все еще отправляло "ошметки" предыдущего пакета... В результате кавардак. Увеличение таймаута - не выход, слишком много времени будет уходить на запрос к неотвечающему порту, а это ИМХО не дело.

    Для этого решил перейти на асинхронный режим. Программа передает байты на порт. Ждет окончания передачи (EV_TXCLEAR) 3 секунды. Если отправка данных завершена успешно, запускается ожидание входных данных (EV_RXCHAR), тоже на 3 секунды. Если это событие происходит, то начинается чтение с порта. Вроде красиво... К сожалению в инете в основном приведены примеры только потоков, которые постоянно читаю с порта, а мне это не нужно.

    Запись и чтение вынесены в отдельный поток, для того чтобы все эти ожидания не тормозили систему. Процедура потока приведена ниже:

    // Поток записи/чтения порта
    procedure TPortThread.Execute;
    const
     TIMEOUT = 3000;
    var
     BuffStruct: ^TBuffStruct;
     EventMask: Cardinal;
     Count: Cardinal;
     Error: Boolean;
    begin
     Error := False;
     EventMask := 0;

     if not WriteFile(FPortHandle, FOutBuff, FOutBuffCount, Count, @FOverlapped)
     then
       if not WaitCommEvent(FPortHandle, EventMask, @FOverlapped) then
         if GetLastError = ERROR_IO_PENDING then
           // Запись данных в процессе. Ожидание завершения
           if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) <> WAIT_OBJECT_0
           then
             Error := True;

     if Error and (EventMask <> EV_TXEMPTY) then begin
       PostMessage(FWindowHandle, FMessageWriteEnd, 0, 0);
       Exit;
     end else begin
       GetMem(BuffStruct, SizeOf(TBuffStruct));
       BuffStruct^.Buff := FOutBuff;
       BuffStruct^.Count := FOutBuffCount;
       PostMessage(FWindowHandle, FMessageWriteEnd, 1, Integer(BuffStruct));
     end;

     Error := False;
     EventMask := 0;
     // Ожидание начала приема байт
     if not WaitCommEvent(FPortHandle, EventMask, @FOverlapped) then
       if GetLastError = ERROR_IO_PENDING then begin
         if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) <> WAIT_OBJECT_0 then
           Error := True;
       end;

     if not Error or (EventMask = EV_RXCHAR) then begin
       GetMem(BuffStruct, SizeOf(TBuffStruct));
       with BuffStruct^ do begin
         ReadFile(FPortHandle, Buff, Length(Buff), Count, @Overlapped);
         PostMessage(FWindowHandle, FMessageReadEnd, 1, Integer(BuffStruct))
       end;
     end else begin
       PostMessage(WindowHandle, MessageReadEnd, 0, 0);
       Exit;
     end;

    end;



    Открытие порта и создание события hEvent:

    PortHandle := CreateFile(PChar(FName), GENERIC_READ or GENERIC_WRITE, 0, nil,
       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_OVERLAPPED, 0);
    ...
    FOverlapped.hEvent := CreateEvent(nil, True, False, nil);



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

    IRP_MJ_CREATE   Serial0 SUCCESS Options: Open  
    IOCTL_SERIAL_GET_BAUD_RATE Serial0 SUCCESS  
    4IOCTL_SERIAL_GET_LINE_CONTROL Serial0 SUCCESS  
    5IOCTL_SERIAL_GET_CHARS  Serial0 SUCCESS  
    6IOCTL_SERIAL_GET_HANDFLOW Serial0 SUCCESS  
    IOCTL_SERIAL_GET_BAUD_RATE Serial0 SUCCESS  
    IOCTL_SERIAL_GET_LINE_CONTROL Serial0 SUCCESS  
    IOCTL_SERIAL_GET_CHARS  Serial0 SUCCESS  
    IOCTL_SERIAL_GET_HANDFLOW Serial0 SUCCESS  
    IOCTL_SERIAL_SET_BAUD_RATE Serial0 SUCCESS Rate: 1200
    IOCTL_SERIAL_SET_RTS  Serial0 SUCCESS  
    IOCTL_SERIAL_SET_DTR  Serial0 SUCCESS  
    IOCTL_SERIAL_SET_LINE_CONTROL Serial0 SUCCESS StopBits: 1 Parity: NONE WordLength: 8
    IOCTL_SERIAL_SET_CHAR  Serial0 SUCCESS EOF:0 ERR:0 BRK:0 EVT:0 XON:11 XOFF:13
    IOCTL_SERIAL_SET_HANDFLOW Serial0 SUCCESS Shake:1 Replace:40 XonLimit:2048 XoffLimit:512
    IOCTL_SERIAL_SET_TIMEOUTS Serial0 SUCCESS RI:100 RM:10 RC:3000 WM:10 WC:10
    IOCTL_SERIAL_SET_WAIT_MASK Serial0 SUCCESS Mask: RXCHAR TXEMPTY  
    IOCTL_SERIAL_PURGE  Serial0 SUCCESS Purge: TXCLEAR RXCLEAR
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED  
    IRP_MJ_WRITE   Serial0 SUCCESS Length 3: 01 02 03  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 SUCCESS  
    IOCTL_SERIAL_WAIT_ON_MASK Serial0 CANCELLED



    Если в код вставить сброс события hEvent:

     
    // Ожидание начала приема байт
     if not WaitCommEvent(FPortHandle, EventMask, @FOverlapped) then
       if GetLastError = ERROR_IO_PENDING then begin
         ResetEvent(FOverlapped.hEvent);
         if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) <> WAIT_OBJECT_0 then
           Error := True;
       end;



    Чтение запускаться будет, но при этом считывать будет всегда 0 байт.

    Вообщем, я совсем запутался со всеми этими задержками, ожиданиями, сообщениями от том, что было сообщение о том, что было... Что я делаю неправильно. Вроде и SDK штудирую, не помогает. Что я делаю неправильно? Или может кто-нибудь делал асинхронную работу именно в режиме запрос-ответ, есть наметки?
  • tesseract © (20.05.08 20:18) [1]

    > Необходимо провести асинхронную запись / асинхронное чтение
    > параллельного порта COM.


    Com или Rs232 - синхронный  последовательный интерфейс. Параллельный LPT или IEEE-1284


    > WaitCommEvent(FPortHandle, EventMask, @FOverlapped)


    GetOverlappedResult !  Или толку в твоей "Асинхронности" ноль. Все операции с асинхронным чтением/записью делаються в отдельном потоке/потоках, иначе толку ноль.
    Ну почитай же наконец документацию.

    ЗЫ: Вообще конечно проблема с кадрами..........
  • nicestep (20.05.08 20:22) [2]
    > Com или Rs232 - синхронный  последовательный интерфейс.
    > Параллельный LPT или IEEE-1284

    RS232

    > GetOverlappedResult !  Или толку в твоей "Асинхронности"
    > ноль. Все операции с асинхронным чтением/записью делаються
    > в отдельном потоке/потоках, иначе толку ноль.

    Так ведь оно и сделано в отдельном потоке вроде
  • tesseract © (20.05.08 20:43) [3]

    > Так ведь оно и сделано в отдельном потоке вроде


    Рекомендую книгу Павла Агурова.   Не код, а каша. ERROR_IO_PENDING совершенно другая функция возвращает. WaitCommEvent тут вообще не к месту.
  • Сергей М. © (20.05.08 20:45) [4]

    > Так ведь оно и сделано в отдельном потоке вроде


    так а нафига тогда асинхронный режим ?
    У тебя что - отдельный поток еще чем-то важным занят, нежели транспортом ?
  • nicestep (20.05.08 20:58) [5]
    WaitCommEvent
    ...
    If the overlapped operation cannot be completed immediately, the function returns FALSE and the GetLastError function returns ERROR_IO_PENDING, indicating that the operation is executing in the background. When this happens, the system sets the hEvent member of the OVERLAPPED structure to the not-signaled state before WaitCommEvent returns, and then it sets it to the signaled state when one of the specified events or an error occurs. The calling process can use one of the wait functions to determine the event object's state and then use the GetOverlappedResult function to determine the results of the WaitCommEvent operation. GetOverlappedResult reports the success or failure of the operation, and the variable pointed to by the lpEvtMask parameter is set to indicate the event that occurred.



    Что я поидее и делаю... Функция вовращает Ложь, GetLastError = ERROR_IO_PENDING, т.е. операция выполняется в фоне. Запускаю одну из "wait functions"  - WaitForSingleObject - и ожидаю событие. Т.к. на регистрацию указано их 2:

    SetCommMask(PortHandle, EV_RXCHAR or EV_TXEMPTY)



    то это либо "конец записи на порт", либо "на порт приходят данные".
    Да, там сказано, что надо еще использовать и GetOverlappedResult, я почитал SDK, но ничего не понял, для чего она вообще нужна. Примеров с ней тоже нет. Вроде бы эти должны уже справляться с поставленной целью. Может подскажешь, как же мне ее применить правильно тогда?
  • nicestep (20.05.08 21:04) [6]
    > так а нафига тогда асинхронный режим ?
    > У тебя что - отдельный поток еще чем-то важным занят, нежели
    > транспортом ?

    Да ну нафиг не нужен. Все раньше и без него работало прекрасно. Просто надо, чтобы пакеты входные по таймауту не обрубались, при этом не перебарщиваяя с самим значением таймаута. А выход я нашел только один - этот. Да и пора бы уже научиться, ибо с портами работаю уже больше года.
  • nicestep (20.05.08 22:55) [7]
    Эх. Разобрался. Почему то здесь мне не указали на этот промах. Я просто не дожидался конца чтения с порта

    > WaitCommEvent тут вообще не к месту.
    К месту. Но только при ожидании входящих данных на порт. При записи он был не нужен

    > GetOverlappedResult !
    Тоже нужна, но только при чтении и только для того, чтобы определить количество присланных байт

    Вообщем, вот переписаная рабочая процедура потока

    procedure TPortThread.Execute;
    const
     TIMEOUT = 3000;
    var
     BuffStruct: ^TBuffStruct;
     EventMask: Cardinal;
     Count: Cardinal;
     Error: Boolean;
    begin
     PurgeComm(PortHandle, PURGE_RXCLEAR or PURGE_TXCLEAR);
     Error := False;
     if not WriteFile(FPortHandle, FOutBuff, FOutBuffCount, Count, @FOverlapped)
     then
       if GetLastError = ERROR_IO_PENDING then
         // Запись данных в процессе. Ожидание завершения
         if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) <> WAIT_OBJECT_0
         then
           Error := True;
     if Error then begin
       PostMessage(FWindowHandle, FMessageWriteEnd, 0, 0);
       Exit;
     end else begin
       GetMem(BuffStruct, SizeOf(TBuffStruct));
       BuffStruct^.Buff := FOutBuff;
       BuffStruct^.Count := FOutBuffCount;
       PostMessage(FWindowHandle, FMessageWriteEnd, 1, Integer(BuffStruct));
     end;
     Error := False;
     EventMask := 0;
     SetCommMask(FPortHandle, EV_RXCHAR);
     // Ожидание начала приема байт
     if not WaitCommEvent(FPortHandle, EventMask, @FOverlapped) then
       if GetLastError = ERROR_IO_PENDING then begin
         if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) <> WAIT_OBJECT_0 then
           Error := True;
       end;
     if not Error or (EventMask = EV_RXCHAR) then begin
       // Прием байт начался. Чтение
       GetMem(BuffStruct, SizeOf(TBuffStruct));
       ReadFile(FPortHandle, BuffStruct^.Buff, Length(BuffStruct^.Buff), Count,
         @Overlapped);
       if WaitForSingleObject(FOverlapped.hEvent, TIMEOUT) = WAIT_OBJECT_0 then
         GetOverlappedResult(FPortHandle, FOverlapped, BuffStruct^.Count, False);
       PostMessage(FWindowHandle, FMessageReadEnd, 1, Integer(BuffStruct))
     end else begin
       PostMessage(WindowHandle, MessageReadEnd, 0, 0);
       Exit;
     end;
    end;



    Спасибо Олегу Титову за статью "Работа с коммуникационными портами (COM и LPT) в программах для Win32". И всем вам!
 
Конференция "Основная" » Параллельный порт. Асинхронная запись/чтение
Есть новые Нет новых   [134491   +8][b:0][p:0.004]