C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

 
Пропуски при приеме через COM-порт., Что в коде не так?
nikolayk
Отправлено: 07.04.2005, 22:12


Ученик-кочегар

Группа: Участник
Сообщений: 19



Кто работал с COM-портом, помогите разобраться.
Для непрерывного приема через COM-порт создан THREAD.
Принятые данные выводятся на Form1->Memo1.
Прием работает, но бывают пропуски байтов по восемь.
Ниже приведен код THREAD.
В чем тут может быть дело?

CODE
#include <vcl.h>
#pragma hdrstop

#include "CommRead1.h"
#include "Unit1.h"
#pragma package(smart_init)
extern HANDLE hCom;
extern DCB dcb;
extern OVERLAPPED    ovReader;
COMSTAT csStat;
DWORD Error;
DWORD dwCommEvent, dwRead, dwWait, result_nbr;
char  chRead;
char buf[11];
unsigned int size;
char szText2[10];
char ReadBuffer[100];
int i=0;

__fastcall CommRead1::CommRead1(bool CreateSuspended)
       : TThread(CreateSuspended)
{
}
//---------------------------------------------------------------------------
void __fastcall CommRead1::Execute()
{
do {
if (Terminated) break;

if(!ReadFile(hCom,&buf,10,&dwRead,&ovReader)){
  if(GetLastError() != ERROR_IO_PENDING)
       ShowMessage("Ошибка чтения данных");
   else {
 // Ждём завершения операции чтения
   dwWait = WaitForSingleObject(ovReader.hEvent,5000);
   switch (dwWait) {
     case WAIT_OBJECT_0: //Операция чтения закончена
          if (GetOverlappedResult(hCom,&ovReader,&result_nbr,FALSE)){
              ResetEvent(ovReader.hEvent);
              MessageBeep(MB_OK);// Стандартный звук
              Synchronize(ReadData);
              }
          else //Обработка полученных данных
              ShowMessage("Ошибка GetOverlapedResult");

          break;
     case WAIT_TIMEOUT: //Операция чтения еще не закончилась
             //MessageBeep(MB_OK);// Стандартный звук
             //ShowMessage("Наступил WAIT_TIMEOUT");
             ;
          break;
     default: //Ошибка выполнения WaitForSingleObject
             ShowMessage("Ошибка выполнения WaitForSingleObject");
          break;
     }//swich
   }//else
 }//if(!ReadFile

}while(!Terminated);//do

}
//---------------------------------------------------------------------------
void __fastcall CommRead1::ReadData()
{

buf[result_nbr]=0;

Form1->Memo1->Text =Form1->Memo1->Text+ AnsiString(buf);

}
//-----------------------------------------------------
Asher
Отправлено: 08.04.2005, 08:09


Мастер участка

Группа: Модератор
Сообщений: 550



