Занимательная микроэлектроника — страница 99 из 117

procedure TForm1.ButtonlClick(Sender: TObject);

begin {запрос}

  if (hCQM=0) or (hCOM=INVALID_HANDLE_VALUE) then exit;

              {если порт еще не инициализирован — выход}

PurgeComm(hCOM,PURGE_RXCLEAR); {очищаем буфер}

     xb:=$А2;

     WriteFile(hCOM,xb,1,xn,nil);

     told:=Time;

     if ReadFile(hCOM,ab,6,xn,nil) then {читаем 6 байтов в массив ab}

   begin

         ttime:=Time;

         if SecondsBetween(told,ttime)>0 then

  begin

             Application.MessageBox('Устройство не обнаружено','Error',MB_OK);

             exit;

 end;

   if xn<>6 then

       begin

  Application.MessageBox('Неправильный формат данных', 1 Error',MB_OK);

             exit;

     end;

StaticText1.Caption:=IntToHex(ab[1],2)

         StaticText2.Caption:=IntToHex(ab[2],2)

         StaticText3.Caption:=IntToHex(ab[3],2)

         StaticText4.Caption:=IntToHex(ab[4],2)

         StaticText5.Caption:=IntToHex(ab[5],2)

         StaticText6.Caption:=IntToHex(ab[6],2)

end else {если не сработало}

begin

Application.MessageBox('COM сломался','Error',MB_0K);

    exit;

end;

end;


Результат будет выглядеть примерно так, как показано на рис. 18.7.



Рис. 18.7.Результат приема значений времени из часов-измерителя


Здесь процедура PurgeComm нужна для очистки приемного буфера, на случай, если там случайно задержались какие-то байты (те, кто работал с СОМ-портом в DOS, часто об этом забывают, т. к. там никаких буферов не было). Второй момент, который нужно прокомментировать, связан с возможным отсутствием нужного устройства на втором конце линии (или его неработоспособностью — включить забыли). Мы здесь, как видите, все делаем очень просто: читаем системное время до и после вызова функции ReadFile, и выясняем — если прошло более 1 с (a timeout у нас задан в 2 с), то «устройство не обнаружено». Опыт показывает, что это самый надежный метод. Если же связь каким-то образом прервется посередине посылки, то мы получим меньше байтов, чем заказывали, и программа выдаст сообщение «Неправильный формат данных». Не забудьте, что при работе с функциями времени в Delphi надо добавить ссылку на модуль DateUtils.

По той же схеме можно организовать взаимодействие с измерителем для всех наших команд, кроме команды чтения данных из flash-памяти. С ней сложности возникнут оттого, что мы точно не знаем, сколько данных нам придет, и когда пора заканчивать. В принципе можно обойтись тем же приемом с проверкой времени, т. к. данные поступают сплошным потоком, и когда поток этот прервется, то можно заканчивать. Организация процедуры тогда в некотором роде напоминает использование сторожевого таймера в МК: мы задаем в процедуре чтения максимальное количество принимаемых байтов за один прием (например, 1024 — столько придет примерно за секунду), и пока данные идут, в цикле принимаем их, «скидываем» временно в какой-нибудь массив (потом будем обрабатывать) и обнуляем разницу между told и ttime. Если она не обнулилась вовремя и превысила 1 с — прием окончен.

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

Для этой цели можно организовать прием данных в отдельном потоке. Мы здесь не будем разбирать этот способ, потому что из-за «заумности» соответствующих функций API самое простое, что там приходится делать, — это создавать сам параллельный поток (по сути очень простое действие, которое почему-то вызывает дрожь у новичков, в значительной степени из-за неудобства этого процесса в Delphi). А вот собственно прием данных и их передача в основную программу там получается неоправданно сложной и запутанной процедурой. Потому я не буду на этом останавливаться (интересующихся подробностями отсылаю опять же к книге [11]), а сразу покажу, как можно создать подобную программу на основе дополнительного стороннего компонента, специально созданного для приема через COM-порт. Именно такой способ используется в программе СОМ2000.


Использование драйвера AsyncFree

Компонентов для работы с COM-портом довольно много, есть среди них платные и бесплатные. Мы будем использовать один из самых удачных и профессионально сделанных компонентов для СОМ-порта — свободно распространяемый AsyncFree некоего Петра Вониса (Petr Vones), судя по электронному адресу из Чехии. Компонент доступен бесплатно, с исходными кодами, и скачанный архив включает в себя, в том числе, и файлы dpk, что упрощает процедуру установки до предела — нужно просто щелкнуть мышью на том из dpk-файлов, который соответствует имеющейся у вас версии Delphi, и компонент установится самостоятельно, без утомительных процедур ручной инсталляции. Хотя к самому компоненту приложена ссылка на (отличный, кстати) сайт Delphree Open Source Initiative (http://delphree.clexpert.com), однако на нем я нашел только старую версию AsyncFree под Delphi 5, а скачивать последние версии лучше отсюда: http://sourceforge.net/project/ showfiles.php?group_id=20226.

Принцип работы компонента заключается как раз в создании параллельного потока, в котором байты принимаются по мере их поступления и накапливаются в буфере независимо от деятельности основной программы. Специально следить за приходом данных не требуется, поскольку все делается автоматически, нам остается только отловить принятые данные. Недостаток этого способа в том, что данные принимаются кучей в одной процедуре, и приходится отдельно разбираться, что же мы приняли в данный момент, и где заканчиваются одни данные и начинаются другие. Но при использовании параллельного потока это пришлось бы делать в любом случае, а тут все организовано за вас, «ручками» ничего делать не приходится.

После установки компонент будет находиться в палитре компонентов на вкладке AsyncFree. На самом деле там образуется много компонентов, но нам требуется только самый первый из них под названием AfComPort. Установим его на форму. В перечень переменных добавим:

FlagCOM:boolean=False;

FlagSend:integer=0;

tall:integer;

Переменные told и ttime нам здесь не понадобятся. На форму добавим Label7, в который будем выводить установленный порт и скорость передачи, и Timer. Проверьте, чтобы у таймера интервал составлял 1000 мс (так по умолчанию). Кроме этого, установим компонент comboBox (выпадающий список), у которого в свойстве Items сразу запишем строки для выбора СОМ-порта («СОМ1», «COM2» и т. п.). Аналогичный список можно создать для выбора скорости передачи, но если речь идет о конкретном приборе, то это необязательно (а вот СОМ выбирать, скорее всего, придется).

Начнем с того, что перепишем процедуру IniCoM, что иллюстрирует листинг 18.4 (как и в предыдущем случае, к моменту ее вызова в stcom должна находиться строка с номером порта, например, «СОМ1»).

Листинг 18.4

procedure IniCOM;

var i, err: integer;

begin

    FlagCOM:=False;

    Form1.Label7.Caption:='COM?';

{инициализация COM — номер в строке stcom}

    Form1.AfComPort1.Close; {закрываем старый COM, если был}

val(stcom[length(stcom)],i,err); {извлекаем номер порта}

   if err=0 then Form1.AfComPort1.ComNumber:=i else exit;

                               {здесь требуется число, а не строка}

  Form1.AfComPortl.BaudRate:=br9600; {скорость 9600}

try

         Form1.AfComPortl.Open  ;{пробуем открыть}

except

       if not Form1.AfComPort1.Active then {если не открылся}

begin

           st:=stcom+' does not be present or occupied.';

           Application.MessageBox(Pchar(st),'Error',MB_OK);

           exit {выход из процедуры — неудача}

 end;

   end;

   ab[1]:=ord('A'); {будем посылать инициализацию модема}

  ab[2]:=ord('Т');

   ab[3]:=13;  {CR}

ab[4]:=10;  {LF}

for i:=1 to 4 do Form1.AfComPort1.WriteData(ab[i],1);

   {ответ не сразу:}

   Form1.Timer1.Enabled:=True;

   tall:=0;

   while tall<1 do application.ProcessMessages; {пауза в 1 с}

   Form1.Timer1.Enabled:=False;

   st:=Form1.AfComPort1.ReadString; {ответ модема 10 знаков}

  if pos(1 OK',st)<>0 then {модем}

begin

         st:=stcom+' занят модемом';

         Application.MessageBox(Pchar(st),'Error',MB_OK);

         exit;

   end else {все нормально, COM открыт}

   begin

   Form1.Label7.Caption:=stcom+' 96001;

   FlagCOM:=True;

   end;

end;


Как видим, процедура создания порта много понятнее, чем в случае прямого обращения к API — все через привычную установку свойств компонента. FlagCOM играет у нас роль индикатора, доступен порт или нет. Если он остался при значении False, то процедуру следует повторить с другим значением в строке stcom (каковую мы задаем с помощью ComboBox, см. далее). При определении модема применен хитрый способ задания паузы — вместо обычного оператора