| | | | |
Работа с СОМ-портом в Windows (W9x, W2k) | Полный текст материала
Другие публикации автора: Василий Пивко
Цитата или краткий комментарий: «... Описание структур и API-функций необходимых для работы с СОМ-портом. Общие принципы построения программы для работы с СОМ-портом ...» |
Важно:- Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
- Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
- При голосовании учитывайте уровень, на который расчитан материал. "Интересность и полезность" имеет смысл оценивать относительно того, кому именно предназначался материал.
- Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.
Добавить свое мнение.
| | Содержит полезные и(или) интересные сведения | [1] | 22 | 91.7% | | | | Ничего особенно нового и интересного | [2] | 1 | 4.2% | | | | Написано неверно (обязательно укажите почему) | [3] | 1 | 4.2% | | Всего проголосовали: 24 | | | Все понятно, материал читается легко | [1] | 16 | 76.2% | | | | Есть неясности в изложении | [2] | 5 | 23.8% | | | | Непонятно написано, трудно читается | [3] | 0 | 0% | | Всего проголосовали: 21 |
[COM-порт]
Отслеживать это обсуждение
Всего сообщений: 8824-11-2015 06:04сообщение от автора материала Извините... Но " Эта железяка, по задумке автора, тупо принимает текстовые сообщения, хоть с терминала, а в ответ шлёт текстовые сообщения либо эхо (эхо можно отключать), либо результат выполненной команды, например посылаешь ей команду запустить мотор на нужное количество шагов их длительности и направления вращения: set 1 1000, run 1 L 100, в ответ получаем m1stop" - это объяснения для манагеров из салона продаж, а не пункты ТЗ для разработчика.
Должно быть четкое описание протокола. Преамбула, тело, завершение в понятиях БАЙТА, потому что любой обмен по цифровому каналу идет на уровне БАЙТОВ. То, что эти байты могут быть читабельны, т.е. есть символы ASCII - так это только для человеков. Поскольку у Вас идет обмен текстовыми байтами - скорее всего ни преамбулы ни самого тела как такового нет. Есть только завершение и это скорее всего CRLF (#13#10) или их вариации. Поэтому Ваша синхронизация по идее проста ка трусы. Принимаем все что принимаем в резиновый буфер, в котором после очередной принятой порции байт ищется завершение. Что принято до него - и есть единая посылка.Она обрабатывается, а из резинового буфера удаляется. Все.
|
|
12-11-2015 02:57Здравствуйте. С этим теперь понятно. Эта железяка, по задумке автора, тупо принимает текстовые сообщения, хоть с терминала, а в ответ шлёт текстовые сообщения либо эхо (эхо можно отключать), либо результат выполненной команды, например посылаешь ей команду запустить мотор на нужное количество шагов их длительности и направления вращения: set 1 1000, run 1 L 100, в ответ получаем m1stop, после выполнения всей команды. Программа сделанная мной на компаненте comport работает, но иногда даёт сбой при получении ответа от железяки, вот хотел средствами api попробовать переделать её, а приём текста не получается сделать. |
|
11-11-2015 19:40сообщение от автора материала А почему в принципе это условие должно сработать, если Ваша реальная железяка ни фига не знает протокола устройства из примера? Вы посмотрите по исходнику что собой представляет константа Sync... Ваша железяка даже понятия не имеет о такой последовательности байт. Я же в примере описываю используемый протокол обмена. Возьмите описание своей железяки и найдите описание протокола обмена. В его соответствии и переделайте алгоритм синхронизации в принимаемом от железяки потоке байт. |
|
11-11-2015 09:13Здравствуйте. Вопрос по статье. Пытаюсь переделать пример под свою железку, которая представляет из себя переходник usb\com для управления шаговыми моторами. Не пойму почему функция WorkComm нормально работающая в примере при посылке в порт текстового сообщения типа rel 1 ON: FillChar(S, 128, #0);
StrPCopy(S, Memo1.Text);
Com1Var.CntByte := 0;
Com1Var.Master := True;
Com1Var.Terminated := False;
Com1Var.DCB.Flags := $1;
Com1Var.DCB.BaudRate := 9600;
FillChar(Ovr,SizeOf(TOverlapped),0);
Ovr.hEvent := CreateEvent(nil,TRUE,FALSE,#0);
if not WriteFile(Com1Var.cId,s,StrLen(S),actual_bytes,@Ovr) and true then begin
if (GetLastError() = ERROR_IO_PENDING) and
(WaitForSingleObject(Ovr.hEvent,INFINITE) = Wait_Object_0)
then GetOverlappedResult(Com1Var.cId,Ovr,actual_bytes, false);
end;
CloseHandle(Ovr.hEvent);
Дальше строки if Cardinal((@RecivBuff[0])^) = Sync не работает, т. е. это условие не выполняется при любом наборе текстовых команд железки? При этом железка нормально реагирует на команды. Таймер tmLink при этом возвращает сообщение, что устройство не отвечает. |
|
22-01-2013 20:31сообщение от автора материала Т.е. "режем" связной кабель и "склеиваем" этой программой. Скорость канала 115200, протокол - запрос-ответ с длиной посылок 2-3 байта (лучше в 1 байт), трафик хотя бы 5-6 кбайт/сек. Как обойтись без "железяки"? Очень просто. Имея две пары виртуальных СОМ-портов, например от com0com, допустим СОМ5-СОМ6 и СОМ7-СОМ8, строим приемо/передатчик<->СОМ5-СОМ6<->снифер<->СОМ7-СОМ8<->приемо/передатчик. Все программно...все бесплатно...паять ничего не нужно.
Задача приемо/передатчика приняв $0001, отправить $0203 или $000102-$030405. Понятное дело, что после $FF идет снова $00. Это для простоты автоматического контроля про...щелкиваний в файле зафиксированном снифером.
Задача снифера обеспечить "склейку" и зафиксировать обмен между приемо/передатчиками в течении часа. При этом не "разбухнув" в объеме занимаемой оперативки. |
|
22-01-2013 20:12сообщение от автора материала Если говорить откровенно, то статья написана от лени. Лени отвечать одно и то же на КС наступающим на одни и те же грабли. Более того, пользующимся готовыми компонентами. Я то исходников не знаю (и знать не хочу). Я работаю с СОМ-портом от DOS-а (милое дело...лучшая ОС в мире), т.е. резидентные программы и обработчики прерываний.
СОМ-порт прост, как трусы. Начиная с 95-х Виндов малыш Билли дал удобное и всеобъемлющее API. Начиная с 98-х, можно сказать даже без ошибок ;-).
Если Вы обслуживаете сканер штрихкодов, электронные весы и т.п., то любой, даже самый кривой, компонент вполне сгодится. Что-то не получается - ответ есть в моей статье...только вот исходники примененного компонента зачастую читать надо.
Я считаю, что не хватает в статье списка рекомендуемых компонент. Прошу предлагать. Только предлагать те, на которых решена задача программного сниффера порта на принципе шлюза. Поясню в следующем посте. Боюсь из лимита выйти. |
|
11-03-2012 10:02Спасибо за оперативный ответ! |
|
11-03-2012 09:56От автора...
А никак! Потому что хоть в синхроне, хоть в асинхроне Вы сами ничего не пишете в порт. В асинхроне WriteFile Вам сразу отдаст ресурсы, а в синхроне после записи последнего байта в порт. И не надо радоваться этому (я про синхрон) - на самом деле еще минимум два байта ПОРТ ЕЩЕ НЕ ПОКИНУЛИ!!!
А вот как определить наличие железяки? Очень просто... Послали, взвели таймер... Получили ответ - "погасили" таймер, не получили - таймер ку-ка-ре-ку. Сообщение не подписано |
|
11-03-2012 09:31Добрый день!
А как понять, при асинхронном режиме, отправлена команда через WriteFile в порт или нет?
Если значении записанных байт в асинхронном режиме всегда будет 0.
Либо как можно определить подключено ли устройство к порту? |
|
30-01-2012 05:18Не буду авторизоваться...
Столкнулся с непоняткой при вызове SetupComm с параметрами равными 255. Попробовал с 253 - аналогично. SysErrorMessage дает "Недопустимый параметр". При этом четные параметры - все "ОК". В хэлпе ничего не нашел по этому поводу. Сообщение не подписано |
|
27-01-2012 02:52
27-01-2012 01:07Сергей, я Вам послал письмо на "мыло", которое Вы указали на Королевстве при регистрации. Сообщение не подписано |
|
27-01-2012 00:40сообщение от автора материала Сергей, давайте не будем нарушать правила. Такая конкретика должна идти через КС или напишите мне в личку. |
|
27-01-2012 00:23Любое обращение к визуальным объектам ТОЛЬКО через вызов методов потомка от TThread через TThread.Syncronize(TThread.MyProc). А вот в коде MyProc делайте что хотите. То есть вывод лога в memo из потока - неправильно и опасно, хотя и работает, а правильно делать это из Synchronize(AssBuff);
в процедуре AssBuff, я правильно понимаю? И вот Form1.lbl6.Caption:='Текущая скорость: '+ IntToStr(dcb.BaudRate); например, на форме, то отобразиться, то нет по этой причине?
Заранее извиняюсь за простые вопросы, в основах "плаваю". Где взять класс TCom у меня в дельфи (Lite Edition 7.3.4.1) его нет? В дельфи ничего раньше не делал, данный код попросил у другого человека и меняю его под свои нужды (заодно учусь). Вставка вывода лога в поток - чисто мое изменение. Данная программа "общается" через виртуальный компорт USB-K-Lline адаптера с электронным блоком управления автомобиля. К разным блокам надо подключаться на разной скорости (причем, физически все на одном проводе) |
|
26-01-2012 09:03Есть еще класс TCom (в нем сидят параметры порта, обработчики событий и которые вызываются TThreadCOM.DoEvent). TCom является владельцем TThreadCOM. У TCom есть свойство Connect, при изменении которого открывается/закрывается порт и генерится/уничтожается TThreadCOM.
Сообщение не подписано |
|
26-01-2012 09:02procedure TThreadCOM.Execute;
const
cRead = EV_RXCHAR or EV_RXFLAG;
cWrite = EV_TXEMPTY;
cError = EV_BREAK or EV_ERR;
cModem = EV_CTS or EV_DSR or EV_RLSD or EV_RING;
var
Buff: array[0..255] of byte;
Kols,Mask,Trans,Modem : cardinal;
FStat: TComStat;
Ovr: TOverlapped;
procedure DoAsinchrone(AError: cardinal);
begin
if AError = ERROR_IO_PENDING
then begin
if WaitForSingleObject(Ovr.hEvent,INFINITE) = Wait_Object_0
then GetOverlappedResult(FHandle,Ovr,Trans,True);
end
else begin
FMessage := SysErrorMessage(AError);
CloseHandle(Ovr.hEvent);
Exception.Create(FMessage);
end;
end;
begin
DoEvent(ceActivate);
ZeroMemory(@Ovr,sizeof(TOverlapped));
Ovr.hEvent := CreateEvent(nil,True,False,'');
while not Terminated do begin
Mask := 0;
if not WaitCommEvent(FHandle,Mask,@Ovr)
then DoAsinchrone(GetLastError);
if Terminated then break;
ClearCommError(FHandle,FError,@FStat);
if Terminated then break;
if (Mask and cError) > 0 then begin
FMessage := FOwner.GetCommErrorString(FError);
if FMessage <> '' then begin
DoEvent(ceError);
if FError <> 0 then begin
CloseHandle(Ovr.hEvent);
raise Exception.Create(FMessage);
end;
end;
end;
if Terminated then break;
if (Mask and cRead) > 0 then begin
Kols := FStat.cbInQue;
if Kols > 0 then begin
if not ReadFile(FHandle,Buff,Kols,Kols,@Ovr)
then DoAsinchrone(GetLastError);
if Terminated then break;
if Kols > 0 then begin
FOwner.FBuffRead.ReadBuff(@Buff[0],Kols);
if (Mask and EV_RXCHAR) > 0
then DoEvent(ceByte)
else DoEvent(ceFlag);
end;
end;
end;
if (Mask and cModem) > 0 then begin
GetCommModemStatus(FHandle,Modem);
if Terminated then break;
FModem.DSR := (Modem and MS_DSR_ON) > 0;
FModem.CTS := (Modem and MS_CTS_ON) > 0;
FModem.RING := (Modem and MS_RING_ON) > 0;
FModem.RLSD := (Modem and MS_RLSD_ON) > 0;
DoEvent(ceModem);
end;
if (Mask and cWrite) > 0
then DoEvent(ceWrite);
end;
CloseHandle(Ovr.hEvent);
DoEvent(ceDeactivate);
end;
Сообщение не подписано |
|
26-01-2012 08:55Очень даже Вы вольно обращаетесь с асинхронным режимом. А попытки с VCL напрямую из потока...это ваще жесть. Почитайте...везде написано, что VCL - ПОТОКОНЕБЕЗОПАСНАЯ вещь!!! Любое обращение к визуальным объектам ТОЛЬКО через вызов методов потомка от TThread через TThread.Syncronize(TThread.MyProc). А вот в коде MyProc делайте что хотите. Я в этих обработчиках никогда не юзаю VCL, поэтому вызов идет напрямую, но проверка есть примерно такая...(сокращаю однотипный код из-за ограничения в 5К на сайте)
procedure TThreadCOM.DoEvent(AEvent: TComEvent);
var
Proc: procedure of object;
begin
FcsEvent.Enter;
try
FTime := Now;
case AEvent of
ceActivate: Proc := OwnerConnect;
else Proc := nil; end;
end;
if Assigned(Proc)
then if FOwner.FSynchrone
then Synchronize(Proc)
else Proc
else case AEvent of
ceByte,ceFlag: FOwner.FBuffRead.FCount := 0;
end;
finally
FcsEvent.Leave;
end;
end;
procedure TThreadCOM.OwnerConnect;
begin
FOwner.FOnConnect(FOwner.FOwner,FOwner,FTime);
end;
Сообщение не подписано |
|
25-01-2012 20:08Вот такой код Procedure TCommThread.Execute;
var
i:Integer;
temp:dword;
comstat : TComStat;
dcb: TDCB;
Begin
N:=0;
COMport:= CreateFile(PortSt,GENERIC_READ or GENERIC_WRITE, 0, NiL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
dcb.DCBlength:= sizeof(DCB);
GetCommState(COMport, dcb);
dcb.BaudRate:= PortSpeed;
dcb.ByteSize := 8;
dcb.Parity := 0;
dcb.StopBits := 0;
SetCommState(COMport, dcb);
SetCommMask(COMport,EV_RXCHAR);
SetupComm(COMport,255,255);
overlapped.hEvent:= CreateEvent(NiL, true, true, '');
Form1.lbl6.Caption:='Текущая скорость: '+ IntToStr(dcb.BaudRate);
Repeat
WaitCommEvent(COMport, temp, @overlapped);
WaitForSingleObject(overlapped.hEvent, INFINITE);
if terminated then break;
ClearCommError(COMport, temp, @comstat);
if comstat.cbInQue >= PrRaz then
begin
sleep(0);
Form1.lbl3.Caption:= 'Будет получено: '+ IntToStr(comstat.cbInQue)+' байт';
ReadFile(COMport, BufRd, comstat.cbInQue, temp, @overlapped);
Form1.lbl4.Caption:='Пришло: '+IntToStr(temp)+' байт';
Synchronize(AssBuff);
for i:=0 to comstat.cbInQue-1 do
begin
tmpHex2:= tmpHex2+IntToHex(byte(BufRd[i]),2)+' ';
end;
Form1.mmo1.Lines.Add(' ECU—>PC '+tmpHex2);
If Pos('05 07 72', tmpHex2)<>0 then Form1.mmo2.Lines.Add(tmpHex2);
tmpHex2:='';
ECUtext:= IntToHex(byte(BufRd[12]),2)+' '+IntToHex(byte(BufRd[13]),2)+' '+IntToHex(byte(BufRd[14]),2)+' '+
IntToHex(byte(BufRd[15]),2)+' '+IntToHex(byte(BufRd[16]),2);
if (BufRd[5]=$05)and(BufRd[6]=$5D) then Form1.lbl2.Caption:= 'Номер блока управления: '+ ECUtext;
ZeroMemory (@BufRd,SizeOf(BufRd));
PurgeComm(COMport, PURGE_RXCLEAR);
end;
Until Terminated;
End;
BufRd: array [0..50]of byte;
1) Еще интересно ваше мнение по поводу:
if comstat.cbInQue >= PrRaz then
// PrRaz - количество ожидаемых байт которое рассчитывается исходя из количества направленных байт и полученного ответа
Если точно не указать сколько придет байт в порт, то в логе (memo) начинается "накопление" байт в BufRd, это конечно понятно. Убираю условие тоже нет стабильности, тоже "накопление" идет.
2) Вывод лога в memo прямо из потока - это корректно? |
|
25-01-2012 09:38для изменения скорости порт закрывать не нужно - а я этого и не говорил. Я сказал, что без закрытия порта резудьтат может быть непредсказуем. Если на Tx и Rx полный молчок, тогда поток порта железно в WaitCommEvent-е и все будет нормально. Но если это не так... Лучше все-таки делать по полной, особенно если на противоположной стороне реальная "железяка" с красным проводом. ;-) Тут даже удостоверение подрывника не поможет. ;-) Сообщение не подписано |
|
25-01-2012 08:02Василий, мне кажется, у вас одна неточность в ответе 25-01-2012 02:22: для изменения скорости порт закрывать не нужно, т.к. в GetCommState/SetCommState мы передает Handle открытого порта. |
|
25-01-2012 04:40Василий, спасибо! Сейчас побежал, завтра выложу код в котором хочу поменять скорость и попробую! |
|
25-01-2012 02:38сообщение от автора материала Вот этому А что будет с потоком, который "ждет байт" вначале не придал значения...
У Вас что поток (грубо) представляет собой что-то типа...
...
while not Terminated do begin
Count := ReadFile(...
if Count > 0 then begin
...
end;
end;
... ????????????
Это же чистый "поллинг"!!! Малыш Билли этого не любит!!! Во всяком случае под 95-ми достаточно быстро наступала Ж... - самолично убедился и начал работать по эвентам, т.е. через WaitCommEvent. В W2K и прочих вистах и семерках эксперименты не ставил, поскольку WaitCommEvent работает безупречно. |
|
25-01-2012 02:22сообщение от автора материала Если посмотреть исходники авторов компонент для СОМ-порта, то там все происходит через его закрытие со всеми вытекающими...т.е.
procedure TCom.SetBaudRate(AValue: cardinal);
begin
if AValue <> FDCB.BaudRate then begin
if FActive
then begin
Connect := False;
FDCB.BaudRate := AValue;
Connect := True;
end
else FDCB.BaudRate := AValue;
end;
end;
В принципе ничего страшного не будет, если Вы вызовите SetCommState в момент, когда поток будет находиться в WaitCommEvent-е (или асинхронной обработке этого Wait-а). По идее, в этом случае произойдет выход из этого ожидания с маской=0. Есс-но в этот момент на Rx и Tx "стоят" 1-цы.
В противном случае - надо смотреть код Вашего потока.
Если на Rx в этот момент чего-то есть, то однозначно принимаемый байт может превратиться в два или наоборот (зависит от скорости). Плюс возможно получение эвента EV_ERR из-за нарушения структуры фрейма (где ожидается стоповый бит, т.е. "1", окажется "0" или бит четности (если используется) будет не тот который нужен).
Короче говоря, для корректности Вы должны закрыть порт, поменять скорость и вновь открыть его. Т.е. что-то типа ...
поток.Terminate;
SetCommMask(FHandle,0);
CancelIo(FHandle);
CloseHandle(FHandle);
выставляем скорость
открываем порт.
Этот подход - 100% гарантия. Во что это выльется у Вас...смотрите Ваш код. |
|
24-01-2012 21:53Доброго времени суток! Надеюсь тема жива? Воникла такая проблема при работе с компортом. Требуется изменение скорости предачи порта не выходя из программы. Как это сделать? Компорт открывается в основном потоке, применяются настройки и поток "крутиться" в ожидании приема байта. Сделал пока изменение скорости при открытии программы кнопками, зашел, нажал нужную скорость, а вот что бы поменять надо выйти из программы. Можно на "лету" менять параметры (скорость)? А что будет с потоком, который "ждет байт"?
Спасибо. |
|
25-06-2009 05:05Да, Василий, я понял, спасибо. |
|
25-06-2009 01:15сообщение от автора материала Я прошу прощения за излишнее упрощение. Если у Вас три провода, то DTR, RTS - понятное дело по барабану, но значения битиков в DCB.Flags, касающиеся DSR, CTS - отнюдь не по барабану. Они должны быть установлены таким образом, чтобы прием/передача не зависила от состояния DSR, CTS. Я в статье об этом информацию всю дал. |
|
25-06-2009 01:002 Василий:
Вы меня убедили! И за одно поделились важной для меня информацией про трехпроводку (именно мой случай). Еще раз СПАСИБО! |
|
24-06-2009 23:36сообщение от автора материала Видите ли, Олег. Чего там кто-то написал...не стоит заморачиваться. У аппаратного СОМ-порта есть три регистра. Запись в эти регистры всяких битиков что-то означает, и значения этих бит никто не менял и менять не собирается. Дабы не заморачивать разработчиков этими девайсиоконтролами, малыш Билли дал ВСЕ нужные функции API. Работа через девайсиоконтролы сродни программированию под ДОС-ом, т.е. их вызовы должны подчиняться неким правилам. Когда Вы юзаете API, а смотрите PortMon-ом чего там и как, то видите, что эти API вызывают в том числе ВПОЛНЕ определенные последовательности девайсиоконтролов. Соответственно, посмотрев PortMon-овский лог чужой программы, Вам нужно ТОЛЬКО определить настройки DCB и ВСЕ! Если Вы будете работать в асинхроне - всяческие таймауты и прочая Вам по барабану. Если у Вас трехпроводный кабель (Tx,Rx,Gnd) - Вам и всяческие DTR-ы и прочая - также по-барабану. Если у Вас двухпроводка и есть интелектуальный преобразователь 232/485 - Вам и все манипуляции чужака с DTR(RTS) также наплевать и забыть.
Нет, если Вы хотите разобраться для чего чужак что-то делает помимо обязательных вещей - подумайте, а он сам понимает что делает? Если это - буржуй, то 100% он начитался теории малыша Билли о разработке программных продуктов (не помню названия) и строго ей следует. В этой теории есть постулат - пусть хреновая программа, написана через ж, но она первая, худо-бедно, но справляется с поставленной задачей - значит она будет продаваться. |
|
24-06-2009 23:082 Василий
К сожалению, это проблема не только чужой программы, но и моя... Похоже, парень, написавший эту программу, апгрейдит Celeron до Core2Duo с клавиатуры. Вот бы всем так!
Еще раз спасибо за статью!
2 Алексей Войт
Интересная информация, поковыряюсь, спасибо!
|
|
24-06-2009 15:00to Олег:
Вот нашёл у себя в DDK, может поможет:
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
)
#define FILE_DEVICE_SERIAL_PORT 0x0000001b
#define METHOD_BUFFERED 0
#define FILE_ANY_ACCESS 0
#define IOCTL_SERIAL_LSRMST_INSERT CTL_CODE(FILE_DEVICE_SERIAL_PORT, 31, METHOD_BUFFERED, FILE_ANY_ACCESS)
P.S. Ваш вопрос больше подходит для Круглого стола, чем для обсуждения статьи. ИМХО. |
|
24-06-2009 12:59сообщение от автора материала Видите ли, Олег. Нормальный аппаратный СОМ-порт 16550 или аналог ничего подобного не знает и не умеет. Чего там малыш Билли написал в MSDN-е...его право... Может конечно чего-то малыш Билл научился Виндами добавлять в мозги микросхеме порта...аж самому свой бред понравился. Короче, со времен еще АТ никто не менял и менять не будет аппаратную начинку микросхем(ы) СОМ-порта. А то, что чужая программа какой-то там девайсиоконтрол вызывает - ее проблема. |
|
24-06-2009 10:31Василий! Спасибо за статью, очень полезный материал!
Если Вы еще отслеживаете обсуждение Вашей статьи, то позволю себе задать вопрос.
Как Вы правильно заметили, достаточно часто прилагаемая к COM-устройству программа не удовлетворяет на все 100 и приходится писать свою, опираясь на перехваченный лог обмена.
В моем случае лог обмена содержит команду, предписывающую СОМ-порту включать или нет помещение данных о состоянии линии и модема в поток данных:
31 IRP_MJ_DEVICE_CONTROL - Request operates a serial port
STATUS_SUCCESS
IOCTL_SERIAL_LSRMST_INSERT - Request enables or disables the insertion of information about line status and modem status in the receive data stream
LSRMST_INSERT - 0xea0188
В MSDN прочел, что этот режим задается с помощью DeviceIoControl:
BOOL DeviceIoControl(
(HANDLE) hDevice,
IOCTL_SERIAL_LSRMST_INSERT,
(LPVOID) lpInBuffer,
(DWORD) nInBufferSize,
NULL,
0,
(LPDWORD) lpBytesReturned,
(LPOVERLAPPED) lpOverlapped
);
Но, к сожалению, команда IOCTL_SERIAL_LSRMST_INSERT в Delphi 6 не определена (или я не нашел соответствующий Unit). Во всяком случае, встроенный в Delphi Help в описании DeviceIoControl приводит перечень определенных команд, среди которых IOCTL_SERIAL_LSRMST_INSERT отсутствует.
Вот, собственно, и вопрос: как в своей программе воспроизвести выше приведенную команду из лога? |
|
24-12-2008 22:08сообщение от автора материала Мир не стоит на месте. Сейчас у меня базовый юнит (ОТНЮДЬ НЕ КОМПОНЕНТ!!!) для работы с портом позволяет стартовать два потока. Второй - обслуживает порт, а первый реализует алгоритм работы с железякой (фактически только пишет в порт) и проверяет ТОЛЬКО "наличие ответа" через Wait эвента, который выставляет второй поток, если получил ответ. Т.е. в OpenCom открывается, настраивается порт и стартуется первый поток. Он (поток) завершается, если железяка перестала отвечать или основной поток вытавил ему Terminate-флаг. Ясное дело, что в самом своем начале первый стартует второй, а в конце самостоятельно выставляет Terminate-флаг второму - короче делает все, чтобы он завершился. Причем, коль скоро во втором есть обработка асинхрона - используется не только SetCommMask, но и CancelIO. Это я сделал потому, что в большинстве случаев имеет место диалоговый режим работы с железякой. Соответственно, в новом проекте переписывается алгоритм посылки команд (первый поток) и алгоритм синхронизации и "выкусывания" приходящей инфы (второй поток). Теперь у основного потока "руки развязаны". Он только вызывает OpenCom (с проверкой есс-но), а дальше только ловит сообщения "принято то-то" от порта (второй поток) и "потеряна связь" (первый поток). |
|
24-12-2008 21:51сообщение от автора материала Я уже не помню...есть ли в примерах к статье настройка DCB.Flags...Наверняка должна быть, а там выставлены биты так, что ни о каких хэндсоверфловах и речи быть не может. Да и на фига? Это управление по CTS и DSR осталось с тех времен, когда скорость порта была сравнима со скоростью работы внешних устройств. А сейчас даже несчастный PIC способен "глотать" на 115200. То что это осталось - очень хорошо, наличие такой возможности управления дает возможность программисту железяки (а ему намного тяжелее приходится, он ограничен в ресурсах) чуть проще продумывать алгоритм работы своего устройства. "Чтоб его не .... извне".
Что касается Read(Write)File, я практически всегда работаю в асинхроне (в этом случае WriteFile НИКОГДА не вернет TRUE) с таймаутами на немедленный выход из этих функций (об этом в статье написано). Если мне нужно дождаться окончания - я вставляю код обработки асинхронной операции ввода/вывода. Он для всех случаев жизни одинаков. Еще раз предупреждаю, что окончание WriteFile хоть в синхроне, хоть в асинхроне означает лишь, что байты ПРИНЯЛ ДРАЙВЕР, а событие EV_TXEMPTY означает лишь то, что МОЖНО ЕЩЕ ЗАПИСАТЬ , т.е. реально в этот момент в микросхеме м.б. еще до 14 байт на передачу (буфер FIFO). |
|
24-12-2008 08:54Василий, большое спасибо за Вашу статью! Очень понятно всё изложено.
...но есть вопросы :)
с оформление приёма всё Вы расписали, а вот несколько слов бы ещё про передачу. Например есть вопросы по исполнению WriteComm в Вашем примере (файл ComUnit.pas).
уж очень смело Вы там с WriteFile обходитесь - и не проверяете всё ли передалось, и не чуть Вы не боитесь "зависнуть" на передаче,
При этом один из потоков у Вас читает COM1, при этом писать собирается в COM2, про состояние которого ничего не знает (а если там, например, через CTS заблокирована передача?) Второй поток хотя читает из COM2 и передаёт в основной поток сообщения о изменении модемного статуса, но при этом пытается писать также "не задумываясь".
Василий, поправьте меня - надеюсь я просто чего-то не понял.
Ещё раз большой спасибо за Вашу статью - она имеет "вечную ценность"! :) |
|
10-06-2008 07:21У функции ReadsComm должен быть один 4-х байтный параметр, т.к. она передаётся в CreateThread.
Впрочем, это не мешает выполняться коду, т.к. сразу после выхода из CreateThread системой вызывается ExitThread. |
|
20-05-2008 21:41сообщение от автора материала Возня с поинтерами в данном случае слишком громоздка
Возможно, но это 100% гарантия непотери данных. Лучше просот передавать массив байтов который ты прочитал из ком порта, и потом в основном потоке его разбирать по нужным тебе элементам - чтоб не задерживать вторичный поток по чтению. Эта "возня" как раз и обеспечивает максимально эффективно использовать поток. Как Вы думаете, сколько операций можно сделать в интервале между приходящими байтами??? Более того, где Вы видите задержку по чтению? Чтение, как впрочем и все остальные события обрабатываются по ЭВЕНТАМ (то бишь следствиям прерываний). Вся эта "возня" идет после приема байта, следующий байт прийдет ЧЕРЕЗ 1 МСЕК (для 9600)! До китайской границы можно успеть добежать!
Я вижу у Вас типичную ошибку в представлениях о принципах работы многопоточных приложений. Эта типичная ошибка - накопление в глобальную переменную/массив/строку, а потом удивление - куда деваются данные. Я раньше определял двумерный массив MxN, первый байт "строк" массива [M,0] использовал в качестве флага (СntByte). Накапливал по-строчно и основному потоку передавал индекс и количество. Разбор байт - в основном потоке. Но это не удобно, удобнее когда основной поток оперирует непосредственно данными, потому что очень легко сделать эмитатор приема, перейти на USB/LPT/LAN канал связи для устройства (оно же выполняет строго определенные функции, значит и данные передает одни и те же, изменяем канал связи - переписываем только приемник. |
|
20-05-2008 09:55Прежде всего хочу сказать ОГРОМНОЕ спасибо Василию Пивко за столь полную и подробную статью по программированию, отдельное спасибо за пример программы.
В ходе написания собственной программы с использованием ваших примеров заметил возможную ошибку - в статье в одном из примеров указан код
Move(Buff[0],RecivBuff[CntByte],Kols);
Inc(CntByte,Kols);
здесь по моему надо в другом порядке
Inc(CntByte,Kols);
Move(Buff[0],RecivBuff[CntByte],Kols);
Так же - при компиляции в Дельфи 7, строчка inc(Buff); вызывает ошибку - Функция inc не перегружена для типа Byte.
И общее мое мнение - возня с поинтерами в данном случае слишком громоздка, лучше просот передавать массив байтов который ты прочитал из ком порта, и потом в основном потоке его разбирать по нужным тебе элементам - чтоб не задерживать вторичный поток по чтению. Если в этом моменте я не прав - поясните пожалуйста.
|
|
04-03-2008 13:58сообщение от автора материала Да любой переходник подойдет.
Второй вопрос не понял...Какое МЕМО...С подобными вопросами пишите лучше в личку. |
|
04-03-2008 11:45Ещё вопрос, если не возражаете. Возможно ли на форме установить компоненты , в которых можно менять чётность и стоп. биты? В какой процедуре их лучше описать? Это ведь битовые данные? Значит, для PARITY в поле МЕМО надо выставлять - 2,3,0,1 (для EVENTPARITY, MARKPARITY,NOPARITY,ODDPARITY соответственно) ?
И описать так : Com1Var. DCB.Parity := StrToInt(edParity.Text); ? Или я не прав?
|
|
04-03-2008 07:12Спасибо!!Если по честному, то я и не знал, что существуют такие переходники (USB-->RS232). Сейчас порою в инете. А Вы бы какой рекомендовали (чтобы не очень дорого)? |
|
04-03-2008 00:31сообщение от автора материала Было бы практичней в качестве второго порта использовать USB. Так вот, не могли бы Вы - Василий посоветовать , возможна ли такая схема? Не совсем понял...Если Вы используете переходник USB/RS232 - то об этом можете не беспокоиться, поскольку у Вас просто добавится еще один СОМ-порт при подключении этого переходника. Если же Вы хотите использовать использовать связку USB - COM, то во-первых без СВОЕГО драйвера для ВАШЕГО USB-устройства Вам не обойтись, во-вторых какой смысл в этой связке? Зачем 2-х мегабитный USB связывать со 115 килобитным СОМ-ом. Проще использовать переходник USB/RS232. |
|
03-03-2008 12:09Статься несомненно превосходная! Эта схема была опробована для тестирования низкоскоростных каналов (менее 9600 бод). Одно неудобство. Дело в том, что как правило, ноутбуки имеют один СОМ-порт и несколько USB-портов. Было бы практичней в качестве второго порта использовать USB. Так вот, не могли бы Вы - Василий посоветовать , возможна ли такая схема? Может Вы порекомендуете схемное решения для этого? |
|
25-01-2008 03:27Тайм-ауты были выставлены как в статье. Весь код был написан в первом моем посте.
В общем я разобрался наконец :). Все дело было в слишком большом параметре nNumberOfBytesToRead процедуры ReadFile при nNumberOfBytesToRead = Stat.cbInQue все работает как надо!.
Спасибо Василию очень хорошая статья! |
|
23-01-2008 03:46сообщение от автора материала Честно сказать, никогда не проверяю на false ReadFile. По-уму, если Вам именно сейчас нужно дождаться этих байт - Вы должны после опять поставить похожий код обработки асинхронного чтения. В принципе, это будет именно ПО-УМУ, но... Дело в том, что пока не будет УДАЧНОГО чтения из порта эвент EV_RXCHAR НЕ СБРОСИТСЯ - и это ПРАВИЛЬНО (на сокетах - тоже самое, пока Вы не дочитаете до конца - Ваш обработчик чтения будет вызываться до бесконечности). Соответственно, при false Вы вернетесь в WaitCommEvent и опять в маске получите EV_RXCHAR. Почему, может быть, я не заморачивался этим - я всегда настраиваю тайм-ауты порта так (как написано в статье), чтобы ReadFile возвращал немедленно все, что есть в буфере порта. Может я и не прав, но не было у меня ГИМОРА. А самый главный принцип программирования - не трогай рабочий код, пока он РАБОТАЕТ УДОВЛЕТВОРИТЕЛЬНО В КРУГЛОСУТОЧНОМ РЕЖИМЕ (необходимость перезапуска программы один раз в неделю считаю нормальным, ПО для атомных станций не писал, доводилось писать комплексы для спецслужб с круглосуточной работой), или пока у тебя не появилась дополнительная инфа о том, как на самом деле (по малышу Билли) он должен выглядеть. |
|
22-01-2008 02:17 Прошу прощение за то что я тут задал вопрос но мне очень хотелось чтобы именно вы Василий ответили на этот вопрос.
Все вы верно подметили, что это не ошибка! Но это место как раз у меня правильно выполняется, а вот оператор ReadFile(hf,MyBuff,SizeOf(MyBuff),ByteReaded, @ov)
возвращает false, а GetLastError после этого оператора равен Error_IO_pending.
По поводу того, что кто-нибудь не даст открыть порт согласен. Но это и не рабочая программа а всего лишь проба работы с COM портом. |
|
22-01-2008 00:34сообщение от автора материала Добавлю... При Вашем подходе (открыл порт, записал, прочитал) сто пудов нарветесь на ситуацию, что кто-то не даст открыть порт в какой-то момент. |
|
22-01-2008 00:32сообщение от автора материала Не нарушайте правила! Вопросы нужно задавать на КС. По сути...
Error_IO_pending - это НЕ ОШИБКА ЧТЕНИЯ МАСКИ ПРЕРЫВАНИЙ!!! Это лишь ситуация, когда именно СЕЙЧАС невозможно прочитать маску. Поэтому дальше и стоят строки
if WaitForSingleObject(ov.hEvent,INFINITE ) = WAIT_OBJECT_0 then
if GetOverlappedResult(hf,ov,ByteReaded,false) then Memo1.Lines.Add('GetOverlappedResult: successfully');;
дабы корректно обработать АСИНХРОННУЮ операцию чтения этой маски!
После этого кода в Вашей ret и будет сидеть маска возникшего прерывания из числа Вами же разрешенных. |
|
22-01-2008 00:14Статья очень понравилась, то что надо!
Но почему то не получается у меня прочитать данные с моего устройства по этому способу.
Делаю следующие:
procedure TForm1.Button1Click(Sender: TObject);
var
hf: Thandle;
ov: Overlapped;
ret: DWORD;
ParInfo: byte;
MyBuff:array [1..2048] of char;
lp:TDCB;
ByteReaded:DWORD;
ct: COMMTIMEOUTS;
begin
hf := CreateFile(
'COM3',
GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, 0);
if hf <> INVALID_HANDLE_VALUE then
Memo1.Lines.Add('CreateFile: successfully');
if GetCommState(hf,lp) then Memo1.Lines.Add('GetCommState: successfully');
Lp.DCBlength:= sizeof(TDCB);
LP.BaudRate :=CBR_9600;
lp.ByteSize:=7;
lp.Parity:= SPACEPARITY;
lp.StopBits:=TWOSTOPBITS;
lp.Flags:=1;
if SetCommState(hf,lp) then Memo1.Lines.Add('SetCommState: successfully');
if SetupComm(hf, SizeOf(MyBuff), SizeOf(MyBuff)) then
Memo1.Lines.Add('SetupComm: successfully');
CT.ReadTotalTimeoutConstant := 0;
CT.ReadIntervalTimeout := MAXDWORD;
CT.ReadTotalTimeoutMultiplier := 0;
CT.WriteTotalTimeoutMultiplier := 0;
CT.WriteTotalTimeoutConstant := 0;
If SetCommTimeouts(hf, CT) Then Memo1.Lines.Add('SetCommTimeouts: successfully');
if SetCommMask(hf, EV_RXCHAR) then Memo1.Lines.Add('SetCommMask: successfully');
ov.hEvent:= CreateEvent(nil, true, FALSE, nil);
if GetLastError =0 then Memo1.Lines.Add('CreateEvent: successfully');
if not WaitCommEvent( hf, ret, @ov) then begin
if GetLastError = Error_IO_pending then
if WaitForSingleObject(ov.hEvent,INFINITE ) = WAIT_OBJECT_0 then
if GetOverlappedResult(hf,ov,ByteReaded,false) then Memo1.Lines.Add('GetOverlappedResult: successfully');;
end;
if (ret and EV_RXCHAR) = EV_RXCHAR then begin
if ReadFile(hf,MyBuff,SizeOf(MyBuff),ByteReaded, @ov) then begin
if ByteReaded>0 then Memo1.Lines.Add('Read: '+MyBuff);
end else
SysErrorMessage(Handle);
end;
CloseHandle(ov.hEvent);
CloseHandle(hf);
Memo1.Lines.Add('CloseHandle');
end;
Программа ожидает событие а когда пытается читать с устройства данные выдается ошибка: Error_IO_pending. Подскажите кто-нибудь как с этим справится? |
|
29-10-2007 01:00сообщение от автора материала Т.е. один и тот же код работает на одной машине, а на двух отказывается? Такого быть не может. Если мне не изменяет память , то сообщение, которое Вы указываете выдается после попытки (при криэйте формы) открыть последовательно СОМ1 и СОМ2. Посмотрел еще раз пример. Есть там маленькая некорректность. Для настройки порта, нужно вначале GetCommState - получить DCB, настроить что нужно, а потом - SetCommState. Но это не может никак препятствовать открытию порта. Пришлите свой код (куски с открытием порта, закрытием порта, обработкой сообщений и функцией потока порта) - что-нибудь подскажу. vpivo_собака_yandex.ru. |
|
26-10-2007 03:53В том то все и дело, что если использовать готовый компонент для работы с портом(какой сейчас и используется в проекте для получения значения веса с электронных весов), то все нормально, а вот при использовании примера из статьи получается такая картина. Пробовал на трех машинах: одни а текущий момент работает и принимает данные с весов, на второй и третьей СОМ порт имеется в наличии и работает нормально(порт), принтеров и модемов к какой из машин не подключалось. |
|
26-10-2007 00:00сообщение от автора материала Да нет конечно... Если CreateFile не открывает порт - значит он занят кем-то... Такое бывает, когда реально его нет, или есть модемы, или когда ранее был установлен драйвер СОМ-портового принтера и не был "снесен" корректно. Тут навряд ли ошибка в коде - скорее надо разбираться с конфигурацией устройств. |
|
25-10-2007 06:59Я использовал модуль для работы с портами из тестового примера, причем поубирал все, что связано с СОМ2. При вызопе фукции CreateFile, с параметром хоть первого, хоть второго порта всегда один и тот же ответ. В качестве имени файла использоал как '\\.\COM1' так и 'COM1'. Открывал в режиме чтения, так как запись не нужна, хотя с полным доступом все-рвно тот же результат. Может ли влиять отсутствие защищенных секций, как в готовом компоненте, на работу? |
|
25-10-2007 00:16сообщение от автора материала Тестовый пример рассчитан на наличие ДВУХ свободных СОМ-портов. И пример показывает как управлять внешним устройством в асинхроне. СОМ2 как раз и эмулирует наличие этого устройства. |
|
24-10-2007 06:04Может вопрос и глуповат, но при коспиляции и запуске тестового примера выдается сообщение что нет доступных СОМ портов, хотя такие в реальности существуют и работают. При работе приложения через отдельный компонент, все нормальнофункционирует. А при попытке получения дступа к порту СОМ1 даже на чтение выдается сообщение об ошибке. В чем может быть подвох? |
|
03-08-2007 03:17Понятно.
Извините, что вмешиваюсь, но на вопрос, заданный Юзер'ом
"как на счёт виртуальных СОМ портов!? Скорость передоваемой, по ним информации (моего апарата) достигает 921600."
могу ответить, что все работает нормально. У меня в последней программе идет обмен с устройством фирмы FTDI http://www.ftdichip.com/, которое подключается через USB, а драйверы поддерживают API двух видов - 1)специфическое, но очень похожее на COM-порт и 2)через виртуальный COM-порт. Я сначала сделал по второму варианту - через виртуальный COM-порт (потом переписали под второй). На скорости 923076 (3МГц/3.25) работает вполне стабильно. Правда я использую синхронный (не overlapped) режим с чтением в отдельном потоке. За секунду обмениваемся ~20 посылками, где от устройства приходят пакеты размером 2048 байт блок данных плюс чуть больше десятка байт служебной информации, заголовков и т.п.
|
|
02-08-2007 10:22сообщение от автора материала 2 Сергей Осколков...
функция ReadFile возвратит результат или когда прочитает указанное в ней (в параметре nNumberOfBytesToRead) число байт, или когда истечет время, определяемое таймаутами - а как можно это опровергнуть? Если в хэлпе достаточно однозначно прописаны условия возврата из ReadFile именно так. Давно не читал свою статью, возможно, я не заострял на это внимание. |
|
02-08-2007 09:32Василий, спасибо за статью, я из нее почерпнул идею о динамическом выделении буферов для чтения (раньше с таким не приходилось сталкиваться).
У вас в разделе про таймауты не сказана явно (если не пропустил) одна вещь. Если используются таймауты в синхронном (т.е. не overlapped) режиме, то функция ReadFile возвратит результат или когда прочитает указанное в ней (в параметре nNumberOfBytesToRead) число байт, или когда истечет время, определяемое таймаутами. Т.е. как только нужное количество байт прочитано, функция возвратится сразу же, не дожидаясь таймаута. Возможно, это всем и так очевидно, но мне почему-то по обсуждениям на КС показалось, что вы считаете, что функция будет ждать конца таймаута в любом случае. |
|
18-07-2007 05:58Статья хорошая, это бесспорно! Однако расчитана на знающего пользователя. Хотелось бы чтоб автор написал ту же статью подробнее для начинающих пользователей. Готов помочь автору все чем смогу, если он возьмется за это. С уважением. Хочу еще раз сказать, что статья лучшая из всего, что мне приходилось читать на данную тему. |
|
25-04-2007 23:20сообщение от автора материала Никогда не работал с виртуальными СОМ-портами, но подозреваю, что в этом случае стандартный драйвер порта работает с адресами, которые не являются адресами физических устройств. Информацию же в эти адреса поставляет другой драйвер-прослойка Блютузная. Так что никаких изменений быть не должно. Что касается скорости, то если Ваш комп справляется с мегабитом по вводу/выводу - тогда какие проблемы? |
|
25-04-2007 17:35Добрый День, очень бы хотелось задать один глуповатый вопрос.. Я в етом деле новичёк, и с АРИ ещё не приходилось работать, так что извеняйте;) как на счёт виртуальных СОМ портов!? Скорость передоваемой, по ним информации (моего апарата) достигает 921600. Если говорить о Bluetooth вертуальном порте, то на скока я знаю инфо по нему передаёца пучтами("плюнул" и затих), таким образом происходит экаономия энегии.. и не загнёца ли ваша система работая с Bluetooth'ным аппаратом? |
|
16-03-2007 04:24сообщение от автора материала Изменения незначительные...Просто исправлена часть кода обработки асинхронных операций. Там была ошибка из-за которой при определенных условиях поток "отъедал" весь тик ресурсов, что не есть хорошо, но не смертельно. Дополнительно исправлены или дополнены некоторые толкования параметров,событий и функций. Что касается синхронного режима, то я не сторонник вообще его использовать хотя бы потому, что "тормозить" программу до скоростей 9600 по меньшей мере не логично. В синхроне работал только один раз за 10 лет и то от безысходности (сейчас поставил I7520 - и опять работаю в асинхроне).Что касается раритетов, то пока на длинных линиях и во взрывобезопасном исполнении мало кто отходит от 422/485 интерфейса - дешево и надежно. |
|
16-03-2007 03:00А что изменилось то? Синхронный режим так и непаявился, мне былобы интересно почитать о смеси синхроного и асинхроного режимов, выполняющимя в отдельном потоке. Правда уже 3 года с ком портом несталкивался и кажется уже пора завязывать с раритетными устройствами, везде Ethernet порты. |
|
01-09-2006 07:50Господа, избавьте автора от вопросов, не имеющих отношения к его статье. Здесь ведется обсуждение статьи, и только! Вопросы по вашим проблемам задавайте в положенном месте — на Круглом столе.
P.S. Г-н Бурдин, информация по вашему вопросу есть на КС. Сделайте поиск по слову GetCommModemStatus |
|
01-09-2006 07:14Вопрос - как легче всего присобачить к ком-порту контактный датчик и считать колво срабатываний оного?
Просьба подсказать... |
|
14-07-2006 05:38Вы не подскажите как лучше организовать ловушки на ответы модема?
'OK';
'ERROR';
'BUSY';
'CONNECT';
'NO CARRIER';
'NO DIALTONE';
'NO ANSWER';
Стоит ли выводить обработку команд модема в отдельный поток или лучше это реализовать в одном потоке (работа с модемом и обмен данными после установки связи с удаленным устройством)? |
|
13-07-2006 15:26сообщение от автора материала На самом деле EV_RLSD - это эвент о появлении/пропадании несущей модемной передачи. Действительно - 1 пин DB9. Поскольку модемами никогда не занимался, то и на момент написания статьи об этом не знал. |
|
13-07-2006 09:31Уважаемый!
В главе
1.2.4. GetCommMask и SetCommMask.
EV_RLSD - честно говоря так и не понял - похоже при изменении состояния Rx;
EV_RLSD - выводит состояние 1 контакта DB9 - DCD,
т.е. когда модем соединился с удаленным или разъединился.
Выяснилось при написании программы дозвона по модеду и опредения его соединения.
|
|
06-07-2006 02:18сообщение от автора материала Цикл while CntByte >= sizeof(TPacketHeader) do begin
работает до тех пор пока в приемном буфере (RecivBuff) количество байт (CntByte) больше либо равен длине заголовка пакета (TPacketHeader). Если меньше - нет смысла в дальнейшей синхронизации.
Cardinal((@RecivBuff[0])^) - возвращает первые четыре байта в приемном буфере, начиная с 0-ого в виде cardinal-а. Этот cardinal сравнивается с неким синхроначалом, размерностью cardinal (Sync). Т.е. это условие выполнено в случае, если первые четыре байта в приемном буфере равны синхроначалу.
А дальше (5-ым байтом по протоколу) идет байт с длиной посылки. Собственно, все комментарии там имеются.
Если принятый буфер не начинается с синхроначалом, то кол-во принятых байт декрементируется и идет "сдвижка" байт в приемном буфере.
Ваш "интересный эффект" можно объяснить только при рассмотрении кода, а так можно лишь предполагать. Если байты именно "вклиниваются" (а не заменяются) - значит у Вас ошибка при работе с индексом очередного принятого байта при записи из вновь прочитанного буфера в накапливаемый буфер (у меня это CntByte). Больше вариантов нет, кроме одного - Вы сами так посылаете байты...:-) |
|
05-07-2006 19:29Доброго времени. Есть много вопросов, прошу совета.
Возникла острая необходимость написать драйвер управления контроллерами щита, находящимся на одной шине (RS-485) и работающими по протоколу FT1.2 (старт-стопная асинхронная передача, ГОСТ МЭК-870-5-2-95). Кадры бывают различной длины, длина указана в заголовке кадра. Сам профилируюсь по СУБД и взялся впервые за СОМ-порт. Не могу понять принцип синхронизации, что происходит при сравнении:
--------
while CntByte >= sizeof(TPacketHeader) do begin
//пока количество принятых байт больше длины синхрочасти преамбулы
//пытаемся засинхронизоваться
-----> if Cardinal((@RecivBuff[0])^) = Sync
then begin
if CntByte >= (sizeof(TPacketHeader)+Byte(RecivBuff[4]))
then begin
//если синхропосылка стоит вначале накопленного буфера
//и общая длина принятых байт больше или равна длине
//преамбулы плюс количество байт, указанное в преамбуле
LenPacket := sizeof(TPacketHeader)+Byte(RecivBuff[4]);
--------
Также интересный эффект наблюдаю - модифицировал код так, что посылки делаю по нажатию, отправляется содержимое Memo2 в порт. Первая посылка всегда отлично читается, вторая или третья приходит с вклинившимися неизвестными байтами, потом опять нормально - ситуация нестабильна. В чем может быть проблема? |
|
14-02-2006 08:16сообщение от автора материала Если в потоке обрабатывающем эвенты порта заниматься "рисованием" и т.п. возникнет гимор с синхронизацией (лично убедился). Поэтому я всегда работаю через PostMessage. Про свой опыт синхронного режима я уже писал. Добавлю, что был вынужден к нему прибегнуть из-за "тупого" прибора. Вернее даже не так. С дури купил преобразователь 232/485 той же фирмы, а вот он (преобразователь) - тупее некуда - невовремя выставил RTS после посылки - привет ответу. Если дадут бабки, куплю другой преобразователь с автоматическим определением направления передачи, и перепишу программу. Дело в том, что этот режим "конфликтует" с драйвером для читалки карт КИБИ001 (подключен к LPT и тоже нестандартный, чисто сделан под фирму Ангстрем), у которого уже никак нельзя отказаться от временных задержек, аппаратный протокол обмена таков. |
|
14-02-2006 04:33>>>За 100 мсек можно исполнить запрос из БД, нарисовать результат и при этом принять ответ от порта.
Так если чтение происходит в своем отдельном потоке, то что мешает исполнять запрос и рисовать результат? У меня, правда, действительно в ряде программ все в одном потоке - несколько раз думал переписать с дополнительными, но, взвешивая плюсы и минусы (в своем конкретном случае), так пока и оставил (в детали не вдаюсь, но аргументы к этому у меня есть). Но и при чтении в своем потоке установка таймаута чтения упрощает логику (в моем случае) - прочитал пакет за один раз и все.
Есть другой вопрос - для обсуждения, м.б. у кого-то есть соображения. Асинхронный режим нужен для того, чтобы избежать зависания процесса. Ту же задачу можно решать и при синхронном чтении, если оно - в отдельном потоке. Аналогичная проблема есть в сокетах - такие же долгие операции чтения. Там тоже есть асинхронные сокеты, но можно и использовать синхронные - в потоках. Библиотека Indy построена именно так.
Вы, Василий, пишете "Работа с портом только в асинхронном режиме, дабы не "подвисать" из-за особенностей конкретного устройства "висящего" на СОМ-порту;"
А кто-нибудь пользуется синхронным режимом? |
|
13-02-2006 21:41сообщение от автора материала Один единственный раз в жизни работал с портом по принципу "записал,прочитал", соответственно с настройкой таймаутов. Это случилось с "наитупейшим" прибором Сигнал-20 SMD, правда таймауты там были бесполезны, работа велась исключительно по задержкам через такты процессора - иначе никак. И никогда не пользовался никакими компонентами. СОМ-порт прост, как все гениальное. Поэтому, есть юнит с интерфейсными OpenComm, CloseComm, WriteComm и "внутренней" функцией потока. Все. Ничего больше не нужно. Более того, каждое устройство индивидуально, и под каждое "переписывается" OpenComm и функция потока, дабы обеспечить минимальные затраты ресурсов. За 100 мсек можно исполнить запрос из БД, нарисовать результат и при этом принять ответ от порта. |
|
12-02-2006 19:01Всем спасибо за оперативный ответ!
Работаю через компонент Tcomm32.
Может есть нюансы.
|
|
11-02-2006 09:13Кстати говоря, выставлять таймауты именно так не всегда удобно. У меня, например типичная ситуация управления устройством: программа посылает запрос или команду (5-10-20 байт) и получает ответ, в разных устройствах тоже от 5 до ~130 байт или порядка этого. Обмен на скорости 9600. Мне удобно послать команду и тут же читать из порта. Определенная задержка есть, но если ставить общий таймаут чтения ~100 мс, то пакеты от устройства читаются. Я ставлю с запасом 200, фактически, естественно, если чтение действительно происходит, то функция возвращается скорее. Получается
ReadIntervalTimeout:=0;
ReadTotalTimeoutMultiplier:=0;
ReadTotalTimeoutConstant:=200;
Не нужно читать несколько раз, достаточно одного вызова ReadFile. |
|
11-02-2006 00:31сообщение от автора материала Собственно, я это не выдумал, а прочитал из хэлпа. Усомнится в правильности не было прецедентов. При непрерывной передаче на разных скоростях получал пропорциональные блоки байт (типа - 4800 - 7-8 байт, 9600 - 3-4 байта). Есстественно были и другого размера. На скоростях выше 2400 стабильно появлялись блоки с нулевым количеством байт. Это совершенно понятно. По приходу первого байта возник эвент, поток получил ресурс, вывалился из Wait. На этом промежутке пришли еще байты, но они для эвента "по барабану" - он и так взведен. "Опущен он будет выходом из Wait. Если хоть один байт пришел между Wait и ReadFile, драйвер работает по аппаратным прерываниям - соответственно сгенерился новый эвент. ReadFile прочитал и этот байт, но эвент то остался "торчать", и на следующем цикле Wait сразу "вывалился", хотя в буфере драйвера ничего нет, и ReadFile вернул 0. |
|
10-02-2006 00:50Всем добрый день!
выдержка из статьи:
Комбинация:
* ReadIntervalTimeout := MAXDWORD;
* ReadTotalTimeoutMultiplier := 0;
* ReadTotalTimeoutConstant := 0;
приводит к тому, что функция ReadFile возвращает немедленно все имеющиеся байты в приемном буфере, даже если их там нет, или идет непрерывная передача на входе порта. Я всегда пользуюсь такими настройками в своем подходе.
Этот момент почему-то не работает,т.к. написано.
если задать 250 вместо МaxDWord то вроде работает
можно сказать об этом поподробнее.
Валерий. |
|
02-12-2005 08:30Помогла разобраться с ошибками неверного возврата числа записанных байт и результата операции WriteFile. Сообщение не подписано |
|
11-11-2005 08:59Хорошая статья, разобрался-таки с СОМ портами, спасибо!
if not WaitCommEvent(cId,Mask,@Ovr) then
begin
if GetLastError() = ERROR_IO_PENDING
then
GetOverlappedResult(cId,Ovr,Trans,False);
end;
Но, почему-то с таким ожиданием грузит процессор, а вот так (как в статье, а не в прилагаемом проекте) работает:
if not WaitCommEvent(CommHandle,Mask,@Ovr) then
begin
if GetLastError() = ERROR_IO_PENDING then
begin
Signal := WaitForMultipleObjects(1,@Events,False,INFINITE);
if (Signal = WAIT_OBJECT_0 + 1) then
GetOverlappedResult(CommHandle,Ovr,Trans,False);
end;
end;
Может, я в настройках что-то упустил? |
|
11-04-2005 02:47
01-04-2005 03:01сообщение от автора материала Для Skiv.
Ну не люблю я "набрасывать" кучу однотипных объектов, каждый под свою задачу. Когда-то, может даже в Дельфи 1.0 был глюк - в дизайнтайме поменяешь период, запускаешь - запускается без компиляции со старым периодом. И изменение Enable работало некорректно. В 5-том и старше - все нормально, но "осадок остался". |
|
31-03-2005 23:39Спасибо большое, я действительно нашел все интересующие меня ответы на мой вопрос http://www.delphikingdom.com/asp/answer.asp?IDAnswer=30278.
Использование дополнительного таймера для определения "живости" устройства - эта та идея , которая и была мне нужна. Еще раз большое спасибо.
Тока немного непонятно зачем было так заморачиваться с таймером таймаута через tag, когда можно про взводить перед посылкой и выключать после окончательного приема пакета.
|
|
31-03-2005 08:49сообщение от автора материала Что значит синхронизация? Виндоус не RealTimeSystem. Почти цитата из статьи - "отправил, взвел таймер, забыл"..."принял, опустил таймер, проанализировал данные". Если не принял - таймер сам "прокукарекает". |
|
31-03-2005 05:59А какже тогда осуществляется синхронизация с записью в порт? Многие устройства на каждый полученый пакет, отвечают своим пакетом данных. |
|
31-03-2005 05:10сообщение от автора материала Erik-у.
Попробуйте откомпилить и запустить прилагаемый пример без соединения СОМ1 с СОМ2 нульмодемным кабелем или отсоедините кабель в процессе обмена. Вы все поймете. |
|
31-03-2005 05:03сообщение от автора материала Erik-у.
Если внимательно почитать статью, то такого вопроса не будет. ReadsComm - это не вызываемая процедура, а функция дочернего потока, стартующего по открытию порта и заканчивающегося при его закрытии. Идея дочернего потока в том, чтобы не "втупую" пытаться читать из порта или еще что то делать, а читать, запрашивать состояние модемных линий и т.п. - только если есть соответствующий эвент. Конкретно по чтению. Таймауты настроены на немедленный возврат из ReadFile со всеми имеющимися N байтами в буфере драйвера порта (в том числе и N=0). Смысл этой настройки в статье подробно рассмотрена. Статья описывает подход к работе с портом, который (это проверено на нескольких комплексах) делает ПО независимым от работы обслуживаемого устройства. Чтобы с устройством (кабелем) не случилось - программа "прокукарекает" об ошибке обмена, выполнит все необходимые в конкретном случае действия, но не зависнет. |
|
31-03-2005 02:45В процедуре ReadsComm происходит страное чтение, почему непопытатся проситать все через GetOverlappedResult сначала выдав ReadFile? Мне кажется в этом коде будут большие проблемы при никуда невключеном com порте. Помоему вот так более правильно:
SetLength(Buf, ReadBufSize);
if not ReadFile(FHandle, PChar(Buf)^, ReadBufSize, DWord(Result), @Overlapped)
and (GetLastError <> ERROR_IO_PENDING) then
begin
ErrorCode := GetLastError;
RaiseCommError(sReadError, ErrorCode);
end;
if FEvent.WaitFor(FReadTimeout) <> wrSignaled then
Result := -1
else
begin
GetOverlappedResult(Handle, Overlapped, DWord(Result), True);
SetLength(Buf, Result);
FEvent.ResetEvent;
end;
Разумеется GetOverlappedResult можно задавать и без ожидания. |
|
30-03-2005 04:16На мой взгляд здесь собрано наболее подробное изложние материала на эту тему.
|
|
|
|