ReadFile может выполнится и успешно, и тогда у вас теряются эти данные.
Требуется ветвь else для условия if(!ReadFile(hCom,... где следует выполнить те-же действия, что и в разделе case WAIT_OBJECT_0:
nikolayk
Отправлено: 08.04.2005, 16:09


Ученик-кочегар

Группа: Участник
Сообщений: 19



Спасибо, помогло.
nikolayk
Отправлено: 20.04.2005, 21:06


Ученик-кочегар

Группа: Участник
Сообщений: 19



При внимательном тестировании оказалось, что иногда бывают пропуски по 10 байт. По совету Asher я сделал вот так:
CODE
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "CommRead1.h"
#include "Unit1.h"
#pragma package(smart_init)
extern HANDLE hCom;
extern DCB dcb;
extern OVERLAPPED    ovReader;
DWORD dwCommEvent, dwRead, dwWait, result_nbr;
char buf[11];

//---------------------------------------------------------------------------

__fastcall CommRead1::CommRead1(bool CreateSuspended)
       : TThread(CreateSuspended)
{
Priority =tpHighest;
}
//---------------------------------------------------------------------------
void __fastcall CommRead1::Execute()
{
       //---- Place thread code here ----
do {
if (Terminated) break;

if(!ReadFile(hCom,&buf,10,&dwRead,&ovReader)){
  if(GetLastError() != ERROR_IO_PENDING)
       ShowMessage("Ошибка чтения данных");
   else {
 // Ждём завершения операции чтения
   dwWait = WaitForSingleObject(ovReader.hEvent,5000);
   switch (dwWait) {
     case WAIT_OBJECT_0: //Операция чтения закончена
          if (GetOverlappedResult(hCom,&ovReader,&result_nbr,FALSE)){
              ResetEvent(ovReader.hEvent);
              Synchronize(ReadData);
              }
          else
              ShowMessage("Ошибка GetOverlapedResult");

          break;
     case WAIT_TIMEOUT: //Операция чтения еще не закончилась
             //ShowMessage("Наступил WAIT_TIMEOUT");
             ;
          break;
     default: //Ошибка выполнения WaitForSingleObject
             ShowMessage("Ошибка выполнения WaitForSingleObject");
          break;
     }//swich
   }//else
 }//if(!ReadFile
else {
     if (GetOverlappedResult(hCom,&ovReader,&result_nbr,FALSE)){
       ResetEvent(ovReader.hEvent);
       Synchronize(ReadData);
       }
     else
        ShowMessage("Ошибка GetOverlapedResult");
     }
}while(!Terminated);//do
}
//---------------------------------------------------------------------------
void __fastcall CommRead1::ReadData()
{

buf[result_nbr]=0;
Form1->Memo1->Text =Form1->Memo1->Text+ AnsiString(buf);

}
//-----------------------------------------------------


Может ли функция передачи данных в Form1->Memo1 ReadData() настолько задерживать поток, что получаются пропуски приема? Или есть другие причины пропусков?
Георгий
Отправлено: 21.04.2005, 07:39


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



знаешь, по логике, у тебя отдельный поток занимается приёмом данных. в этом случае зачем нужен Overlaped режим? убери.

смотрим дальше и.. видим метод ReadData который поток чтения выполняет в основном потоке. т.е. работа твоего высокоприоритетного потока останавливается, разгребаются Windows сообщения и, после них, срабатывает метод ReadData. это время? время, за которое вполне могут 10 байт вылететь из 15 байтного fifo буфера rs232. сделай нормальный промежточный буфер, на несколько десятков пакетов, обложенный семаформами, и пиши в него, а в основном потоке обновление изображения выполняй по таймеру, например каждые 250 ms.

кстати, как порт настроен? на работу с fifo?
nikolayk
Отправлено: 21.04.2005, 11:54


Ученик-кочегар

Группа: Участник
Сообщений: 19



Спасибо Георгий за ответ.
Я подозревал, что метод ReadData может тормозить. Ты советуещь организовать буфер, к которому будет доступ из читающего потока и основной формы. Но как сделать, чтобы совместное использование буфера не вызвало конфликтов и, опять же, потерю данных? Если можно, приведи простой пример.
И как настроить работу COM-порта с fifo?
Георгий
Отправлено: 23.04.2005, 00:00


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



тебе нужен кольцевой буфер (очередь) обложенный объектами типа TMultiReadExclusiveWriteSynchronizer.
CODE
TMultiReadExclusiveWriteSynchronizer Synch;
//в потоке чтения порта
Synch.BeginWrite();
queue.Add();//запись в буфер данных
Synch.EndWrite();

//в потоке визуализации
data tmp;
Synch.BeginRead();
tmp=queue.Extract();//извлечение данных из очереди и создание локальной копии этих данных
Synch.EndRead();
visualize(tmp);//только после того, как закончили заботать с буфером отправляем на визуализацию _копию_ данных

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

о поводу очередей — в STL есть объект адаптер queue. он может практически из любого контейнера сделать fifo очередь. подробнее — help->Standart C++ Library Help

fifo — включал очень просто — на машине, куда ПО инсталлировалось, заходил в "панель управления-менеджер устройств-порты" и уже там, в настройках контретного порта, ставил галочку — использовать fifo.

так же есть возможность заказать буферизацию на уровне драйвера — функция SetupComm. но, похоже, у тебя она не работает — по умолчанию буфер должен устанавливаться на 2kB (по крайней мере в win98 было так) попробуй вызвать, сказать, что на 2kB хочешь буфер

забыл сказать, что далеко не факт, что визуализация данных будет идти быстрее чем, чтение их из порта — винда любит иногда что то с винта читать, да и просто тормозить, попутно притормаживая потоки с пользовательскими приоритетами, а поток с приоритетом tpHighest (кстати, я использовал tpRealtime) будет качать и качать данные в буфер.
так что надо предусмотреть корректную обработку переполнения временного буфера и посмореть что делать в этом случае — очистить его и продолжить писать или остановить запись, до очистки буфера. и так же учесть что должен делать блок визуализации в этих случаях.

Отредактировано Георгий — 23/04/2005, 00:12
nikolayk
Отправлено: 26.04.2005, 11:47


Ученик-кочегар

Группа: Участник
Сообщений: 19



Георгий, спасибо за совет. Я учел твои замечания и использовал критическую секцию и двойную буферизацию.
В потоке чтения порта я сделал так. Из буфера порта buf накапливаю данные в промежуточный буфер ReadBuffer[] размером 1000 байт. Вот функция, которая использована вместо функции Synchronize()
CODE

//---------------------------------------------------------------------------
void __fastcall CommRead1::UpdateReadBuffer()
{
unsigned int i;
EnterCriticalSection(&CS);
for (i=0;i<result_nbr;i++){
ReadBuffer[i+NumBytesReadBuffer]=buf[i];
}//NumBytesReadBuffer — это количество записанных в ReadBuffer байт
NumBytesReadBuffer=NumBytesReadBuffer+ result_nbr;
LeaveCriticalSection(&CS);
}

В потоке визуализации я по таймеру(200мс) в критической секции (быстро) копирую данные из буфера ReadBuffer в буфер ReadBuffer2 и из ReadBuffer2 вывожу на экран
CODE

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
int i;
EnterCriticalSection(&CS);
//Копирование данных из буфера ReadBuffer[] в
//буфер ReadBuffer2[] размером 1000 байт
if (NumBytesReadBuffer>0){
       for (i=0;i<NumBytesReadBuffer;i++){
       ReadBuffer2[i] =ReadBuffer[i];
       }
NumBytesReadBuffer2=NumBytesReadBuffer;
NumBytesReadBuffer=0;
}
LeaveCriticalSection(&CS);
//Вывод данных из буфера ReadBuffer2[] на экран(Memo1)
if (NumBytesReadBuffer2>0){
ReadBuffer2[NumBytesReadBuffer2]=0;
Memo1->Text =Memo1->Text+ AnsiString(ReadBuffer2);
NumBytesReadBuffer2=0;
SendMessage(Memo1->Handle,WM_VSCROLL, SB_BOTTOM, 0);
}
}

Все работает замечательно. Загрузка процессора не более 4%. Но когда я пытаюсь обеспечить прием любых байтов, включая ноль, а не только строк, и делаю вывод на экран так
CODE

for (i=0;i<NumBytesReadBuffer2;i++){
Memo1->Text =Memo1->Text+ ReadBuffer2[i];
}

вместо
CODE

Memo1->Text =Memo1->Text+ AnsiString(ReadBuffer2);

то загрузка процессора сильно возрастает и через некоторое время доходит до 100%.
Что тут можно сделать?
Георгий
Отправлено: 26.04.2005, 19:27


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



даже и не знаю в чём дело может быть..
в принципе TMemo далеко не резиновое, а ты в него новые строки добавляешь и добавляешь — может оно при перерисовке и начинает тормозить. т.к. выполняешь прокрутку на самую последнюю строку, то думаю остальные тебе не очень то и нужны — попробуй очищать Memo
CODE
Memo1->Lines->Clear();
кстати, зачем так жестоко текст в Memo добавляешь — есть более гуманные методы:
CODE
Memo1->Lines->Add("test string");
вместо
Memo1->Text = Memo1->Text + AnsiString("test string");


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

2. похоже буферы ты реализовал как обычные массивы тогда копирование из буфера в буфер можно реализовать вызвав функцию memcpy — и быстрее работает и код читать проще.

3. приняв произвольные данные (как понимаю байты в диапазоне 0x00-0xFF ) не хочешь их к текстовому представлению преобразовать? IntToStr что ли. тогда вывод на экран будет вот так выглядеть:
CODE
AnsiString tmp;
for (i=0;i<NumBytesReadBuffer2;i++)
{
AnsiString localBuf;
localBuf.printf("%02X ",ReadBuffer2[i]);
tmp+=localBuf;
};
Memo1->Lines->Add(tmp);
А то вдруг там попадаются байты которые TMemo переварить не может

PS. пропускать при приёме перестал?

Отредактировано Георгий — 26/04/2005, 21:31
nikolayk
Отправлено: 28.04.2005, 23:52


Ученик-кочегар

Группа: Участник
Сообщений: 19



Спасибо огромное, Георгий, за помощь!
Обязательно поробую все ,что ты предложил.
Пропусков при приеме не стало.
Еще один вопрос.
При приеме текст в MEMO добавляется в нижнюю строку и интересно видеть свежую информацию, т.е. нижние строки. А MEMO все время старается показывать верхние строки. Чтобы видеть нижние строки я посылаю сообщение
CODE

SendMessage(Memo1->Handle,WM_VSCROLL, SB_BOTTOM, 0);


Это вызывает непрерывное дергание MEMO с верхних строк на нижние. Можно ли как-то заставить MEMO все время показывать нижнюю строку?
nikolayk
Отправлено: 29.04.2005, 16:30


Ученик-кочегар

Группа: Участник
Сообщений: 19



Попробовал
CODE

Memo1->Lines->Add("test string");
вместо
Memo1->Text = Memo1->Text + AnsiString("test string");

Теперь после каждой "test string" добавляется перенос строки.
Но зато всегда стали показываться нижние строки без перескоков на верхние.

Вернуться в Работа с внешними устройствами