Главная страница
Библиотека (скачать книги)
Скачать софт
Введение в программирование
Стандарты для C++
Уроки по C#
Уроки по Python
HTML
Веб-дизайн
Ассемблер в среде Windows
ActiveX
Javascript
Общее о Линукс
Линукс - подробно
Линукс - новое
Delphi
Паскаль для начинающих
Турбопаскаль
Новости
Партнеры
Наши предложения
Архив новостей





ПРИЛОЖЕНИЕ 4
Обмен данными с персональным компьютером и отладка программ через UART
Технические аспекты взаимодействия ПК с микроконтроллерными устройствами мы разбирали в главе 13. Но обойтись без самостоятельного написания программ для взаимодействия ПК и МК разработчик устройств для МК, как правило, не может: даже если окончательный вариант для какого-либо коммерческого проекта будет писать "настоящий" программист, квалифицированно разбирающийся в устройстве Windows или построении баз данных, то заставлять его участвовать в процессе отладки схемы й программы МК нецелесообразно, т. к. это удорожит и затянет проектирование. Ну а для радиолюбителей просто нет другого выхода, кроме написания "верхней" программы самостоятельно.
Правда, в этом деле есть смягчающие обстоятельства. Создание таких программ заметно проще, чем написание офисных или веб-приложений, и представляет собой сравнительно несложную и к тому же достаточно консервативную область программирования. Во многих случаях даже не требуется знание API Windows, нужны лишь простейшие приемы обращения со стандартными компонентами графической среды для того, чтобы правильно сконструировать интерфейс, и понять общие принципы функционирования Windows. Воспользоваться можно любой визуальной средой программирования, например, Borland Delphi, С++ Builder, Microsoft Visual С++ или Visual Basic. Мы будем далее ориентироваться на Delphi 7 (совместима с более современной Delphi 2007 for Win32 от CodeGear). Указанный далее компонент для СОМ-порта также может применяться в Borland С++ Builder без каких-то доработок.
В дальнейшем я буду предполагать, что читатель имеет некоторые навыки работы в Delphi — изучение этого вопроса выходит за рамки этой книги. Остальным я рекомендую обратиться к [18] и [19], а также к моей книге [20].

Работа с СОМ-портом в Delphi
Так как наше повествование имеет сугубо практическую направленность, то не будем задерживаться на том, как можно "правильно" организовать работу с коммуникационным портом через Windows API (см. об этом в [8] и [20]), и сразу перейдем к варианту с использованием готового компонента. Мы будем использовать один из самых удачных и профессионально сделанных компонентов для СОМ-порта— свободно распространяемый AsyncFree некоего Петра Вониса (Petr Vones), судя по электронному адресу, из Чехии. Компонент доступен бесплатно, с исходными кодами, скачанный архив включает в себя в том числе и файлы dpk, что упрощает процедуру установки — нужно просто щелкнуть мышью на том из этих файлов, который соответствует имеющейся у вас версии Delphi, и компонент установится самостоятельно. Хотя к самому компоненту приложена ссылка на сайт Delphree Open Source Initiative, этого сайта больше не существует, скачивать следует версию AsyncFree 1.04 по ссылке: http://sourceforge.net/project/showflles.php? group_id=20226.
Принцип работы компонента заключается в создании параллельного потока, в котором байты принимаются по мере их поступления и накапливаются в буфере независимо от "деятельности" основной программы. Специально следить за приходом данных не требуется — все делается автоматически, остается только отловить и идентифицировать принятые данные. Недостаток этого способа в том, что данные принимаются кучей в одной процедуре, и приходится отдельно разбираться, что же мы приняли в данный момент, и где заканчиваются одни данные и начинаются другие. После установки компонент будет находиться в палитре компонентов на вкладке программы AsyncFree. На самом деле там образуется много компонентов, но нам потребуется только самый первый из них под названием AfComPort. Установим его на форму. В перечень переменных добавим:
FlagCOM:boolean=False; FlagSend: integer=0; //значение флага определяет тип данных tall:integer; 11для отсчета времени таймера xn,xb:byte; //счетчик байт и буфер передатчика st,stcom: string; ab: array[1..65536] of byte; //приемный буфер
На форму добавим также компонент Label (Labeii), в который будем выводить установленный порт и скорость передачи, а также Timer (Timerl). Проверьте, чтобы у таймера интервал составлял 1000 мс (так по умолчанию),
Кроме этого, установим компонент ComboBox (выпадающий список), у которого в свойстве items запишем строки для выбора СОМ-порта ("СОМ1", "COM2" и т. п.). Аналогичный список можно создать для выбора скорости передачи, но если речь идет о конкретном приборе, то это необязательно (а вот СОМ выбирать, скорее всего, придется).

