Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2010.08.27;
Скачать: CL | DM;

Вниз

Ньюансы работы с COM-портами и асинхронный ввод/вывод   Найти похожие ветки 

 
shkurkin   (2010-05-29 15:26) [0]

В интернете множество статей и тем на форумах по работе с COM портами, и асинхронному вводу/выводу (а в литературе как ни странно это вопрос подробно не разбирается), но у меня так и не сложилась полная картина по этой теме.
Программу я написал и добился от нее работоспособности, но остались сомнения в грамотности ее построения и неудовлетворенность стабильносте работы.

Итак, ЗАДАЧА:
1. Программа должна взаимодействовать с внешним устройством через один COM-port, внешнее устройсво - микроконтроллер с портами ввода/вывода а также с АЦП и ЦАП.
2. Программа должна уметь отображать состояние внешнего устройства, состояние его портов ввода, а также вводить/выводить иформация с/на АЦП/ЦАП.
3. Через второй COM порт программа должна взаимодействовать с такими-же программами по радиоканалу (к порту подключен приемо/передатчик).
4. При определенных условиях прорамма должна ваступать в роле моста между двумя портами, т.е. данные которые принимаются по одному порту должны быть переданы во второй, и наоборот.

КАК Я ЭТО РЕАЛИЗОВАЛ:
1. Работа с портмами реализована через API.
2. Прием и передача по портам организована асинхронно, в отдельном потоке и прием и передача (4 потока т.к. 2 порта). Все потоки работают постоянно, т.к. данные мгут прийти в любой момент.
3. Программа использует кольцевые буферы (256 байт) на прием и передачу (4 буфера т.к. 2 потра).
4. Данные из порта читаются по байтно, т.к. детектирование команды в буфере данных выполняется побайтно для экономии времени. Команда состоит минимум из 8 байт, маскимум - 136 (до 128 байт данных).

Часть исходника 1-го потока на прием:
repeat
   lpEvntRF:=0;
   WaitCommEvent(hPortRF,lpEvntRF,@lpOvrRF);        //ждем
   if (lpEvntRF and EV_RXCHAR)=EV_RXCHAR then       //проверяем нужное событие
   begin
     ClearCommError(hPortRF,lpErrRF,@lpStatRF);     //сбрасываем флаг
     lpSzInRF := lpStatRF.cbInQue;
     ReadFile(hPortRF,bRe,lpSzInRF,lpSzOutRF,@lpOvrRF);

     // Побайтное чтение:
     if lpSzOutRF <> 0 then
     for i:=0 to (lpSzOutRF-1) do
     begin
       inc(ieReRF);                 // инкрементируем индекс буфера
       bReRF[ieReRF]:=bRe[i];
       fndCMDbRF();
       end;//for
   end;//if
 until Terminated;

Часть исходника 1-го потока на передачу:
 repeat
//Передача данных из буфера UART:
   if (fTrEvCD) then
       begin
       chrTR := bTrCD[ibTrCD];
       inc(ibTrCD);
       SzIn:=1;
       WriteFile(hPortCD,chrTr,SzIn,SzOut,@lpOvrCD);
       //Если буфер на передачу пуст:
       if (ieTrCD=ibTrCD)then
           begin
fTrEvCD := false;     // сброс флага о событии на передачу
//Обнуление индексов буфера. ???Нужно ли???
{ieTr=0;
ieTr=0;}
end//if(ieTrCD=ibTrCD)
    end;//if(fTrEvCD)
until Terminated;

ПРОБЛЕМЫ И ВОПРОСЫ:
1. Постоянно работающие потоки загружают проц, и Windows начинает тормозить, при приеме/передаче данных появляются бешенные задержки. Уменьшение приоритетов потоков улучшает ситуацию но не кардинально. Получается ситуация: 8битный микроконтроллер с частотой 16МГц реагирует на команду в течении порядка 1-5мкс. А комп с частотой более 2ГГц реагирует в течении порядка 50-1000мс! Как это можно исправить? Windows не может генерировать событие и запускать поток, чтобы он выполнялся не постоянно (примерно так реализованно у меня в микроконтроллере)?
2. При инициализации порта выставляю событие на прием - "Прием любого символа", т.е. Windows должна генерировать событие при приеме каждого байта, однако, если принимается сразу несколько байт (без паузы) генерируется одно событие по приему последнего байта, и мне в проге приходиться разбирать эти данные по байтно. Как исправить?
3. Как открыть COM порт на скорости больше 115200?
4. Как открыть порт с номером больше 9, например COM10?
5. Как я понял функция WaitCommEvent() "вешает" поток пока не произойдет событие?
6. Функция WriteFile() завершает свою работу когда данные переданны в буфер ОС или когда данные физически будут полностью переданны в
порт?

Заранее СПАСИБО ЗА ПОМОЩЬ.


 
Loginov Dmitry ©   (2010-05-29 22:36) [1]


> 1. Постоянно работающие потоки загружают проц, и Windows
> начинает тормозить, при приеме/передаче данных появляются
> бешенные задержки. Уменьшение приоритетов потоков улучшает
> ситуацию но не кардинально. Получается ситуация: 8битный
> микроконтроллер с частотой 16МГц реагирует на команду в
> течении порядка 1-5мкс. А комп с частотой более 2ГГц реагирует
> в течении порядка 50-1000мс! Как это можно исправить? Windows
> не может генерировать событие и запускать поток, чтобы он
> выполнялся не постоянно (примерно так реализованно у меня
> в микроконтроллере)?


Почему бы не использовать ГОТОВЫЕ и РАБОТОСПОСОБНЫЕ компоненты для работы с СОМ-портами. Их очень много, и в них нет таких проблем с производительностью.
Кстати, время реакции компьютера 50мс - это норма. При работающем касперском может и до 500мс доходить :)


 
Германн ©   (2010-05-30 02:38) [2]


> а в литературе как ни странно это вопрос подробно не разбирается

1. Могу выслать на почту книгу П.Агурова. В ней этот вопрос достаточно подробно разобран.
2.
> Почему бы не использовать ГОТОВЫЕ и РАБОТОСПОСОБНЫЕ компоненты
> для работы с СОМ-портами
?
Могу посоветовать tpapro. http://sourceforge.net/projects/tpapro/


 
shkurkin ©   (2010-05-30 05:25) [3]

