Palm. COM-порт (RS232). Программирование. IDE CodeWarrior

Palm. COM-порт (RS232). Программирование. IDE CodeWarrior


Автор: Андрей Маркеев

Источник: robotics.ru

В этой статье я расскажу о низкоуровневой работе с COM-портом на КПК Palm.

В многочисленных источниках в интернете можно найти распиновку
разъемов для Palm-ов разных моделей. Привожу сведения для имеющегося у
меня Palm m505:

(Eсли смотреть спереди (как на крэдл, так и на КПК), то пины пронумерованы слева направо)

 1  GND (земля)
2 USB D+
3 USB D-
4 USB VBUS
5 HotSync IRQ (при нажатии на кнопку HotSync: +3.3В)
6 reserved (не подключено)
7 GND (земля) (к 5му пину DE-9)
8 ID (идентификация типа периферии: 7.5КОм к GND для последовательного порта)
9 Vout ~ 3.3В @ 100 мА максимум
10 Rxd (вход) (к 2му пину DE-9)
11 Txd (выход) (к 3му пину DE-9)
12 Attach Detect (gnd)
13 CTS (вход) (к 7му пину DE-9)
14 RTS (выход) (к 8му пину DE-9)
15 DTR (выход) (к 6му пину DE-9)
16 зарядка Vin (+5.0В DC 5% @ 1.0А)

Из представленной распиновки видно, что из шестнадцати контактов -
шесть имеют непосредственное отношение к COM-порту, остальные
предназначены для работы с USB, для коммуникации с крэдлом и для
зарядки.

Таким образом, очевидно, что КПК частично реализует COM-порт,
позволяя оперировать с сигналами CTS, RTS и DTR в дополнение к
стандартным каналам Rxd и Txd. Этого с избытком достаточно для
оперирования практически любым разумным устройством. Например,
интересным вариантом является подключение компьютерной мыши...

Давайте протестируем, действительно ли Palm работает с COM-портом.

Для этого понадобится любая программа-монитор порта RS232. Я использовал COM Port Visual Control от Eshar Soft.

После нажатия кнопки HotSync на крэдле, подключенном к COM-порту компьютера, в мониторе порта я увидел следующие строки:

 Подключено к : Com1

12:55:06 $84 1 0 0 0 0 1 0 0 [ 2 ] 132
12:55:49 $14 0 0 0 1 0 1 0 0 [ 3 ] 20
12:55:50 $EF 0 0 0 0 0 0 1 1 [ 4 ] 190
12:55:51 $00 0 0 0 0 0 0 0 0 [ 5 ] 0
12:55:51 $03 0 0 0 0 0 0 1 1 [ 6 ] 3
12:55:51 $BE 1 1 1 0 1 1 1 1 [ 7 ] 3
12:55:51 $BA 1 0 1 1 1 0 1 0 [ 8 ] 239
12:55:51 $00 0 0 0 0 0 1 1 0 [ 9 ] 252
12:55:51 $03 0 0 0 0 1 0 1 1 [ 10 ] 8
12:55:51 $00 0 0 0 0 0 0 1 1 [ 11 ] 0
12:55:51 $AC 1 0 0 0 1 1 1 1 [ 12 ] 9
12:55:51 $09 0 0 0 0 1 0 0 1 [ 13 ] 9
12:55:51 $EF 1 0 1 1 1 1 1 1 [ 14 ] 190
12:55:51 $00 1 0 1 0 0 1 1 0 [ 15 ] 0
12:55:51 $01 0 0 0 0 0 0 0 1 [ 16 ] 1
12:55:53 $EF 0 0 0 0 0 0 1 1 [ 17 ] 190
12:55:53 $00 0 0 0 0 0 0 0 0 [ 18 ] 132
12:55:53 $01 0 0 0 0 0 0 0 1 [ 19 ] 1
12:55:55 $AF 0 0 0 0 0 0 0 0 [ 20 ] 255
12:55:55 $01 0 0 0 0 0 0 0 1 [ 21 ] 1
12:55:57 $EF 0 0 0 0 0 0 0 0 [ 22 ] 190
12:55:57 $00 1 1 1 1 0 1 1 0 [ 23 ] 0
12:55:57 $01 0 0 0 0 0 0 0 1 [ 24 ] 1
12:55:59 $EF 0 0 0 0 0 0 1 1 [ 25 ] 190
12:55:59 $00 0 0 0 0 0 0 0 0 [ 26 ] 132
12:55:59 $01 0 0 0 0 0 0 0 1 [ 27 ] 1
12:56:02 $03 0 0 0 0 0 0 1 1 [ 28 ] 0
12:56:02 $01 0 0 0 0 0 0 0 1 [ 29 ] 1
12:56:04 $EF 0 0 0 0 1 0 1 1 [ 30 ] 190
12:56:04 $00 1 0 1 0 0 0 0 1 [ 31 ] 0
12:56:04 $01 0 0 0 0 0 0 0 1 [ 32 ] 1
12:56:06 $00 0 0 0 0 0 0 0 0 [ 33 ] 0
12:56:06 $01 0 0 0 0 0 0 0 1 [ 34 ] 1
12:56:08 $00 0 0 0 0 1 0 0 0 [ 35 ] 255
12:56:08 $01 0 0 0 0 0 0 0 1 [ 36 ] 1
12:56:10 $EF 0 0 0 0 0 0 0 0 [ 37 ] 190
12:56:10 $00 1 0 1 0 0 1 1 0 [ 38 ] 0
12:56:10 $01 0 0 0 0 0 0 0 1 [ 39 ] 1
12:56:12 $EF 0 0 0 1 0 0 1 1 [ 40 ] 190
12:56:12 $84 0 0 0 0 0 1 0 0 [ 41 ] 3
12:56:12 $01 0 0 0 0 0 0 0 1 [ 42 ] 1

Таким образом, очевидно, что COM-порт действительно нормально работает, и можно двигаться дальше.

Пример программы для обращения к COM-порту я взял среди исходников для тестирования PPRK (Palm Pilot Robot Kit).

Общий алгоритм примерно таков (в примере я отсылаю "Hello world!" в порт):

  UInt32 portRef;
UInt32 result;

// ищем библиотеку последовательного порта
SysLibFind("Serial Library", &portRef);
result = SerOpen(portRef, 0, 9600); // открываем порт

if (result == 0) // если порт открыт успешно
{
Err error;
char prm_buffer[];

// Подготовка строки для записи в порт
StrPrintF(prm_buffer, "Hello world!\r");

// Отсылаем. SerSendWait нужен для ожидания реакции порта
SerSendWait(portRef, -1);
SerSend(portRef, prm_buffer, StrLen(prm_buffer), &error);
SerSendWait(portRef, -1);

SerClose(portRef); // закрываем порт
}

На деле все чуть-чуть сложнее, но совсем ненамного.

Самое противное при программировании древнего мастодонта под
названием Palm - отсутствие как класса многопоточности. Это встроенное
ограничение Palm OS, и ничего с ним не сделаешь, к сожалению. Если Ваш
КПК имеет на борту, например, Windows Mobile - используйте C# и .Net
Framework, там есть все возможные средства. Поподробнее, и простейший
пример программирования - можно посмотреть в статье Виталия Клебана.

Но вернемся к Palm. Есть два типа сред разработки для Palm - которые
можно использовать на самом КПК (OnBoardC), и те, которые можно
использовать на компьютере, а на КПК загружать уже готовые программы
(CodeWarrior). OnBoardC очень неудобен, даже при наличии клавиатуры. И
между исходниками не покопипастишь, и экранчик маленький, и визуальные
средства никакие, да и в стандартном хедере для OnBoardC просто нет
функций управления COM-портом (или я их не нашел). В общем, лучше
используйте компьютерные среды в сочетании с эмулятором Palm OS на ПК.

Кстати, под Palm существуют компиляторы самых разных языков - от простейших Basic и Pascal до экзотичных типа Forth и LISP.

Одну из наиболее мощных и проработанных из компьютерных сред
разработки под Palm OS (язык C/C++), CodeWarrior от компании
MetroWerks, выбрал я. По слухам, кое-какую информацию по скачиванию
этой IDE можно найти на форуме сайта Palmz... :)

После скачивания, CodeWarrior устанавливаем и запускаем. Первым делом жмем кнопку New, и выбираем создание нового проекта C++:

Создание нового проекта в CodeWarrior

Обзываем его TestComPort. Затем отвечаем на пару вопросов - я все
оставил по умолчанию. Наконец, проект готов. Хорошо бы посмотреть, что
за шаблон нам подкинул CodeWarrior. Для этого жмем кнопку Make, затем
запускаем эмулятор (меню PalmOS->Launch Emulator). Это стандартный
эмулятор, который можно скачать и отдельно, однако то, что его встроили
- конечно приятно, все равно без него не обойтись. Выбираем образ
прошивки нашего Palm-а. У меня такой образ уже был, так как я баловался
с эмулятором и раньше. Если у Вас такого файлика еще нет - создайте
его. Именно для этого существует программка ROM Transfer, поставляемая
вместе с эмулятором. В общем и целом, все что потребуется - это
закачать ROM Transfer на КПК, запустить его, и запустить на компьютере
Emulator, выбрав в его меню соответствующий пункт (естественно, КПК
должен быть в крэдле, присоединенном к компьютеру). Через пару минут
нужный образ будет у Вас на диске.

Загрузив образ, получаем картинку с экраном КПК. Жмем на этом экране
правой кнопкой мыши, и ищем в появившемся меню пункт Install
Application/Database:

Устанавливаем программу в эмулятор

Затем выбираем наш недавно скомпилированный (после нажатия на
кнопочку Make) prc-файлик - он лежит в каталоге My Documents ->
TestComPort -> Debug. Жмем ОК - и вот наше приложение уже в
эмуляторе:

Тестовое приложение загружено в эмулятор

Запускаем, видим примерно такую картину:

Тестовое приложение запущено в эмуляторе

Прекрасно, пример достаточно хорош, присутствует меню, присутствуют
контролы на форме. С интерфейсом придется поработать совсем немножко.
Выходим из эмулятора, возвращаемся в IDE. Выбираем в списке файлов
проекта слева файл ресурсов TestComPort_Rsc.rcp. Именно там хранятся
описания для всех наших форм и контролов. Видим в самом начале:

MENU ID MainMenuBar
BEGIN
PULLDOWN "Edit"
BEGIN
MENUITEM "Undo" ID 10000 "U"
MENUITEM "Cut" ID 10001 "X"
MENUITEM "Copy"ID 10002 "C"
MENUITEM "Paste" ID 10003 "P"
MENUITEM "Select All" ID 10004 "S"
MENUITEM SEPARATOR ID 10005
MENUITEM "Keyboard" ID 10006 "K"
MENUITEM "Graffiti Help" ID 10007 "G"
END


END

Очевидно, это описание главного меню. Вставим сформированное по
аналогии собственное меню, в приведенный текст перед строкой PULLDOWN
"Edit":

PULLDOWN "Port"
BEGIN
MENUITEM "Open port" ID MainMenuOpenPort "O"
MENUITEM "Send \"Hello world!\"" ID MainMenuSendHello "H"
MENUITEM "Close port" ID MainMenuClosePort "E"

END

Легко удостовериться, что меню действительно появилось. Сохраним
файл, затем нажмем Make и снова запустим эмулятор. Кстати, более
простой способ скомпилировать и сразу запустить программу - выбрать в
меню Project пункт Run (или нажать Ctrl-F5; вообще, многие кнопки
работают по аналогии с Microsoft Visual Studio). Итак, смотрим что
получилось у нас с меню:

Новое меню тестового приложения

Пункты меню, правда сказать, не работают. Естественно, мы и не
задавали кода для их обработки. Но это достаточно просто исправить.

Посмотрим на структуру программы, и определим, где нам вставить наш код для обработки нажатия пунктов меню.

Как и любая программа Palm OS, наш пример начинает выполнение с
функции PilotMain, которая содержит проверку на соответствие версии ОС
допустимой для данного проекта (вызов процедуры ROMVersionCompatible),
а также вызовы основных процедур:

  • AppStart() - в эту процедуру можно вставить код, необходимый для
    выполнения при запуске программы. Например, восстановление сохраненных
    данных, работа с БД, разнообразные проверки и др.
  • FrmGotoForm(MainForm) - это стандартная процедура для
    активизирования формы с заданным ID. (в коде можно найти объявление
    "#define MainForm 1006")
  • AppEventLoop() - эта процедура осуществляет прием сообщений от ОС и
    соответствующие действия. Схема практически 1 в 1 напоминает работу с
    Windows API, что очень здорово и очень удобно, согласитесь :)
  • AppStop() - в эту процедуру можно вставить код, необходимый для
    выполнения при завершении программы. Например, сохранение текущего
    состояния и параметров программы в БД.

Рассмотрим более внимательно AppEventLoop - она не делает
практически ничего, кроме как в цикле вызывает AppHandleEvent. Эта
процедура, в свою очередь, содержит шаблон для обработки процедур
нескольких форм. В нашем случае форма всего одна, так что можно сразу
перейти к MainFormHandleEvent, в которой, наконец-то, уже присутствует
собственно switch обработки событий!

switch (eventP->eType) 
{
case menuEvent:
return MainFormDoCommand(eventP->data.menu.itemID);

case frmOpenEvent:
frmP = FrmGetActiveForm();
FrmDrawForm(frmP);
MainFormInit(frmP);
handled = true;
break;

case frmUpdateEvent:
/*
* To do any custom drawing here, first call
* FrmDrawForm(), then do your drawing, and
* then set handled to true.
*/
break;

case ctlSelectEvent:
{
if (eventP->data.ctlSelect.controlID == MainClearTextButton)
{
/* The "Clear" button was hit.
 Clear the contents of the field.*/
FieldType * field =
(FieldType*)GetObjectPtr(MainDescriptionField);
if (field)
{
FldDelete(field, 0, 0xFFFF);
FldDrawField(field);
}
break;
}

break;
}
}

