Модераторы: feodorv, GremlinProg, xvr, Fixin

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Обмен по COM порту, Проблема пересылки больших сообщений 
:(
    Опции темы
TheDestroyer
Дата 3.4.2009, 10:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Здравствуйте.
Для тестирования и отработки протокола обмена между микроконтроллером и управляющим блоком необходимо сделать обмен по RS-232 между двумя компьютерами. 
Сделано так: помимо основного потока программы, есть еще 3 потока - чтение, запись и управляющий поток (чтобы не зависало окно приложения).
У программы есть два режима работы - master и slave.  Master запускаю на одном компьтере, slave на другом. 
Master отсылает сообщение (его длина указывается в #define BUFSIZE 100 - количество байт), и ждет пока не прийдет сообщение назад, как только сообщение приходит назад, он отправляет новое сообщение и т.д. 
Slave ждет сообщение от Master-а, как только он его получает - тут же отправляет назад Master-у.
Также есть возможность посылать Master-ом как единичные сообщения, так и посылать сообщения циклически. (Размер сообщения, как было сказано выше, можно менять).

Запуск разового и циклического обмена происходит  соответствующими кнопками. Среда Borland C++ 6, но стараюсь писать на чистом с++, т.к. часть кода будет использоваться при написании программы под микроконтроллер.

Проблема: при пересылке небольших сообщений 1байт, 10 байт, как единично так и циклически - все работает  нормально. При пересылке больших сообщений (100 байт) даже при единичной пересылке возникают проблемы - иногда Master- ом принимается все нормально, но чаще принимается не 100 байт, а 186 байт. При отладке стало видно, что прием происходит не за один цикл (пришло сообщение, определилось, что пришло 100 байт и считалось), а за два цикла (происходит событие прихода сообщения, определяется, что принято 14 байт, считывается, затем снова происходит событие приема, определяется что принято 86 байт и считывается. Иногда во втором цикле приема приходит больше чем 86 байт).
Заметил, что если поставить точку останова на строке сразу после отправки сообщения в порт:
Код

WriteFile(COMport, bufwrHEX, sizeof(bufwrHEX), &temp, &overlappedwr);  //записать байты в порт (перекрываемая операция!)  
ОСТАНОВ. signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);      //приостановить поток, пока не завершится перекрываемая операция WriteFile

то приходит 100 байт, т.е. ровно столько сколько было отправлено и приходит за один раз.

Догадка: возможно прием большого сообщения происходит не сразу из-за того, что в цикле чтения стоит ожидание события приема байта (EV_RXCHAR), а надо бы дождаться когда прийдет все сообщение.

Как решить данную проблему?
Возможно ли вообще передавать таким образом большие сообщения? (Большие файлы-то по COM порту качать можно)
Как связана данная передача с протоколом UART? При пересылке большого сообщения оно перепаковывается в небольшие слова со стоп и старт битами и передается, или же сразу пересылается все сообщение разом?

Привожу код программы (постарался оставить самое необходимое):
Код


//=============================================================================
//..................... объявления глобальных переменных ......................
//=============================================================================

#define BUFSIZE 100     //ёмкость буфера приемо-передачи

unsigned char bufwrHEX[BUFSIZE] = {123}; // передающий буфер для обмена в HEX
unsigned char bufrdHEX[sizeof(bufwrHEX)]; // приёмный буфер для обмена в HEX
//---------------------------------------------------------------------------

HANDLE COMport;        //дескриптор порта

//структура OVERLAPPED необходима для асинхронных операций, при этом для операции чтения и записи нужно объявить разные структуры
//эти структуры необходимо объявить глобально, иначе программа не будет работать правильно
OVERLAPPED overlapped;        //будем использовать для операций чтения (см. поток ReadThread)
OVERLAPPED overlappedwr;        //будем использовать для операций записи (см. поток WriteThread)

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

int handle;              //дескриптор для работы с файлом с помощью библиотеки <io.h>

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

bool fl=0;    //флаг, указывающий на успешность операций записи (1 - успешно, 0 - не успешно)

unsigned long counter;    //счётчик принятых байтов, обнуляется при каждом открытии порта

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

bool stopfl = false; // флаг остановки цикла передачи данных
bool Read_done = true; // флаг окончания чтения информации от ведомого
bool slave_mode = false; // флаг работы программы в режиме slave
HANDLE info_read; // сигнальное событие, показывающее прочитана ли информация из порта
unsigned long Timer_cycles = 0; // переменная количества циклов таймера для счета времени по таймеру
unsigned long Bytes_received = 0; // число принятых байт за один пуск
unsigned long Bytes_written = 0;// число записанных байт в порт за один пуск
bool show_info = false; // флаг вывода информации на экран

//=============================================================================
//.............................. объявления функций ...........................
//=============================================================================

void COMOpen(void);             //открыть порт
void COMClose(void);            //закрыть порт
void Cycle_stop(void);            //остановка цикла обмена

//=============================================================================
//.............................. объявления потоков ...........................
//=============================================================================

HANDLE reader;    //дескриптор потока чтения из порта
HANDLE writer;    //дескриптор потока записи в порт
HANDLE MainThr;    //дескриптор основного потока

DWORD WINAPI ReadThread(LPVOID);
DWORD WINAPI WriteThread(LPVOID);
DWORD WINAPI MainThread(LPVOID);


//=============================================================================
//.............................. реализация потоков ...........................
//=============================================================================

//-----------------------------------------------------------------------------
//............................... поток ReadThead .............................
//-----------------------------------------------------------------------------

void ReadPrinting(int btr);
void Retranslate(void);

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

//главная функция потока, реализует приём байтов из COM-порта
DWORD WINAPI ReadThread(LPVOID)
{
 COMSTAT comstat;        //структура текущего состояния порта, в данной программе используется для определения количества принятых в порт байтов
 DWORD btr, temp, mask, signal;    //переменная temp используется в качестве заглушки

 overlapped.hEvent = CreateEvent(NULL, true, true, NULL);    //создать сигнальный объект-событие для асинхронных операций
 SetCommMask(COMport, EV_RXCHAR);                            //установить маску на срабатывание по событию приёма байта в порт
 //SetCommMask(COMport, EV_TXEMPTY);
 while(1)                        //пока поток не будет прерван, выполняем цикл
  {
   WaitCommEvent(COMport, &mask, &overlapped);                //ожидать события приёма байта (это и есть перекрываемая операция)
   signal = WaitForSingleObject(overlapped.hEvent, INFINITE);    //приостановить поток до прихода байта
   if(signal == WAIT_OBJECT_0)                        //если событие прихода байта произошло
    {
     if(GetOverlappedResult(COMport, &overlapped, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
      if((mask & EV_RXCHAR)!=0)                    //если произошло именно событие прихода байта
       {
        ClearCommError(COMport, &temp, &comstat);        //нужно заполнить структуру COMSTAT
        btr = comstat.cbInQue;                           //и получить из неё количество принятых байтов
        if(btr)                                  //если действительно есть байты для чтения
        {
         ReadFile(COMport, bufrdHEX, btr, &temp, &overlapped);     //прочитать байты из порта в буфер программы
         counter+=btr;        // увеличиваем счётчик байт за все время работы программы
         Bytes_received+=btr; // счетчик принятых байт за один пуск
         ReadPrinting(btr);   // вызываем функцию для вывода данных на экран и в файл
         SetEvent(info_read); // устанавливаем событие прочтения информации в сигнальное положение

         if (slave_mode)
         {
         // транслируем приянтое отправителю если в режиме slave
         memcpy(bufwrHEX,bufrdHEX,sizeof(bufrdHEX));
         ResumeThread(writer);               //активировать поток записи данных в порт
         } // if (slave_mode)
         memset(bufrdHEX, 0, BUFSIZE);         //очистить буфер (чтобы данные не накладывались друг на друга)
        } // if(btr)
       } // if((mask & EV_RXCHAR)!=0)
    } // if(signal == WAIT_OBJECT_0)
  } // while(1)
}

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

//выводим принятые байты на экран и в файл (если включено)
void ReadPrinting(int btr)
{
 for (int i=0;i < btr;i++)
 {
 Form1->Memo1->Lines->Text = Form1->Memo1->Lines->Text + "  " + bufrdHEX[i];
 }
 Form1->Memo1->Lines->Add("");
 Form1->StatusBar1->Panels->Items[2]->Text = "Всего принято " + IntToStr(counter) + " байт";    //выводим счётчик в строке состояния
 // если единичный запуск, то выведем информацию на форму
 if (show_info)
 {
 Form1->Label11->Caption = IntToStr(Bytes_received);
 Bytes_received = 0;
 }
 if(Form1->CheckBox3->Checked == true)  //если включен режим вывода в файл
  {
   write(handle, bufrdHEX, sizeof(bufrdHEX)); //записать в файл данные из приёмного буфера
  }
}

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

//-----------------------------------------------------------------------------
//............................... поток WriteThead ............................
//-----------------------------------------------------------------------------

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

//главная функция потока, выполняет передачу байтов из буфера в COM-порт
DWORD WINAPI WriteThread(LPVOID)
{
 DWORD temp, signal;    //temp - переменная-заглушка

 overlappedwr.hEvent = CreateEvent(NULL, true, true, NULL);      //создать событие
 while(1)
  {
  PurgeComm(COMport, PURGE_TXCLEAR);             //очистить передающий буфер порта
  WriteFile(COMport, bufwrHEX, sizeof(bufwrHEX), &temp, &overlappedwr);  //записать байты в порт (перекрываемая операция!)
  signal = WaitForSingleObject(overlappedwr.hEvent, INFINITE);      //приостановить поток, пока не завершится перекрываемая операция WriteFile
   if((signal == WAIT_OBJECT_0) && (GetOverlappedResult(COMport, &overlappedwr, &temp, true)))    //если операция завершилась успешно
     {
     Bytes_written += sizeof(bufwrHEX);
   // если единичный запуск, то выведем информацию на форму
   if (show_info)
   {
   Form1->Label14->Caption = IntToStr(Bytes_written);
   Bytes_written = 0;
   }
   SuspendThread(writer);
  }
}

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


//-----------------------------------------------------------------------------
//............................... поток MainThead ............................
//-----------------------------------------------------------------------------

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

//поток ждет ответа (чтения из COM порта) и пишет информацию в COM порт
DWORD WINAPI MainThread(LPVOID)
{
 // ============= переменные =============
int i = 0; // счетчик циклов передачи
int size=0;
// ============= программа =============
stopfl = false;
info_read = CreateEvent(NULL, true, true, NULL); // создаем событие чтения информации из порта в сигнальном состоянии, чтобы начать первый цикл
Form1->StatusBar1->Panels->Items[1]->Text  = "Идет обмен";
// ============= цикл чтения и записи информации в порт =============
 while (stopfl != true)
 {
// ============= ожидание записи информации в порт и подготовка на след. цикл =============
    // ждем пока информация не будет записана в порт
    //WaitForSingleObject(info_sent, INFINITE);
   if (WaitForSingleObject(info_read, 5000) == WAIT_OBJECT_0 ) // ждем чтения ответа 5 сек. максимум
     {
      sleep(1);
      ResetEvent(info_read);              // переключаем событие чтения информации в порт в несигнальное положение
      // заполним массив информацией
      for (int j=0;j < sizeof(bufwrHEX);j++)
      {
        bufwrHEX[j] = char(i);
        if (i > 250) {i=0;}
      } // for (int j=0;j < sizeof(bufwrHEX);j++)

      // вывод на экран
      for (int i=0;i<sizeof(bufwrHEX);i++)
           {
            Form1->Memo2->Lines->Text = Form1->Memo2->Lines->Text + "  " + bufwrHEX[i];
           }
      Form1->Memo2->Lines->Add("");

// ============= запись информации в порт =============
       PurgeComm(COMport, PURGE_TXCLEAR);  //очистить передающий буфер порта
       ResumeThread(writer);               //активировать поток записи данных в порт
       i++;
      } //  if (WaitForSingleObject(info_sent, 5000) == WAIT_OBJECT_0 ) // ждем 5 сек. максимум
   else
     { // если таймаут ожидания, остановим цикл и выведем сообщение
      stopfl = true;
      Form1->StatusBar1->Panels->Items[0]->Text  = "Таймаут ожидания записи в порт";
      Form1->Memo2->Lines->Add("Таймаут ожидания записи в порт");
      Cycle_stop();
     }

} // while (stopfl != true)
Cycle_stop();

} //  DWORD WINAPI MainThread(LPVOID)




//функция открытия и инициализации порта
void COMOpen()
{
 String portname;       //имя порта (например, "COM1", "COM2" и т.д.)
 DCB dcb;                //структура для общей инициализации порта DCB
 COMMTIMEOUTS timeouts;  //структура для установки таймаутов
 
 portname = Form1->ComboBox1->Text;    //получить имя выбранного порта

 //открыть порт, для асинхронных операций обязательно нужно указать флаг FILE_FLAG_OVERLAPPED
 COMport = CreateFile(portname.c_str(),GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
 //здесь:
 // - portname.c_str() - имя порта в качестве имени файла, c_str() преобразует строку типа String в строку в виде массива типа char, иначе функция не примет
 // - GENERIC_READ | GENERIC_WRITE - доступ к порту на чтение/записть
 // - 0 - порт не может быть общедоступным (shared)
 // - NULL - дескриптор порта не наследуется, используется дескриптор безопасности по умолчанию
 // - OPEN_EXISTING - порт должен открываться как уже существующий файл
 // - FILE_FLAG_OVERLAPPED - этот флаг указывает на использование асинхронных операций
 // - NULL - указатель на файл шаблона не используется при работе с портами

 if(COMport == INVALID_HANDLE_VALUE)            //если ошибка открытия порта
  {
   Form1->SpeedButton1->Down = false;           //отжать кнопку
   Form1->StatusBar1->Panels->Items[0]->Text = "Не удалось открыть порт";       //вывести сообщение в строке состояния
   return;
  }

 //инициализация порта

 dcb.DCBlength = sizeof(DCB);    //в первое поле структуры DCB необходимо занести её длину, она будет использоваться функциями настройки порта для контроля корректности структуры

 //считать структуру DCB из порта
 if(!GetCommState(COMport, &dcb))    //если не удалось - закрыть порт и вывести сообщение об ошибке в строке состояния
  {
   COMClose();
   Form1->StatusBar1->Panels->Items[0]->Text  = "Не удалось считать DCB";
   return;
  }

 //инициализация структуры DCB
 dcb.BaudRate = StrToInt(Form1->ComboBox2->Text);       //задаём скорость передачи 115200 бод
 dcb.fBinary = TRUE;                                    //включаем двоичный режим обмена
 dcb.fOutxCtsFlow = FALSE;                              //выключаем режим слежения за сигналом CTS
 dcb.fOutxDsrFlow = FALSE;                              //выключаем режим слежения за сигналом DSR
 dcb.fDtrControl = DTR_CONTROL_DISABLE;                 //отключаем использование линии DTR
 dcb.fDsrSensitivity = FALSE;                           //отключаем восприимчивость драйвера к состоянию линии DSR
 dcb.fNull = FALSE;                                     //разрешить приём нулевых байтов
 dcb.fRtsControl = RTS_CONTROL_DISABLE;                 //отключаем использование линии RTS
 dcb.fAbortOnError = FALSE;                             //отключаем остановку всех операций чтения/записи при ошибке
 dcb.ByteSize = 8;                                      //задаём 8 бит в байте
 dcb.Parity = 0;                                        //отключаем проверку чётности
 dcb.StopBits = 0;                                      //задаём один стоп-бит
 //dcb.EvtChar = EV_TXEMPTY;

 //загрузить структуру DCB в порт
 if(!SetCommState(COMport, &dcb))    //если не удалось - закрыть порт и вывести сообщение об ошибке в строке состояния
  {
   COMClose();
   Form1->StatusBar1->Panels->Items[0]->Text  = "Не удалось установить DCB";
   return;
  }

 //установить таймауты
 timeouts.ReadIntervalTimeout = 0;        //таймаут между двумя символами
 timeouts.ReadTotalTimeoutMultiplier = 0;    //общий таймаут операции чтения
 timeouts.ReadTotalTimeoutConstant = 0;         //константа для общего таймаута операции чтения
 timeouts.WriteTotalTimeoutMultiplier = 0;      //общий таймаут операции записи
 timeouts.WriteTotalTimeoutConstant = 0;        //константа для общего таймаута операции записи

 //записать структуру таймаутов в порт
 if(!SetCommTimeouts(COMport, &timeouts))    //если не удалось - закрыть порт и вывести сообщение об ошибке в строке состояния
  {
   COMClose();
   Form1->StatusBar1->Panels->Items[0]->Text  = "Не удалось установить тайм-ауты";
   return;
  }

 //установить размеры очередей приёма и передачи
 SetupComm(COMport,2000,2000);
 //SetupComm(COMport,50,50);

 //создать или открыть существующий файл для записи принимаемых данных
 handle = open("test.txt", O_CREAT | O_APPEND | O_BINARY | O_WRONLY, S_IREAD | S_IWRITE);

 if(handle==-1)        //если произошла ошибка открытия файла
  {
   Form1->StatusBar1->Panels->Items[1]->Text = "Ошибка открытия файла";    //вывести сообщение об этом в командной строке
   Form1->Label6->Hide();                                               //спрятать надпись с именем файла
   Form1->CheckBox3->Checked = false;                                   //сбросить и отключить галочку
   Form1->CheckBox3->Enabled = false;
  }
 else { Form1->StatusBar1->Panels->Items[0]->Text = "Файл открыт успешно"; } //иначе вывести в строке состояния сообщение об успешном открытии файла

 PurgeComm(COMport, PURGE_RXCLEAR);    //очистить принимающий буфер порта

 reader = CreateThread(NULL, 0, ReadThread, NULL, 0, NULL);            //создаём поток чтения, который сразу начнёт выполняться (предпоследний параметр = 0)
 writer = CreateThread(NULL, 0, WriteThread, NULL, CREATE_SUSPENDED, NULL);    //создаём поток записи в остановленном состоянии (предпоследний параметр = CREATE_SUSPENDED)

}

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

//функция закрытия порта
void COMClose()
{
//Примечание: так как при прерывании потоков, созданных с помощью функций WinAPI, функцией TerminateThread
//          поток может быть прерван жёстко, в любом месте своего выполнения, то освобождать дескриптор
//          сигнального объекта-события, находящегося в структуре типа OVERLAPPED, связанной с потоком,
//          следует не внутри кода потока, а отдельно, после вызова функции TerminateThread.
//          После чего нужно освободить и сам дескриптор потока.

 if(writer)        //если поток записи работает, завершить его; проверка if(writer) обязательна, иначе возникают ошибки
  {TerminateThread(writer,0);
   CloseHandle(overlappedwr.hEvent);    //нужно закрыть объект-событие
   CloseHandle(writer);
   writer = NULL;                       // надо обнулить, иначе после закртия хэндла переменная всеравно остается
  }
 if(reader)        //если поток чтения работает, завершить его; проверка if(reader) обязательна, иначе возникают ошибки
  {TerminateThread(reader,0);
   CloseHandle(overlapped.hEvent);    //нужно закрыть объект-событие
   CloseHandle(reader);
   reader = NULL;
  }
  if(MainThr)        //если поток чтения работает, завершить его; проверка if(MainThread) обязательна, иначе возникают ошибки
  {
   TerminateThread(MainThr,0);
   //CloseHandle(info_sent);    //нужно закрыть объект-событие
   CloseHandle(info_read);      //нужно закрыть объект-событие
   CloseHandle(MainThr);
   MainThr = NULL;
  }

 CloseHandle(COMport);                  //закрыть порт
 COMport=0;                //обнулить переменную для дескриптора порта
 close(handle);                //закрыть файл, в который велась запись принимаемых данных
 handle=NULL;                //обнулить переменную для дескриптора файла
                                        // надо обнулить, иначе после закртия хэндла переменная всеравно остается
}

//---------------------------------------------------------------------------
// функция остановки цикла обмена
void Cycle_stop()
{
int timer_time = 0;
float speed = 0;
stopfl = true;
Form1->StatusBar1->Panels->Items[1]->Text  = "";

if(MainThr)        //если поток чтения работает, завершить его; проверка if(MainThread) обязательна, иначе возникают ошибки
  {
   TerminateThread(MainThr,0);
   //CloseHandle(info_sent);    //нужно закрыть объект-событие
   CloseHandle(info_read);      //нужно закрыть объект-событие
   CloseHandle(MainThr);
   MainThr = NULL;
  }

// если таймер был запущен - выведем информацию
if (Form1->Timer1->Enabled)
{
Form1->Timer1->Enabled = false;
_sleep(1); // поток чтения, как правило, к этому моменту еще не завершился, ждем
timer_time = Timer_cycles * Form1->Timer1->Interval; // время работы таймера, мс
Form1->Label9->Caption = IntToStr(timer_time) + "мс";
Form1->Label11->Caption = IntToStr(Bytes_received);
Form1->Label14->Caption = IntToStr(Bytes_written);
speed = float(Bytes_written)/float(timer_time)*1000*8; // Бит/сек
speed = RoundTo(speed, 0);
Form1->Label16->Caption = FloatToStr(speed) + " Бит/сек";

// обнулим переменные статистики
Timer_cycles = 0;
Bytes_received = 0;
Bytes_written = 0;
}
}
//---------------------------------------------------------------------------

// ============= Кнопка запуска разового обмена =============
void __fastcall TForm1::Button2Click(TObject *Sender)
{
if (Form1->CheckBox5->Checked)
{show_info = true;}
else
{show_info = false;}
 PurgeComm(COMport, PURGE_TXCLEAR);             //очистить передающий буфер порта
 ResumeThread(writer);               //активировать поток записи данных в порт
}
//---------------------------------------------------------------------------

// =============  Кнопка запуска циклического обмена  =============
void __fastcall TForm1::Button4Click(TObject *Sender)
{
// ============= Запустить таймер статистики =============
Form1->Timer1->Enabled = true;
// ============= Запустить поток управления чтением и записью =============
MainThr = CreateThread(NULL, 0, MainThread, NULL, 0, NULL);    //создаём основной поток в остановленном состоянии (предпоследний параметр = CREATE_SUSPENDED)
Form1->Button4->Enabled = false;
Form1->Button5->Enabled = true;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button5Click(TObject *Sender)
{
Cycle_stop();
Form1->Button4->Enabled = true;
Form1->Button5->Enabled = false;
}


Заранее спасибо.

PM MAIL   Вверх
Dem_max
Дата 3.4.2009, 11:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1780
Регистрация: 12.4.2007

Репутация: 16
Всего: 39



У UART имеется ограниченный буффер приема, так что винда будет твои большие сообщения отдавать порциями. Так что постоянно приходяться считывать эти порции.


--------------------
Американские программисты долго не могли понять, почему русские при зависании Windоws всё время повторяют "Твой зайка написал" ("Yоur bunnу wrоte")
PM MAIL   Вверх
TheDestroyer
Дата 3.4.2009, 13:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



На счет буфера, обращаю внимание, что он выставляется
Код

 //установить размеры очередей приёма и передачи
 SetupComm(COMport,2000,2000);

Если конечно про этот буфер идет речь.
PM MAIL   Вверх
Dem_max
Дата 3.4.2009, 16:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1780
Регистрация: 12.4.2007

Репутация: 16
Всего: 39



Это промежуточный виндовый буффер, а я упоминал про реальный железковый.
Кстати программый SetupComm(COMport,2000,2000); задается чтобы не терять байты если вдруг ваша прога немножко подзависла, винда сохранит байты у себя.

Это сообщение отредактировал(а) Dem_max - 3.4.2009, 16:06


--------------------
Американские программисты долго не могли понять, почему русские при зависании Windоws всё время повторяют "Твой зайка написал" ("Yоur bunnу wrоte")
PM MAIL   Вверх
TheDestroyer
Дата 3.4.2009, 17:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Обнаружил интересный эффект: Master и Slave режим пока не рассматриваем. Просто делаю передачу с одного компьютера на другой.
При разовой передаче 15 байт максимальное число байт, которое передается за один цикл равно 14, потом идет второй цикл приема (событие приема опять в сигнальном подложении) и принимается еще 1 байт. Итого всего 15 байт принято, но за два цикла. Число 14 - какое-то магическое. Больше никак - меньше пожалуйста.
Если сделать точку останова на ClearCommError(COMport, &temp, &comstat); , то число принятых байт сразу будет равно 15-ти, т.е. сразу определится, правильное число принятых байт, а не 14+1.

Код


//...
if(GetOverlappedResult(COMport, &overlapped, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
      if((mask & EV_RXCHAR)!=0)                    //если произошло именно событие прихода байта
       {
        ClearCommError(COMport, &temp, &comstat);        //нужно заполнить структуру COMSTAT
        btr = comstat.cbInQue;                           //и получить из неё количество принятых байтов
        if(btr)                                  //если действительно есть байты для чтения
        {
         ReadFile(COMport, bufrdHEX, btr, &temp, &overlapped);     //прочитать байты из порта в буфер программы
         //...
         }
        // ...
        }
//...


Далее, если вообще не проверять количество принятых байт, а просто принудительно читать ровно столько байт, сколько было отослано:
Код

ReadFile(COMport, bufrdHEX, 15, &temp, &overlapped);     //прочитать байты из порта в буфер программы

то прочитается все нормально. Также вместо 15-ти можно ставить и другое число.
В итоге получается, что структура comstat (ее параметр comstat.cbInQue) определяется неправильно. 
Из-за чего это может случаться?
Как это поправить?

Это сообщение отредактировал(а) TheDestroyer - 3.4.2009, 17:07
PM MAIL   Вверх
xvr
Дата 3.4.2009, 17:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 40
Всего: 223



Установите timeout'ы (SetCommTimeouts) в какое нибудь осмысленное значение. Все ваши проблемы с недочитанными байтами именно из за этого. UART очень медленное устройство (по сравнению со скоростью работы CPU). Установка timeout'ов в 0 заставляет ReadFile вернуть немедленно столько байтов, сколько было прочитанно из порта к этому моменту. Ваше магическое число 14 байтов - это размер очереди (FIFO) у аппаратного UART. Он осуществляет прием с некоторым timeout'ом, на который ваши нулевые установки не распространяются.
Далее, размер прочтенных данных надо брать из вызова ReadFile (переменная temp, в строке 95), а не из переменной btr.
В ReadFile можно передавать запрос сразу на чтение всего буффера, она прочтет не более того, что реально есть.
В вашей реализации Overlapped ввод/вывод не нужен, используйте обычные блокирующие ReadFile/WriteFile. Я уже не говорю о странной конструкции с WaitCommEvent/WaitForSingleObject/ReadFile - первые 2 части явно лишние, ReadFile и сам умеет работать в Overlapped режиме
Далее, вызывать методы VCL объектов напрямую не из главного потока категорически нельзя - VCL не расчитанна на такое использование, будет масса трудноуловимых глюков (используйте класс TThread и его метод Synchronize)
И последнее - для синхронизации потоков обычно используют Event'ы или семафоры, а уж никак не запуск и остановку этих самых потоков

PM MAIL   Вверх
TheDestroyer
Дата 6.4.2009, 11:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Цитата(xvr @  3.4.2009,  17:38 Найти цитируемый пост)
14 байтов - это размер очереди (FIFO) у аппаратного UART

А размер FIFO какой у контроллера? Почему именно 14 байт... Это стандарт? Никак не могу найти спецификацию, где бы говорилось об этом аппаратном буфере.

Посоветуйте какие таймауты выставить, из каких соображений они выставляются, в инете в разных примерах разные таймауты.

Про вызов VCL из потока согласен, это пока для отладочных целей.

Про странную на ваш взгляд конструкцию - я взял пример из http://piclist.ru/S-COM-THREAD-RUS/S-COM-THREAD-RUS.html , он оказался наиболее полным и рабочим, т.к. все примеры, что нашел в инете если и работают, то не так как ожидается.
Буду признателен за любые советы.
PM MAIL   Вверх
xvr
Дата 6.4.2009, 12:41 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 40
Всего: 223



Цитата(TheDestroyer @ 6.4.2009,  11:14)
Цитата(xvr @  3.4.2009,  17:38 Найти цитируемый пост)
14 байтов - это размер очереди (FIFO) у аппаратного UART

А размер FIFO какой у контроллера? Почему именно 14 байт... Это стандарт? Никак не могу найти спецификацию, где бы говорилось об этом аппаратном буфере.

Читатйте datasheet на микросхему UART (16550 кажется)

Цитата

Посоветуйте какие таймауты выставить, из каких соображений они выставляются, в инете в разных примерах разные таймауты.
Таймауты зависят от вашей железки. С каким интервалом она шлет данные, есть ли интервалы между данными в пакете и пр.

Цитата

Про странную на ваш взгляд конструкцию - я взял пример из http://piclist.ru/S-COM-THREAD-RUS/S-COM-THREAD-RUS.html , 
Не все, что пишут в Интернете стоит повторять - здесь нечно явно излишнее. Возможно там ловили не только принятые байты - тогда эта конструкция имеет смысл.
Сделайте обычный не Overlapped read

PM MAIL   Вверх
TheDestroyer
Дата 6.4.2009, 13:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Нашел в настройках COM порта компьютера - диспетчер устройств - COM1 - параметры порта - дополнительно - использовать буферы FIFO, как раз там стоял размер 14 байт. Выключил этот буфер, результат не поменялся. 

По поводу использования не Overlapped read, в этом случае поток будет непрерывно читать из порта? Хорошо ли это, ведь рациональнее ждать прихода информации и по приходу читать ее.

Код

//главная функция потока, реализует приём байтов из COM-порта
DWORD WINAPI ReadThread(LPVOID)
{
 COMSTAT comstat;        //структура текущего состояния порта, в данной программе используется для определения количества принятых в порт байтов
 DWORD btr, temp, mask, signal;
 while(1)                        //пока поток не будет прерван, выполняем цикл
  {
        ClearCommError(COMport, &temp, &comstat);        //нужно заполнить структуру COMSTAT
        btr = comstat.cbInQue;                           //и получить из неё количество принятых байтов
        if(btr)                                  //если действительно есть байты для чтения
        {
         ReadFile(COMport, bufrd, btr, &temp, NULL);
         counter+=btr;                                          //увеличиваем счётчик байтов
         ReadPrinting();                           //вызываем функцию для вывода данных на экран и в файл
        } // if(btr)
  } // while(1)
}

PM MAIL   Вверх
xvr
Дата 6.4.2009, 13:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 40
Всего: 223



Функция ReadFile будет блокироваться, пока в буфере не появится хотя бы один байт. После чего она начнет читать байты, пока не сработает один из timeout'ов или не исчерпается буфер. Насколько я понимаю именно это вам и надо. (Timeout'ы должны быть не нулевые!)

PM MAIL   Вверх
TheDestroyer
Дата 6.4.2009, 15:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Следуя вашим советам сделал так:
установил таймауты, почитав в хэлпе про них:
Код

timeouts.ReadIntervalTimeout = 100;        //таймаут между двумя символами
 timeouts.ReadTotalTimeoutMultiplier = 15;    //общий таймаут операции чтения
 timeouts.ReadTotalTimeoutConstant = 100;         //константа для общего таймаута операции чтения
 timeouts.WriteTotalTimeoutMultiplier = 15;      //общий таймаут операции записи
 timeouts.WriteTotalTimeoutConstant = 100;        //константа для общего таймаута операции записи


Поток чтения сделал таким:
Код

DWORD WINAPI ReadThread(LPVOID)
{
 DWORD temp;    

 while(1)                        //пока поток не будет прерван, выполняем цикл
  {
         ReadFile(COMport, bufrd, 255, &temp, NULL);     //прочитать байты из порта в буфер программы
         if (temp>0)
         {
         counter+=btr;                                 //увеличиваем счётчик байтов
         ReadPrinting();                           //вызываем функцию для вывода данных на экран и в файл
         } // if (temp>0)

  } // while(1)
}


Цикл в потоке крутится непрерывно, и даже если выставить  timeouts.ReadTotalTimeoutConstant = 10000, т.е. 10-ти секундный таймаут ожидания чтения функцией ReadFile, то визуально ReadFile как мгновенно выполнялась, так и продолжает выполняться мгновенно.
В переменной temp всегда ноль.
Подскажите, плз, что не так делаю.
PM MAIL   Вверх
xvr
Дата 6.4.2009, 15:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 40
Всего: 223



FILE_FLAG_OVERLAPPED из CreateFile убрали?

PM MAIL   Вверх
TheDestroyer
Дата 6.4.2009, 16:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Нет, забыл убрать. Убрал - заработало так, как ожидалось!

Для полного понимания происходящего:
В реализации потока чтения без overlapped:
Код

DWORD WINAPI ReadThread(LPVOID)
{
 DWORD temp;    //переменная temp используется в качестве заглушки
 while(1) //пока поток не будет прерван, выполняем цикл
  {
         ReadFile(COMport, bufrd, 255, &temp, NULL);  //прочитать байты из порта в буфер программы
         if (temp>0)
         {
         counter+=temp;      //увеличиваем счётчик байтов
         ReadPrinting();     //вызываем функцию для вывода данных на экран и в файл
         } // if (temp>0)
  } // while(1)
}

Есть вопросы:
1. С вышеуказанными таймаутами ReadFile ждет появления принятых байт 100*15+100мс= 1,6 сек и возвращает false если ничего так и не пришло, true если что-то пришло и считалось, это так?
2. Если было отправлено, например, 1кБ или более, то чтение будет продолжаться, пока передача не прекратится?
3. Если 2 - верно, то возникает вопрос, если передавать, гигабайты, то не объявишь же гигабайтный буфер приема bufrd, как следует поступать?
4. Если COM порт настроен на работу на скорости  9600 бод/с, это скорость передачи полезной информации, невключая транспортные издержки в виде старт, стоп, ... битов?
5. Чтобы добиться максимальной скорости передачи информации, наверное необходимо разбивать ее на части по сколько-то байт, чтобы принимать сразу целые части, или это несущественно?
Из документации по контроллеру:
Код

A standard ASC frame consists of:
• An idle time with the signal level 1.
• One start of frame bit (SOF) with the signal level 0.
• A data field containing a programmable number of data bits (1-63).
• A parity bit (P), programmable for either even or odd parity. It is optionally possible to
handle frames without parity bit.
• One or two stop bits with the signal level 1.

т.е. кадр может содержать от 1 до 63 информационных бита. 
Следует ли перепаковывать информационные кадры специальным образом, если их размер около 16 байт?

В реализации потока чтения c overlapped:
Код

DWORD WINAPI ReadThread(LPVOID)
{
 COMSTAT comstat;        //структура текущего состояния порта, в данной программе используется для определения количества принятых в порт байтов
 DWORD btr, temp, mask, signal;    //переменная temp используется в качестве заглушки
 bool result = false;

 overlapped.hEvent = CreateEvent(NULL, true, true, NULL);    //создать сигнальный объект-событие для асинхронных операций
 SetCommMask(COMport, EV_RXCHAR);                            //установить маску на срабатывание по событию приёма байта в порт
 while(1)        //пока поток не будет прерван, выполняем цикл
  {
   WaitCommEvent(COMport, &mask, &overlapped);                //ожидать события приёма байта (это и есть перекрываемая операция)
   signal = WaitForSingleObject(overlapped.hEvent, INFINITE);    //приостановить поток до прихода байта
   if(signal == WAIT_OBJECT_0)                        //если событие прихода байта произошло
    {
     // \/ставим здесь задержку в 1 сек. или останов и все принимается как надо!
     flag = GetOverlappedResult(COMport, &overlapped, &temp, true);
     if(GetOverlappedResult(COMport, &overlapped, &temp, true)) //проверяем, успешно ли завершилась перекрываемая операция WaitCommEvent
     if(flag)
     if((mask & EV_RXCHAR)!=0)                    //если произошло именно событие прихода байта
       {
        ClearCommError(COMport, &temp, &comstat);        //нужно заполнить структуру COMSTAT
        btr = comstat.cbInQue;                           //и получить из неё количество принятых байтов
        if(btr)                                  //если действительно есть байты для чтения
        {
         result = ReadFile(COMport, bufrd, btr, &temp, &overlapped);     //прочитать байты из порта в буфер программы
         counter+=btr;        //увеличиваем счётчик байтов
         //ReadPrinting();    //вызываем функцию для вывода данных на экран и в файл
        } // if(btr)
       }// if((mask & EV_RXCHAR)!=0)
    } // if(signal == WAIT_OBJECT_0)
  } // while(1)
}


1. Так и не удалось получить за один цикл приема все 15 байт, только 14+1. Для чего может понадобиться использовать Overlapped вариант?
Казалось бы, в строке 12 происходит ожидание прихода информации, как раз тут можно было бы дождаться пока вся информация не придет и продолжить.

Спасибо.
PM MAIL   Вверх
xvr
Дата 6.4.2009, 17:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 40
Всего: 223



Цитата(TheDestroyer @ 6.4.2009,  16:21)
Есть вопросы:
1. С вышеуказанными таймаутами ReadFile ждет появления принятых байт 100*15+100мс= 1,6 сек и возвращает false если ничего так и не пришло, true если что-то пришло и считалось, это так?

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

2. Если было отправлено, например, 1кБ или более, то чтение будет продолжаться, пока передача не прекратится?
Да
Цитата

3. Если 2 - верно, то возникает вопрос, если передавать, гигабайты, то не объявишь же гигабайтный буфер приема bufrd, как следует поступать?
Прием прекратится как только в буфере bufrd кончится место
Цитата

4. Если COM порт настроен на работу на скорости  9600 бод/с, это скорость передачи полезной информации, невключая транспортные издержки в виде старт, стоп, ... битов?
Включая. Т.е. на байт придется 10-11 битовых интервалов по 9600 бит
Цитата

5. Чтобы добиться максимальной скорости передачи информации, наверное необходимо разбивать ее на части по сколько-то байт, чтобы принимать сразу целые части, или это несущественно?
Несущественно
Цитата

т.е. кадр может содержать от 1 до 63 информационных бита. 
Следует ли перепаковывать информационные кадры специальным образом, если их размер около 16 байт?
Это уж вам решать - надо их перепаковывать, или нет. Зависит от требований приложения

Цитата

1. Так и не удалось получить за один цикл приема все 15 байт, только 14+1. Для чего может понадобиться использовать Overlapped вариант?
Если одновременно с ожиданием надо сделать что то, или если надо ждать одновременно несколько событий (например - прием данных или сигнал на завершение нити приема от главной программы)


PM MAIL   Вверх
TheDestroyer
Дата 7.4.2009, 13:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 112
Регистрация: 5.11.2007

Репутация: нет
Всего: нет



Да, как только начал тестировать, оказалось, что пока ReadFile ждет, то несмотря на то, что ждет она в отдельном потоке, это ожидание подвешивает все. Например, пока идет ожидание - жму кнопку отправки данных, функции PurgeComm и WriteFile не выполняются пока ReadFile не завершит ожидание. Конечно, можно уменьшить таймауты, чтобы ReadFile "крутилась" быстрее, но это неправильно. Похоже, все-таки, нужно использовать overlapped. 

Если в режиме overlapped передавать 15 байт, то ReadFile читает 14+1 байт.
Код

btr = comstat.cbInQue;
ReadFile(COMport, bufrd, btr, &temp, &overlapped);

Если же указать явно вместо btr - 15 байт, то прочитает сразу 15 байт. Т.е. надо явно указывать сколько читать байт. 
Если указать буфер чтения больше, чем передано байт, то принимается ноль байт.
Как бы принимать в режиме overlapped ровно столько байт сколько было передано за один раз?

PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "C/C++: Системное программирование и WinAPI"
Fixin
GremlinProg
xvr
feodorv
  • Большое количество информации и примеров с использованием функций WinAPI можно найти в MSDN
  • Описание сообщений, уведомлений и примеров с использованием компонент WinAPI (BUTTON, EDIT, STATIC, и т.п.), можно найти в MSDN Control Library
  • Непосредственно, перед созданием новой темы, проверьте заголовок и удостоверьтесь, что он отражает суть обсуждения.
  • После заполнения поля "Название темы", обратите внимание на наличие и содержание панели "А здесь смотрели?", возможно Ваш вопрос уже был решен.
  • Приводите часть кода, в которой предположительно находится проблема или ошибка.
  • Если указываете код, пользуйтесь тегами [code][/code], или их кнопочными аналогами.
  • Если вопрос решен, воспользуйтесь соответствующей ссылкой, расположенной напротив названия темы.
  • Один топик - один вопрос!
  • Перед тем как создать тему - прочтите это .

На данный раздел распространяются Правила форума и Правила раздела С++:Общие вопросы .


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Chipset, Step, Fixin, GremlinProg, xvr. feodorv.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Системное программирование и WinAPI | Следующая тема »


 




[ Время генерации скрипта: 0.1601 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.