Спасибо за советы.
Герман, вышли пожалуйста книгу: shkurkins@yandex.ru.
Уже вчера с помощью этого форума нашел компонент "BComPort". Вроде все понятно, но начинаю реализовывать захожу в тупик (после нескольких лет программирования под микроконтроллеры, мой мозг никак не хочет перестраиваться на програмирование под Windows и понимать ООП :[ ).

С приемом данных все понятно: компонент BComPort имеет событие на прием "OnRxChar" создаем обработчик события в теле самой проги и делаем в нем, что нужно. Никаких дополнительных потоков.
ПРОБЛЕМА с передачей данных: почему в моих собственных процедурах не могу обращатся к компоненту "BComPort"? Например:
procedure TrPortRF(Sender: TObject);
var
 str: string;

begin
 //Ожидание завершения предыдущей асинхронной передачи
 repeat
 until BComPortRF.IsAsyncCompleted(pAsyncRF);   // *** ЗДЕСЬ ПРОИСХОДИТ ОШИБКА
 BComPortRF.WaitForAsync(pAsyncRF);
 DoneAsync(pAsyncRF);
 //Новая асинхронная передача:
 if (bTrRf <> "") then
   begin
   str := bTrRf;
   InitAsync(pAsyncRF);
   BComPortRF.WriteStrAsync(str, pAsyncRF);
   bTrRf := "";
   end;
end;//TrPortRf


Компилятор сообщает что "BComPortRF" не описанный определитель, хотя если этот же код поместить в обработчик события "OnTxEmpty" компонента BComPort - все отлично. Такая же проблема у меня была и когда делал с помощью потоков, в своих подпрограммах пытался приостановить поток, но имя  потока в подпрограмме "Не описанный определитель". Получается, что в своих подпрограммах я могу обращатся только к объектам визуальной формы. А обращаться к потокам и компоненту "BComPort" только в обработчиках событий от объектов визуальной формы. ЧТО Я ДЕЛАЮ НЕ ТАК?

И еще один вопрос: можно ли в программе вызывать обработчик события если событие не произошло?


 
YurikGL ©   (2010-05-30 09:34) [4]

Попробуй использовать компоненту comm32 ... В свое время на ней много с COM-портом работал.


 
Loginov Dmitry ©   (2010-05-30 12:32) [5]


> Могу посоветовать tpapro


Кстати, я бы не стал рекомендовать именно эти компоненты. Может у меня версия этих компонент не самая новая, но в моей версии весь прием данных, полученных по СОМ-порту, повешен на основной поток.


 
Плохиш ©   (2010-05-30 16:49) [6]


> Компилятор сообщает что "BComPortRF" не описанный определитель

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

За код типа <Форма>.BComPortRF полагается пожизненный эцик с гвоздями.

> И еще один вопрос: можно ли в программе вызывать обработчик
> события если событие не произошло?

Можно как и обычный метод класса, но про это надо прочитать в описании синтаксиса языка.


 
shkurkin ©   (2010-05-30 23:16) [7]

РЕШИЛ проблему с использованием компонента BComport. Работает все изумительно. Нагрузка на проц - 0%! А опрос устройств по обоим портам одновременно до 40 раз в секунду (большее количество запросов не пробовал), причем на запрос успевает приходить и ответ от устройства!

Чтобы использовать методы BComPort в любой части проги, нужно создать дочерний объект (часть методов просто описана в "public"), кстати сам компонент на форму я даже не помещал, а просто назначал для нового объекта обработчики событий.

ЗАМЕЧАНИЯ по компоненту BComPort:
Как мне кажется в описании к этому компоненту автор приводит не удачные примеры по асинхронной записи/чтении данных. Я бы не стал применять этот компонент таким образом. Почему:
1. В примере приведен код асинхронной записи:
var
 Operation1: PAsync;
 Str: String;
begin
 Str := "Hello";
 InitAsync(Operation1);
 try
   BComPort1.WriteAsync(Str[1], Length(Str), Operation1);
   repeat
     // Выполнение других действий
   until BComPort1.IsAsyncCompleted(Operation1);
   BComPort1.WaitForAsync(Operation1);
 finally
   DoneAsync(Operation1);
 end;
end;


Какой смысл в такой асинхронной записи? Пока данные физически не будут переданы в порт, выход из подпрограммы не произойдет (а мне нужно еще успеть данные принять из второго порта и как можно быстрее ответить). Когда просто достаточно вызвать метод "WriteAsync", чтобы поместить данные в буфер, и можно выходить из процедуры, а прога может делать другие действия. Чтобы продолжить передачу данных когда буфер освободиться создаем обработчик события "OnTxEmpty".

2. Асинхронное чтение из примера. Здесь назначен обработчик события "RxChar":

procedure TForm1.BComPort1RxChar(Sender: TObject; Count: Integer);
var
 Str: String;
 Stat: PAsync;
 N: Integer;
begin
 InitAsync(Stat);
 try
   BComPort1.ReadStrAsync(Str, Count, Stat);
   repeat
     // Выполнение других действий
   until BComPort1.IsAsyncCompleted(Stat);
   N := BComPort1.WaitForAsync(Stat);
   if N <> Count then SetLength(Str, Count);
 finally
   DoneAsync(Stat);
 end;
 // Действия с переменной Str
end;


Т.к. назначен обработчик события, то если он был вызван значит данные в буфере есть, использование обработчика события это уже асинхронная операция. Зачем тогда делать асинхронное чтение из буфера? Можно заранее узнать кол-во байт в буфере методом "InBufCount" и одним махом это Count кол-во байт считать методом "Read" и сразу выйти из обработчика.

Написал программу с учетом этих замечаний, все работает отлично.

P.S. Всем спасибо за участие.


 
Германн ©   (2010-05-31 02:46) [8]


> Loginov Dmitry ©   (30.05.10 12:32) [5]
>
>
> > Могу посоветовать tpapro
>
>
> Кстати, я бы не стал рекомендовать именно эти компоненты.
>  Может у меня версия этих компонент не самая новая, но в
> моей версии весь прием данных, полученных по СОМ-порту,
> повешен на основной поток.
>

Эээ. Поясни, пожалуйста, что именно тебе не нравиться?
Я эти версии давно знаю.
Что есть "весь прием данных, полученных по СОМ-порту,
> повешен на основной поток"?

P.S.
А не надо "весь приём данных" вешать на доппоток! Если "граблей по лбу" не хочешь!


 
Loginov Dmitry ©   (2010-05-31 23:28) [9]


> Я эти версии давно знаю.
> Что есть "весь прием данных, полученных по СОМ-порту,
> > повешен на основной поток"?


Это значит примерно то, что для каждого полученного байта вызывается PostMessage в основной поток, и этот самый основной поток подвисает. Может это как-то и отключается, уже не помню, но разбираться в тоннах кода крайне сложно.


> А не надо "весь приём данных" вешать на доппоток! Если "граблей
> по лбу" не хочешь!


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



Страницы: 1 вся ветка

Текущий архив: 2010.08.27;
Скачать: CL | DM;

Наверх




Память: 0.52 MB
Время: 0.193 c
2-1271803089
GalarG
2010-04-21 02:38
2010.08.27
свзь с базой в Access


2-1271063677
zod2009
2010-04-12 13:14
2010.08.27
Путь к каталогу


2-1273938390
Zoom
2010-05-15 19:46
2010.08.27
Можно ли управлять классом через TComponent ?


2-1274355596
Jacksotnik
2010-05-20 15:39
2010.08.27
Помогите составить SQL запрос


2-1269953221
anastasia78
2010-03-30 16:47
2010.08.27
посик в f1book