Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2010.08.27;
Скачать: [xml.tar.bz2];

Вниз

Ньюансы работы с 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;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.5 MB
Время: 0.066 c
2-1273619311
NasdaqPredictor
2010-05-12 03:08
2010.08.27
О написании собственной DLL


15-1265651858
OneYoungMan
2010-02-08 20:57
2010.08.27
Речевое общение


6-1217592758
user
2008-08-01 16:12
2010.08.27
Как перебрать все файлы с FTP ?


2-1268737444
misha_gr
2010-03-16 14:04
2010.08.27
Многопоточное приложение


2-1271055485
JohnKorsh
2010-04-12 10:58
2010.08.27
Как программно закрыть информационное сообщение.





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский