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

RAD, Rapid Application Development). Можно использовать и Visual Basic (в некоторых отношениях это даже более удобно, т. к. в силу происхождения он лучше интегрирован с Windows), однако мой личный опыт общения с этим продуктом отрицательный — значительную часть времени приходится тратить на то, чтобы бороться с внутренними проблемами самого VB. Язык Delphi (Object Pascal) проще и понятнее новичку. Для тех, кто привык к С, можно рекомендовать Borland C++ Builder — это почти то же самое, что и Delphi, даже с общей библиотекой компонентов, только язык другой.

Мы будем ориентироваться на Delphi 7 — последнюю версию для Win32. Программы работают без оговорок во всех версиях Windows (начиная с 98-й). Насколько программы для этой платформы работоспособны в Vista — на момент написания этих строк мне еще не удалось проверить, но, по слухам, все более-менее в порядке (правда, установление этого порядка все требует некоторых «плясок с бубном»), а на крайний случай там имеется режим совместимости с более ранними версиями, который работает, говорят, лучше, чем в Windows ХР. Потому, надеюсь, с изучением платформы. NET можно повременить.

В дальнейшем я буду предполагать, что читатель имеет некоторые навыки работы в Delphi, поскольку рассмотрение данного вопроса выходит за рамки этой книги. Остальным я рекомендую обратиться к [15] и [16], а также к моей книге [11].


Работа через функции Win32 API

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

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

Объявим следующие переменные:

var

   Form1:TForm1;

   hCOM:hFile=0;

   pDCB:TDCB;

   comt ime:TCOMMTIMEOUTS;

   xb:byte;

   xn:dword;

   ab:array[1..32768] of byte;

   st,stcom:string;

   ttime,told:TDateTime;

Размер буфера ab может быть произвольным в зависимости от количества ожидаемых данных (в данном случае он подогнан для чтения данных из используемой нами внешней flash-памяти). К началу выполнения процедуры iniCOM у нас в строке stcom должно содержаться название порта, например, «СОМ1». Задаваемые параметры: прием по схеме 8n1, скорость 9600. Текст процедуры приведен в листинге 18.1.

Листинг 18.1

procedure IniCOM;

var i:integer;

begin

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

hCOM:=CreateFile(Pchar(stcom),

   GENERIC_READ+GENERIC_WRITE,0,nil,OPEN_EXISTING,0,0);

if (hCom = INVALID_HANDLE_VALUE) then

begin

st:=stcom+'не найден';

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

      exit;

  end;

if GetCommState(hCOM,pDCB)

then st:=stcom+': baud=9600 parity=N data=8 stop=l';

if BuildCommDCB(Pchar(st), pDCB) then SetCommState(hCOM,pDCB)

   else

   begin

      st:=stcom+' занят или заданы неверные параметры';

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

      exit;

end;

GetCommTimeouts(hCom,comtime); {устанавливаем задержки:}

comtime.WriteTotalTimeoutMultiplier:=1;

   comtime.WriteTotalTimeoutConstant:=10;

   comtime.ReadlntervalTimeout:=10;

   comtime.ReadTotalTimeoutMultiplier:=1;

   comtime.ReadTotalTimeoutConstant:=2 000; {ждем чтения 2 с}

SetCommTimeouts(hCom,comtime);

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

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

   ab[3]:=13  ;{CR}

   ab[4]:=10  ;{LF}

   WriteFile(hCOM,ab,4,xn,nil);

   if ReadFile(hCOM,ab,10,xn,nil) then {ответ модема 10 знаков}

  begin

       st:=»;

for i:=1 to 10 do st:=st+chr(ab[i]);

if pos('OK',st)<>0 then

  begin

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

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

           CloseHandle(hCOM); hCOM:=0;

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

           exit;

end;

    end;

    Form1.Labe17.Caption:=s tcom+' 9600 1;

end;


Сначала текст тут мало отличается от того, что описано во всех стандартных рекомендациях по программированию порта. Единственный момент, который несколько выходит за рамки стандарта: мы не устанавливаем поля структуры DCВ напрямую, а используем функцию BuildCommDCB. Я это делаю отчасти потому, что структура DCB в Delphi транслируется из API не полностью (сравните ее описание в Windows.pas и в Win32.hip), и хотя для данного случая, разумеется, все нужные поля имеются, но функция BuildCommDCB все равно удобнее.

После стандартных установок мы сразу выполняем два действия, о которых упоминают далеко не всегда. Во-первых, мы устанавливаем все возможные задержки (timeout) для разных вариантов приема и передачи. В параметрах, которые заканчиваются на «Multiplier» можно для простоты всегда ставить «1» (если больше, то процедуры чтения/записи будут отслеживать еще и скорость поступления байтов, что нам не требуется). А остальные из этих параметров делают следующее: если задержка посылки через порт больше, чем writeTotaiTimeoutConstant (в миллисекундах), то будет прервана передача, а при задержке между поступающими байтами больше, чем ReadIntervaiTimeout, и при задержке всей процедуры чтения (в данном случае — самый главный параметр) больше, чем ReadTotalTimeoutConstant, будет прерван прием. Последний параметр мы установили равным двум секундам. При выборе этих параметров следует иметь в виду, что один байт при скорости 9600 передается/принимается примерно за 1 мс. Если эти параметры вообще не устанавливать (оставить их в значении «0», как по умолчанию), то при отсутствии принимаемых байтов процедура чтения через ReadFile просто зациклится и «повесит» всю программу.

Во-вторых, мы определяем, не является ли установленный нами порт модемом. Так как мы рассматриваем «чистый» RS-232, то для нас модемный порт все равно как бы занят. Определяем модем мы очень просто: посылаем в выбранный порт символьный код инициализации, который одинаков для всех модемов: «AT» (65 84 13 10). В ответ мы от модема должны получить строку «АТOK» (65 84 13 10 13 10 79 75 13 10), но все такие подробности нам не требуются, подозреваю, что строка для разных модемов может немного отличаться. Но в любом случае в ней должны содержаться символы «ОК», если модем, конечно, свободен (с занятым модемом я предоставляю читателю разобраться самостоятельно). В последнем операторе, если порт инициализировался нормально, выводим в Label номер порта и скорость.

Эту процедуру мы выполним сразу при запуске (для СОМ1). Заодно напишем процедуру (листинг 18.2) закрытия порта (ведь порт все время занят, пока программа запущена).

Листинг 18.2

procedure TForm1.FormCreate(Sender:TObject);

begin

{инициализация COM1 при запуске}

stcom:='COM1';

    IniCOM;

end;

procedure TForm1.FormDestroy(Sender:TObject);

begin {уничтожаем COM}

 CloseHandle(hCOM);

end;

Кроме этого, конечно, следует создать меню для выбора порта по ходу работы с программой (аналогичное меню СОМ в моей программе СОМ2000, но мы не будем здесь на этом останавливаться, т. к. на основе приведенной процедуры его создать несложно.

Итак, порт открыт, инициализирован с нужными параметрами — что дальше? Далее все очень непросто, потому что мы здесь не можем, как в DOS, зациклить программу в ожидании поступившего байта или нажатия клавиши. Windows и сама представляет собой бесконечный цикл в ожидании сообщений (событий) — в точности такой же, как мы организовывали у себя в контроллере в ожидании прерываний. И вот этими сообщениями мы и должны ее снабдить.

Сначала разберем простейший способ обмена данными: когда мы точно знаем, что нам придет в ответ и в каком количестве. Например, посмотрим, как можно организовать процедуру приема значения часов в ответ на команду $А2 (описание того, как это работает в контроллере, см. главу 16). Расставим на форме следующие компоненты: кнопку Button1, а также Label и напротив него StaticText, и то и другое в количестве 6 штук. В компоненты Label запишем, соответственно, по порядку: «Часы», «Минуты», «Секунды», «Дата», «Месяц», «Год» (в таком же порядке их выдает наш контроллер). Листинг 18.3 иллюстрирует, как будет выглядеть сама процедура, когда по нажатию кнопки Button1 в МК выдается команда $А2, а потом принятые значения выводятся в Static Text (т. к. они в BCD-формате, то приходится переводить в НЕХ-представление).

Листинг 18.3