Очевидно, нам предстоит очередной переход - в процедуру
MainFormDoCommand, которая должна обрабатывать события выбора пунктов
меню. Эта процедура на текущий момент пуста. Присутствует лишь
заготовка для switch:

static Boolean MainFormDoCommand(UInt16 command)
{
Boolean handled = false;

switch (command)
{

}

return handled;
}

Честно сказать, название переменной "command" странное, лучше
подошло бы menuItemID - ведь передается в процедуру именно ID-шник
пункта меню, который был выбран.

Далее добавим в switch соответствующие строки для обработки
обращения к COM-порту, взятые, с небольшими изменениями (под данную
версию IDE), из примера в начале статьи:

	case MainMenuOpenPort:
// ничего не делаем, если порт уже открыт
if (portOpened == 0) break;

// иначе открываем порт
portOpened = SrmOpen(serPortCradleRS232Port, 9600, &portRef);

// обрабатываем ошибки
if (portOpened == 0) FrmAlert(SerialPortOKAlert);
else FrmAlert(SerialPortErrorAlert);

case MainMenuSendHello:
if (portOpened == 0) // если порт открыт успешно
{
Err error;
char prm_buffer[20];

// Подготовка строки для записи в порт
StrPrintF(prm_buffer, "Hello world!\r");

// Отсылаем. SerSendWait нужен для ожидания реакции порта
SrmSendWait(portRef);
SrmSend(portRef, prm_buffer, StrLen(prm_buffer), &error);
SrmSendWait(portRef);

// Обрабатываем ошибки
if (error == 0) FrmAlert(SerialPortOKAlert);
else FrmAlert(SerialPortErrorAlert);

}
break;

case MainMenuClosePort:
if (portOpened == 0) // если порт открыт успешно
SrmClose(portRef); // закрываем порт
break;

Для работы с COM-портом необходимо, кстати, добавить директиву
#include в начало файла TestComPort.c - #include <SerialMgr.h>.
Также в представленном фрагменте используются две глобальных переменных
- portRef и portOpened. Их необходимо объявить - в хедере TestComPort.h:

UInt32 portRef;		// Serial port descriptor
Err portOpened; // Is serial port opened ok?

и проинициализировать portOpened - в процедуре AppStart:

// Initializing values of serial port variables
portOpened = -1;

В случае, если что-то пойдет не так, очень желательно сообщить об
ошибке. Для этого в файл ресурсов вставим описания окошек удачи и
ошибки:

ALERT ID SerialPortErrorAlert
DEFAULTBUTTON 0
ERROR
BEGIN
TITLE "Serial port"
MESSAGE "Serial port operation failed!"
BUTTONS "OK"
END

ALERT ID SerialPortOKAlert
DEFAULTBUTTON 0
BEGIN
TITLE "Serial port"
MESSAGE "Serial port operation completed successfully!"
BUTTONS "OK"
END

Осталось совсем немного. Компилируем программу и запускаем ее.
Теперь необходимо указать эмулятору, что нужно обрабатывать
последовательный порт. Жмем правой кнопкой по форме эмулятора, выбираем
пункт меню Settings -> Properties, и указываем ему использовать
нужный COM-порт для вывода в этот порт данных программы.

Выбор используемого последовательного порта в эмуляторе

Тестируем! Если в ответ на все Ваши действия программа выдает
"Serial port operation completed successfully!" - значит все прекрасно,
компилируйте проект в Release, и закидывайте получившийся prc-шник в
КПК. Не снимая КПК с крэдла, запустите программу, и попробуйте отослать
в последовательный порт КПК сообщение "Hello World!", а на компьютере -
получить это сообщение, используя HyperTerminal (не забудьте HotSync
выключить, а то он не даст HyperTerminal'у подключиться к данному
порту), или какой-нибудь другой монитор COM-порта.

Создание подключения в HyperTerminal

Вот настройки порта для HyperTerminal, они должны соответствовать
настройкам порта, заданным программно (помните, при открытии порта с
помощью SmrOpen мы указывали параметр 9600? Это как раз скорость
подключения):

Настройки COM-порта в HyperTerminal

Запускаем, открываем порт, посылаем строку... Ура!

Результат

Замечу, в отладке мне здорово помог дебаггер в сочетании с online-декодером кодов ошибок.
Кстати, если программа перестает запускаться, начинает выдавать
эксепшены или ошибки - сделайте резет на Emulator-е, если не поможет -
перезагрузите IDE, и только потом начинайте копать дальше. Тактика себя
оправдывает :) Такие дела!

Скачать TestComPort.prc