Подробности
В данной программе мы задаем номера портов принудительно, и проверяем их на доступность при каждой инициализации. При этом обычно достаточно ограничиться восьмью портами (СОМ1-СОМ8), в большинстве случаев даже четырьмя (СОМ 1-COM4). Но, общем случае программист не может заранее знать, сколько в данной системе портов, точнее, какие номера они имеют — вполне вероятен случай, когда в системе создан какой-нибудь виртуальный порт с номером СОМ11 или СОМ17. Для того чтобы выяснить список доступных портов, в Windows имеется функция EnumPorts, применение которой подробно описано в [22].
Начнем с того, что напишем процедуру инициализации порта inicoM (к моменту ее вызова в переменной stcom должна находиться строка с номером порта, начиная с единицы, по образцу "СОМГ), листинг П4.1.
procedure IniCOM; var i, err :integer; begin FlagCOM:=False; Forml.Labell.Caption:=•COM?'; {инициализация COM — номер в строке stcom} Forml.AfComPortl.Close; {закрываем старый COM, если был} val (stcom[length(stcom) ] ,i,err); {извлекаем номер порта} if err=0 then Forml.AfComPortl.ComNumber:=i else exit; Forml .AfComPortl.BaudRate:=br9600; {скорость 9600} try Fo rml.AfComPort1.Open; {пробуем открыть} except if not Forml.AfComPortl.Active then {если не открылся} begin st:=stcom+' does not be present or occupied.'; Application.MessageBox(Pchar(st), 'Error',MB_OK); exit {выход из процедуры — неудача} end; end; //проверка, не является ли открытый порт модемом ab[l]:=ord('А'); {будем посылать инициализацию модема} ab[2] :=ord('T'); ab[3]:=13;{CR} ab[4]:=10; {LF} for i:=l to 4 do Forml.AfComPortl.WriteData(ab[i],1); {ответ не сразу:} Forml.Timerl.Enabled:=True; tall:=0; while talKl do application. ProcessMessages; {пауза в 1 с} Forml .Timerl.Enabled:=False; st:=Forml.AfComPortl.ReadString; {ответ модема 10 знаков} if pos('OK',st)<>0 then {модем} begin st:=stcom+' занят модемом'; Application.MessageBox(Pchar(st),'Error',MB_OK); exit; end else {все нормально, COM открыт} begin Forml.Labell.Caption:=stcom+' 9600'; FlagCOM:=True; end; end;

FiagcOM играет роль индикатора — доступен порт или нет. Если флаг остался в значении False, то процедуру следует повторить с другим значением в строке stcom (ее мы задаем с помощью ComboBox). При определении модема применен хитрый способ задания паузы — вместо обычного оператора sleep, который тормозит программу, мы использовали таймер. Чтобы это сработало, нужно в обработчике события onTimer все время увеличивать переменную tail. Полностью процедура по таймеру приводится в листинге П4.2.
Как только мы обратимся к процедуре AfcomPorti.open, у нас немедленно будет создан параллельный поток и весь прием пойдет через него. Поэтому, чтобы при определении модема принятые байты не обрабатывались, нужно не забыть добавить в процедуру приема выход по условию FiagCOM=Faise.
Для создания этой процедуры обычным способом — через инспектор объектов — создадим обработчик события AfComPortlDataRecived1 (ЛИСТИНГ П4.2).
Написание слова "receive" — настоящая проблема для любого, кто не является носителем английского языка. Каких только вариантов не встретишь на просторах Сети! Как видим, наш чех Петя Вонис также не избежал общей участи.
' Листинг П4.2
procedure TForml.AfComPortlDataRecived(Sender: TObject; Count: Integer); {чтение очередного байта по сообщению wmCOMPORT} var i:integer; begin if FlagCOM=False then exit; {если модем еще не опрошен}
if countoO then {если что-то принято}
begin
AfComPortl.ReadData(ab,count); {читаем буфер в массив} xn:=xn+count; {число принятых байтов} tall:=0; {обнуляем время} end; end;
На самом деле условие countoo не требуется (иначе бы процедура просто не была бы вызвана), оно введено просто ради порядка. По выходу из процедуры в переменной хп будет накапливаться количество принятых байтов. Осталось только дописать остальные процедуры. Поставим на форму кнопку, назовем ее "Запрос", и по ее нажатию будем посылать команду (в данном случае байт со значением $А2 — запрос времени для наших часов из главы 13), листинг П4.3.

procedure TForml.ButtonlClick(Sender: TObject); begin {запрос} if FlagCOM=False then exit;
{если порт еще не инициализирован — выход}
AfComPortl.PurgeRX; {очищаем буфер порта на всякий случай} xb:=$А2; AfComPortl.WriteData(xb,1); {посылаем команду} FlagSend:=$A2; {запоминаем, что посылали именно А2} tall:=0; {обнуляем время} хп:=0; {счетчик принятых байтов} Timerl.Enabled:=True; {запускаем таймер} end;
procedure TForml.FormCreate(Sender: TObject); begin {инициализация C0M1 при запуске} stcom:='C0M11; IniCOM; end; procedure TForml.ComboBoxlSelect(Sender: TObject); begin //в список заранее заведены строки "С0М1", "COM2" и т. д. stcom: =ComboBoxl.Text; {устанавливаем порт} IniCOM; end;
procedure TForml.FormDestroy(Sender: TObject); begin AfComPortl. Close; {закрытие порта} end;

Теперь нам осталось разобраться с тем, что мы там напринимали. Это позволит сделать установленное нами значение FiagSend и таймер. Так как мы общаемся с часами из главы 13, то в ответ на команду $А2 ДОЛЖНО прийти шесть байтов времени в формате ЧЧ:ММ:СС ДД:мм:ГГ (причем, как мы помним, сразу в десятичном виде). Поставим на форму шесть компонентов StaticText, в которые мы будем принимать эти значения времени (рис. П4.1).
В таймере переменную tall мы будем увеличивать на единицу, а в процедуре приема, мы все время ее обнуляем, так что пока она равна нулю, можно полагать, что прием еще не закончился. Как только она станет больше единицы (прошло более секунды с момента последнего принятого байта или прием вообще не происходил), мы начинаем что-то делать — но только в том случае, если флаг FlagCOM установлен (True), иначе это вообще был не прием, а опрос модема (листинг П4.4).
Листинг П4.4
procedure TForml.TimerITimer(Sender: TObj ect); var i:integer; begin {таймер} inc tall if FlagCOM=False then exit;
if tall>l then
begin Timerl.Enabled:=False; {выключаем таймер} if xn=0 then {если счетчик = 0, то ничего не принято} begin Application.MessageBox('Устройство не обнаружено','Error',МВ_ОК); exit {выход из процедуры — неудача} end else begin {иначе обрабатываем данные} if FlagSend=$A2 then {если был запрос времени} begin if xn<>6 then begin Application.MessageBox('Неправильный формат данных', 'Error',МВ_ОК); exit; end; StaticTextl.Caption:=IntToHex(ab[l] ,2); //часы StaticText2.Caption:=IntToHex(ab[2] ,2); //минуты StaticText3.Caption:=IntToHex(ab[3] ,2); 11сек StaticText4.Caption:=IntToHex(ab[4],2); 11дата StaticText5.Caption:=IntToHex(ab[5] ,2); //месяц StaticText6.Caption:=IntToHex(ab[6],2); //год end; end; end; end; {конец таймера}

Отметим, что размещение каждого значения в отдельности в соответствующем окне показано в этой процедуре лишь для наглядности. Если, как в данном случае, последовательные значения массива размещаются в однотипных элементах, расположенных по порядку, более грамотно будет это сделать в цикле (причем в общем случае элементов может бь1ть достаточно много, и перебирать вручную их неудобно). Так как вперемешку с компонентами staticText на форме встречаются и другие (на рис. П4.1 это неупомянутые нами Label, в которых записаны названия полей "Час", "Мин" и т. д., а также Label 1, в который выводится порт и скорость), то процедура может выглядеть так, как в листинге П4.5.
I Листинг П4.5 ; xb:=l; for i := 0 to ComponentCount-1 do if (Components[i] is TStaticText) then begin (Components[i] as TStaticText).Caption:=IntToHex(ab[xb], 2); xb:=xb+l; if xb>xn then break; end;

По аналогии вы легко добавите процедуры, соответствующие всем остальным командам, предусмотренным в программе МК с часами. Пользуясь функцией Delphi DateTime, нетрудно соорудить процедуру, которая будет загружать из компьютера точное время (только с форматом TDateTime придется немного попотеть, см. по этому поводу [18] и [19]). Не забывайте принимать и анализировать возвращаемые байты для процедур записи. При длинной операции приема данных из памяти, когда число байтов заранее неизвестно, суммарное значение счетчика хп покажет, сколько именно байтов принято. Причем если это число не кратно четырем, то можно смело утверждать, что целостность данных была нарушена.

Установка линии RTS в DOS и Windows
При начальной загрузке компьютера линии RTS и DTR чаще всего устанавливаются в состояние с отрицательным уровнем напряжения (от -9 до -12 В), но вообще говоря, могут оказаться в любом состоянии. В среде DOS и Win95/98/Me для установки в положительный уровень в принципе можно применить любой имеющийся под рукой DOS-драйвер мыши, подключаемой к СОМ-порту,' который удобно загружать, например, через autoexec.bat прямо при включении компьютера (если только пренебречь опасностью, что при поступлении данных на этот порт курсор может самопроизвольно начать бегать по экрану и производить всякие нехорошие действия). Однако в Windows NT и других ОС из этого семейства такой примитивный способ, естественно, работать не будет. Рассмотрим, как можно установить уровни принудительно на примере линии RTS. В DOS можно написать простую программку на Turbo Pascal, которая под названием RTSDOS имеется в архиве, доступном по адресу http:// revich.lib.ru/rts.zip. Исходный текст ее расположен в файле RtsDos.pas. Запускается она из командной строки с ключами "+СОМх" или "-СОМх" (где х есть 1, 2, 3 или 4). Если первый символ ключа "+", то линия установится в положительный уровень напряжения, если наоборот— в отрицательный. Когда все в порядке — программа вернет (в текстовом режиме) номер порта ввода-вывода для заданного COM ($03F8, например), если его не существует, то не вернется ничего. При запуске без ключа программа выдаст текст (на достаточно корявом английском), рассказывающий примерно то, чТо я тут описал.
В этой программе мы сначала определяем в служебной области памяти BIOS (сегмент 0040h) номер порта ввода-вывода для заданного СОМ (они расположены в самых первых адресах этого сегмента, каждый адрес порта занимает двухбайтовую ячейку). Если там записаны нули, то порт не существует, если же он есть, то мы используем ассемблерную процедуру для установки линии RTS через прерывание Intl4.
Аналогичная Windows-программа называется RTSWIN и расположена в том же архиве в папке RTSWIN вместе с проектом. Написана она в виде консольного приложения, которое запускается по той же методике, что и описанная программа для DOS. Она работает только под Windows 9х (относительно семейства NT см. пояснения далее), и потому первым делом в ней определяется версия ОС, если это семейство NT, то программа ничего не делает. Всю информацию программа выдает в текстовое окно.

Заметки на полях
Для знатоков API Windows отметим особенность этой программы. В Delphi структура DCB транслируется не полностью. В частности, там отсутствует поле fRtsControl, через которое можно управлять режимом линии RTS, зато имеется поле Flags, через биты которого и предлагается в том числе этим режимом управлять. Сначала через Flags там устанавливается режим управления дополнительными линиями (константа RTS_CONTROL_HANDSHAKE = $юо), при этом само управление осуществляется через функцию EscapeCommFunction, вот так: tpDCB.Flags:=(tpDCB.Flags and $FFFFC0FF) or $00000100; SetCommState( pCOM, tpDCB); // Reset RTS if ch='-' then EscapeCommFunction(pCOM, CLRRTS); // Set RTS if ch=1 +' then EscapeCommFunction(pCOM, SETRTS);

Теперь главный вопрос — а почему все это не работает в Windows семейства NT? На самом деле приведенная процедура вполне будет работать в любой Windows (и обязана это делать, т. к. функции Win32 везде одинаковые), но NT тщательно следит за тем, чтобы все ресурсы использовались каждой программой независимо от других. Если запустите указанную программу в ХР (удалив, естественно, из нее условие выбора ОС), то изначально установленный, например, в состояние с отрицательным уровнем вывод RTS на долю секунды перейдет в состояние с положительным уровнем, а потом, когда программа закончит работу, вернется обратно в исходное. Иначе говоря, установка вывода порта действует только на время работы программы. Отсюда методика управления выводом RTS в семействе NT может быть только такой: если ваше устройство использует вывод RTS для питания, то прилагаемая к нему программа должна устанавливать этот вывод самостоятельно каждый раз при запуске. Для такой установки можно включить процедуру из модуля RTSWIN в вашу программу.

Программа СОМ2000
Отлаживать микроконтроллерные программы удобно с помощью т. н. эмуляторов терминала, которые представляют собой программы для посылки и приема данных через последовательный порт. Существует много программ этого рода разной степени сложности, одну из них автор этих строк развивает уже в течение 10 лет. Программа для доступа к микроконтроллерным устройствам через СОМ-порт под названием СОМ2000 доступна на моей домашней страничке по адресу: http://revich.lib.ru/comcom.zip. Устанавливать ничего не требуется — просто распакуйте содержащий два файла архив в любую папку. Сама программа содержится в файле com2000.exe. Файл помощи help2000.htm можно открыть как изнутри программы (через меню со знаком вопроса или клавишей ), так и обычным способом в браузере, что удобнее. Собственно, в этом файле все рассказано, здесь я только немного подробнее опишу основные возможности программы.
На рис. П4.2 представлено единственное окно программы СОМ2000. Основная функциональность ее заключается в постоянном ожидании приема данных по заданному порту с заданной скоростью (на рис. П4.2 установлен порт СОМ1 и скорость 9600, см. статусную строку внизу). Принятые данные побайтно отображаются на экране, причем отображение их может осуществляться тремя различными способами (в соответствии с выбором из показанного на рис. П4.2 меню в пункте Receive): в шестнадцатеричной форме, в десятичной и в виде текстового символа, соответствующего значению принятого байта. К последней возможности нужно относиться с осторожностью— Windows не "любит" встречать в текстовых компонентах несуществующие символы (вроде символа с номером 0) и программа может "рухнуть". Так что текстовый режим следует выбирать, только если вы ожидаете именно текст.
На рис. П4.2 показан пример приема байтов в шестнадцатеричной форме в ответ на посланные команды (во втором случае, показанном на экране полностью, это команда $Е2). Посылать команды можно выбором из меню Send Byte(s) также одной из трех возможностей — с клавиатуры (пункт Keyboard, +), непосредственным вводом значений (Value, +) или из файла (From file, +). Посылка с клавиатуры означает то же, что и прием в текстовой форме — при нажатии буквенной клавиши посылается ее код в виде байта с соответствующим значением. В Windows с ее путаницей в отношении виртуальных кодов клавиш эта возможность почти потеряла значение, но до сих пор встречаются устройства, в инструкции к которым команды записаны именно в виде символов (а не их номеров в таблице ASCII). Для совместимости с этими устройствами и сохранена такая возможность. Если вы включали клавиатуру, то внизу в статусной строке надпись Keyboard Off сменится надписью Keyboard On. Не забудьте обратиться к пункту меню Keyboard или нажать комбинацию клавиш + еще раз, чтобы выключить отсылку символов с клавиатуры после ввода, иначе они будут отсылаться и дальше при любом нажатии клавиш.

В обычном режиме используются две другие возможности, в основном вторая— посылка байтов с конкретным значением. При обращении к меню Send Byte(s) | Value (+) вы вызовете на экран однострочный редактор, в поле которого можно ввести нужное значение байтов, причем сразу много — до 32. Байты можно вводить в десятичном или шестнадцатеричном виде (с предваряющим знаком $) вперемешку, разделяя их пробелами. В выпадающем списке редактора запоминаются ранее отосланные вами строки (в том числе там есть несколько значений по умолчанию, для образца). После ввода значений нужно либо нажать на , либо совершить двойной щелчок мышью в окне редактора с введенной строкой байтов. Обратите внимание, что значение не проверяется, и при превышении диапазона посылаемый байт усекается до восьми разрядов, например значение 257 будет послано, как 257 - 256 = 1. Проверяется только корректность записи — например, при попытке послать OA без предваряющего $, вам будет выдано сообщение об ошибке.

Аналогично осуществляется посылка из файла, которая используется тогда, когда нужно послать много байтов, и вводить их в однострочный редактор неудобно. Тогда следует создать текстовый файл, в котором содержится строка со значениями, составленная по точно таким же правилам, что действуют для непосредственной посылки, и выбрать этот файл через меню Send Byte(s) I From file (+).
Заметим, что непрерывный прием можно отключить, если выбрать пункт меню Disable (он изменится на Enable, для включения его следует нажать еще раз). Это полезно, когда устройство (вроде GPS-навигатора) выдает информацию непрерывно и не хочется забивать экран ненужными данными. Только будьте внимательны: если режим непрерывного приема отключен, вы можете забыть об этом и подумать, что прибор внезапно перестал работать. Пункт меню Clear предназначен для очистки экрана. Важная особенность СОМ2000 — непрерывное ведение log-файла, который создается при первом запуске и далее только дополняется. В него записывается все, что отображается на экране, плюс при каждом запуске программы пишется еще текущая дата и время. Log-файл полезен, если вы хотите сохранить принятые данные. Со временем его размер чрезмерно увеличивается, и, чтобы удалить ненужные данные, просто сотрите com.log, и он создастся заново при следующем запуске.

В программе можно, естественно, задавать СОМ-порт (от СОМ1 до COM4) и скорость обмена (пункт меню СОМ). Кроме этого, можно менять оформление программы (цвет фона и надписей) через пункт меню Receive | Colors. Оформление и заданные режимы запоминаются к следующему сеансу. Недостаток текущей версии программы — невозможность манипулирования 9-битовыми посылками, а также выводами RTS и DTR.

Отладка программ
с помощью эмулятора терминала
С помощью СОМ2000 очень удобно отлаживать программы МК: любую схему можно превратить в отладочный стенд, временно расставив в нужных местах программы контрольные точки в виде пар операторов:
move temp,RegX rcall Out_com
Описание процедуры outcom см. в главе 13. Здесь Regx— регистр, значение которого хочется отследить в реальном времени. Если это регистр ввода-вывода, то move нужно заменить инструкцией in. Подсоединив схему к компьютеру (см. главу 13), вы будете получать на ПК значения требуемого регистра при каждом прохождении программой этой контрольной точки. Иногда это может нарушить нормальную работу программы (при отправке нескольких байтов UART работает медленно), но даже с учетом этого обстоятельства такой способ нагляднее, быстрее и дешевле, чем применение дорогих отла-т дочных модулей в совокупности с AVR Studio. Если вы исследуете программу, в которой сама по себе работа с UART не предусмотрена, то ничего не стоит вставить его инициализацию туда временно, и также на время подключить проводами выводы RxD и TxD к небольшому отладочному стенду, состоящему из одного-единственного преобразователя уровней UART/RS-232. Единственное неудобство — при перестановке контрольных точек программу придется каждый раз перекомпилировать и заново записывать ее в МК, но это все равно потребуется при ее правке. Поэтому я стараюсь иметь компьютеры с двумя СОМ-портами: к одному из них подключается программатор, к другому — выход UART через преобразователь. Если у вас есть редактор текста, позволяющий запускать компиляцию прямо из него, то процесс отладки микропрограммы становится ненамного сложнее, чем работа в среде Turbo Pascal, Delphi или Visual Basic. Держа на экране открытыми одновременно три окна (asm-редактор, программу для загрузки через программатор и СОМ2000), вы получаете возможность в реальном времени править программу и немедленно проверять ее работоспособность, расставляя контрольные вызовы функции outcom в нужных местах.



     
 

Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru