В этой статье мы рассмотрим пример асинхронного приема данных из СОМ порта средствами Delphi. То-есть опрос порта будет производиться непрерывно, до тех пор пока мы его не остановим, а прочитанную информацию она будет записывать в Memo. Чтобы процедура опроса не "подвешивала" наше приложение, мы организуем прослушивание порта в отдельном потоке. Поэтому прежде чем Вам начать практиковаться по настоящей статье, автор предполагает что читатель уже знаком с темой потоков в Delphi. Пример конечно примитивен, поскольку демонстрирует лишь минимум функций, которые можно реализовать для связи по трем проводкам (GND, RX и TX), но эта простота вовсе не снижает интереса к нашей с Вами теме. По скольку дает "живое" представление о принципе асинхронного чтения, да еще и на "сквозном" примере.
И так приступим:
Form1:TForm1;
Adr:PW >//Переменная номера COM порта;
W:W >//Промежуточная переменная;
ComFile:THandle; //Хендл ком порта;
Dcb:TDCB; //Структура настроек порта;
ComStat:TComStat; //Переменная состояния порта;
Timeouts:TCommTimeouts; //Переменная таймаутов;
OverRead:TOverlapped;
Buffer:array [0..255] of AnsiChar; //Массив данных AnsiChar;
Btr, Temp, Mask, Signal:DWORD;
PurgeComm(ComFile, Purge_TXabort or Purge_RXabort or Purge_TXclear or Purge_RXclear); //Очищаем буферы приема и передачи и очередей чтения/записи;
GetCommState(ComFile, DCB); //Настраиваем DCB настройки порта;
with DCB do
begin
BaudRate:=9600;
ByteSize:=8;
Parity:=NoParity;
StopBits:=OneStopBit;
end;
if not SetCommState(ComFile, DCB) then
begin
ShowMessage(‘Порт не настроен’); //Если не удается выводим сообщение об ошибке;
CloseHandle(ComFile);
exit;
end;
if ComFile <> INVALID_HANDLE_VALUE then
begin
GetCommTimeouts(ComFile, Timeouts); < Чтение текущих таймаутов и настройка параметров структуры CommTimeouts >
Timeouts.ReadIntervalTimeout:=MAXDWORD; //Таймаут между двумя символами;
Timeouts.ReadTotalTimeoutMultiplier:=0; //Общий таймаут операции чтения;
Timeouts.ReadTotalTimeoutConstant:=0; //Константа для общего таймаута операции чтения;
Timeouts.WriteTotalTimeoutMultiplier:=0; //Общий таймаут операции записи;
Timeouts.WriteTotalTimeoutConstant:=0; //Константа для общего таймаута операции записи;
SetCommTimeouts(ComFile, Timeouts); //Установка таймаутов;
end;
SetupComm(ComFile, 4096, 4096); //Настройка буферов;
if not SetupComm(ComFile, 4096, 4096) then //Ошибка настройки буферов;
begin
ShowMessage(‘ Ошибка настройки буферов ‘);
CloseHandle(ComFile);
exit;
end;
SetCommMask(ComFile, EV_RXchar); <Устанавливаем маску для срабатывания по событию — "Прием байта в порт">
ListBox1.Enabled:=False; // Деактивируем listbox;
Button2.Enabled:=True; // Активируем кнопку "Разъединить";
Button3.Enabled:=True; //Деактивируем кнопку "Начать чтение";
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); //Закрытие програмы;
begin
if MyThr <> nil then //Если поток запущен;
MyThr.Terminate; //Останавливаем его;
CloseHandle(ComFile); //Закрываем Порт;
end;
Ну вот как бы и все, теперь наш проект готов к запуску, но прежде настроим аппаратную часть.
Для этого нам потребуется любая программка — терминал для отправки данных, 2 COM порта на материнке, если таковые отсутствуют, тогда 2 конвертера USB>COM. Я например использую 2 вот таких переходничка и соединяю их тремя проводками по следующей схеме:
RXD одного конвертера соединяем с TXD другого и наоборот, плюс массу одного конвертера соединяем с массой другого. Если Вы будете использовать штатные COM порты на матплате то нужно будет соединить их нульмодемным кабелем.
Один переходничок у меня в системе определился как COM3, второй как COM4. Запускаем наше приложение, подключаемся к порту COM3 и нажимаем нашу кнопку начала чтения, на этом "прослушивание" порта началось. Теперь запускаем программку терминал, для примера я использую COM Port Toolkit 4.0 После запуска терминал нужно настроить — выбрать порт для подключения (в моем случае это COM4) и установить настройки DСB (скорость, количество бит информации, контроль четности, количество стоповых бит) такие же как у нашей программки:
Далее заходим в пункт меню "Сообщение" — "Отправить" и через его интерфейс пробуем отправлять различный текст, можно из файла, можно просто какие нибудь одиночные слова
или символы,
в общем если Вы все выполняли последовательно и внимательно, то в Mемо нашей программы будет отображаться все, что Вы будете отправлять из терминала. На всякий случай прилагаю исходник нашего с Вами примера. На этом пока все, всем удачи. В следующей статье рассмотрим наверное отправку данных в порт и немного доработаем наш сегодняшний пример.
Оригинальное название статьи — "Вид на порт из окна" — прим. ред.
Вопрос «как работать с COM-портами?» стал классическим на многих конференциях по языкам программирования. Рано или поздно чуть не каждому программисту приходится работать с портами ввода/вывода. Сегодня я хочу рассказать про работу с последовательным портом из-под самой распространенной на сегодняшний день 32-разрядной операционной системы — Windows. К статье прилагается пример программы, работающей с COM-портом, написанной на Borland Delphi 7.
Статья построена по принципу «от простого к сложному». Сначала будут изложены основы работы с портами из-под Win32 с описанием необходимых функций. Затем рассмотрим применение этих функций на примере Delphi-программы. Конечным результатом будет класс, предназначенный для работы с COM-портом, и пример использующей его программы.
Очень часто программисту приходится управлять с помощью компьютера каким-либо внешним устройством, или просто анализировать состояние этого устройства. Порты ввода/вывода — самый распространенный способ сопряжения компьютера и внешнего устройства. Давным-давно уже написано множество классов, библиотек и компонент для работы с портами, поэтому можно, конечно, воспользоваться уже готовым и к тому же бесплатным решением. Именно так я и поступил лет семь назад, при этом потеряв самое главное — своевременное понимание того, как все-таки работать с портами из-под Win32. Незнание внутренних механизмов — это, во-первых, пробел в стройном ряду знаний, а во-вторых, актуальная возможность ошибок в работе программы.
С портами из-под Win32 работают так же, как и с обычными файлами, используя при этом всего несколько специфичных функций WinAPI. Однако коммуникационный порт — это не совсем обычный файл. Для него, например, нельзя выполнить позиционирование файлового указателя, или же создать порт, если таковой отсутствует. Любая работа с портом начинается с его открытия. Для этого используется файловая функция WinAPI (описания WinAPI-функций взяты из MSDN (Microsoft Developer Network), следовательно, приводятся в синтаксисе C):
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
lpFileName — указатель на строку с нулевым завершающим символом. Обычно это имя открываемого файла, но в нашем случае это должно быть название порта (COM1, COM2, …).
dwDesiredAccess — тип доступа. В нашем случае должен быть равен GENERIC_READ|GENERIC_WRITE.
dwShareMode — параметр совместного доступа. Для коммуникационных портов всегда равен 0.
lpSecurityAttributes — атрибут защиты. Для коммуникационных портов всегда равен NULL.
dwCreationDistribution — режим автосоздания. Для коммуникационных портов всегда равен OPEN_EXESTING.
dwFlagsAndAttributes — атрибут режима обработки. Для коммуникационных портов должен быть равен 0 или FILE_FLAG_OVERLAPPED.
hTemplateFile — описатель файла-шаблона. Для коммуникационных портов должен быть равен NULL.
При успешном открытии порта функция возвращает его описатель, а в случае ошибки возвращает INVALID_HANDLE_VALUE.
Сразу оговорюсь: все недостающие описания можно найти на http://msdn.microsoft.comи еще по ряду адресов, которые вам подскажет поисковый сервер.
Из всех параметров функции CreateFile() особого пояснения требует dwFlagsAndAttributes. Работа с портом может быть организована в синхронном (nonoverlapped) или асинхронном (overlapped) режимах обработки, что и задается этим флагом. При синхронном режиме (когда параметр dwFlagsAndAttributes = 0) только один поток приложения может либо читать, либо писать в порт. Помните переговорное устройство в лифте? Нажали кнопку — можем только говорить, отпустили кнопку — можем только слушать.
Синхронный режим обработки прост в реализации. Если надо записать данные в порт, то вызываем функцию записи и ожидаем, пока она не завершится. Если же надо читать данные, то вызываем функцию чтения и ждем, пока она не отработает. Для простых задач синхронный режим обработки вполне подходит, однако в мире Windows он почти всегда обречен на неудачу. Ожидание операции чтения или записи воспринимается пользователем программы как «зависание».
Асинхронный режим (когда параметр dwFlagsAndAttributes = FILE_FLAG_OVERLAPPED) позволяет производить операции чтения и записи в порт параллельно из разных потоков. В то время, пока один поток приложения принимает данные, другой поток может параллельно с первым передавать данные — как при разговоре по телефону, когда вы можете слушать и говорить одновременно. Данный режим обработки больше импонирует идее многозадачности Windows. Но за все надо платить: для реализации этого режима обработки требуется в два раза больше написанного кода, вдобавок, умения писать многопоточные программы. Какой режим выбрать — решайте сами. Но если уж разбираться в работе порта, то разбираться «по-взрослому», до конца, а потому и рассмотрим более сложный вариант — асинхронную обработку.
На практике открытие порта для асинхронного режима обработки из программы на Delphi выглядит примерно так:
hPort := CreateFile(‘COM1’, GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if hPort = INVALID_HANDLE_VALUE then raise Exception.Create(‘Error opening port’);
Функция возвращает описатель порта (hPort), который нам потом пригодится для вызова других функций работы с портом. Если в результате открытия порта описатель не получен, то возбуждается исключение с соответствующим текстом ошибки. Открыв порт, мы получаем его в свое распоряжение. Теперь с этим портом может работать только наша программа (точнее, только наш процесс). По окончании работы с портом его следует закрыть, вызвав функцию:
BOOL CloseHandle( HANDLE hObject );
В качестве единственного параметра надо передать полученный ранее описатель порта (hPort).
Хоть система при завершении выполнения программы и освобождает все выделенные ей ресурсы (в том числе и порты), хорошим тоном программирования считается собственноручное закрытие портов. Открывать/закрывать порт как будто несложно. Кроме того, нам потребуется программная настройка порта. Думаю, все видели диалог настройки последовательного порта в диспетчере устройств системы. Все эти настройки мы можем произвести программно. Для этих целей используется функция WinAPI:
BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );
hFile — описатель открытого порта.
lpDCB — указатель на структуру DCB.
Основные параметры последовательного порта описываются структурой DCB. Она содержит массу полей, каждое из которых соответствует определенному параметру настройки порта. Мы рассмотрим несколько полей, которые нам нужны:
BaudRate — скорость передачи данных. Возможно указание констант —CBR_100, CBR_300, CBR_600, CBR_1200, …, CBR_256000.
Parity — схема контроля четности. Может содержать одно из следующих значений: EVENPARITY, MARKPARITY, NOPARITY, ODDPARITY, SPACEPARITY.
ByteSize — число информационных бит в передаваемых и принимаемых байтах.
StopBits — количество стоповых бит. Может быть ONESTOPBIT, ONE5STOPBIT, TWOSTOPBIT.
Чтобы не заполнять структуру DCB вручную, ее можно заполнить информацией о текущем состоянии порта вызовом функции GetCommState(), затем изменить необходимые поля и установить настройки вызовом функции SetCommState(). Настройку порта желательно производить сразу после его открытия. На Delphi это выглядит так:
var Dcb: TDcb; … if not GetCommState(hPort, Dcb) then raise Exception.Create(‘Error setting port state’); Dcb.BaudRate := CBR_9600; Dcb.Parity := NOPARITY; Dcb.ByteSize := 8; Dcb.StopBits := ONESTOPBIT; if not SetCommState(hPort, Dcb) then raise Exception.Create(‘Error setting port state’);
Еще одна операция, которая нам понадобится сразу после открытия порта — его сброс.
BOOL PurgeComm( HANDLE hFile, DWORD dwFlags );
Вызов этой функции очищает очередь приема/передачи и завершает все находящиеся в ожидании запросы ввода/вывода.
hFile — описатель открытого порта.
dwFlags — производимые действия в виде набора флагов PURGE_TXABORT, PURGE_RXABORT, PURGE_TXCLEAR, PURGE_RXCLEAR.
Пример на Delphi:
if not PurgeComm(hPort, PURGE_TXCLEAR or PURGE_RXCLEAR) then raise Exception.Create(‘Error purging port’);
На этом подготовительная фаза заканчивается, и можно приступать непосредственно к приему/передаче данных. Прием данных у нас будет происходить по событийной схеме; программа будет ожидать прием одного или нескольких символов (байт). Для перевода порта в этот режим необходимо вызвать функцию SetCommMask() с флагом EV_RXCHAR:
if not SetCommMask(hPort, EV_RXCHAR) then raise Exception.Create(‘Error setting port mask’);
Прием и передача данных выполняется функциями ReadFile() и WriteFile(), то есть теми же самыми функциями, которые используются для работы с дисковыми файлами. Вот их описание:
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );
hFile — описатель открытого порта.
lpBuffer — адрес буфера.
nNumberOfBytesToRead/nNumberOfBytesToWrite — число ожидаемых к приему или предназначенных для передачи байт.
lpNumberOfBytesRead/lpNumberOfBytesWritten — число фактически принятых или переданных байт.
lpOverlapped — адрес структуры OVERLAPPED, используемой для асинхронных операций.
Передача данных является довольно быстрой операцией, поэтому как правило ее выполняют из главного потока приложения. На Delphi это выглядит так:
var dwWrite: DWORD; OverWrite: TOverlapped; WriteBytes: array of Byte; … begin OverWrite.hEvent := CreateEvent(nil, True, False, nil); if OverWrite.hEvent = Null then raise Exception.Create(‘Error creating write event’); … if (not WriteFile(hPort, WriteBytes, SizeOf(WriteBytes), dwWrite, @OverWrite)) and (GetLastError <> ERROR_IO_PENDING) then raise Exception.Create(‘Error writing port’); end;
В данном примере функция WriteFile() выполняет асинхронную запись массива байтов WriteBytes в порт. Она сразу возвращает управление, и запись в порт происходит параллельно с выполнением основного кода потока. Если результат WriteFile() равен False, то это значит, что на момент возврата управления передача массива байтов еще не закончилась. Поэтому код ошибки выполнения WriteFile() в данном случае должен быть равен ERROR_IO_PENDING. Переменная OverWrite — overlapped-структура, необходимая для асинхронных операций.
В принципе, вас не должно волновать, когда закончится передача массива байтов. Зато момент приема одного или нескольких символов действительно важен. Поэтому его можно разбить на две части: инициирование приема и определение момента приема с последующим чтением символов. Поскольку при этом приходится считаться с фактором ожидания приема символа, рекомендуется функции приема данных вынести в отдельный поток. Передавать данные можно и из основного потока приложения, поскольку это происходит довольно быстро. А вот событие приема символа будем ожидать в отдельном потоке.
Рассмотрение работы с потоками в Windows, в частности того, как это реализовано в Delphi, выходит за рамки данной статьи. Предполагаю, что читатель встречался или по крайней мере знаком с этим. Скажу лишь, что у любого потока есть главная функция, которая начинает выполняться после его создания. В Delphi для потоков существует класс TThread, а его главная процедура называется TThread.Execute().
Вот так выглядит главная процедура отдельного потока, которая ожидает появление одного или нескольких символов и считывает их:
procedure TReadThread.Execute; var ComStat: TComStat; dwMask, dwError: DWORD; OverRead: TOverlapped; Buf: array[0..$FF] of Byte; dwRead: DWORD; begin OverRead.hEvent := CreateEvent(nil, True, False, nil); if OverRead.hEvent = Null then raise Exception.Create(‘Error creating read event’); FreeOnTerminate := True; while not Terminated do begin if not WaitCommEvent(hPort, dwMask, @OverRead) then begin if GetLastError = ERROR_IO_PENDING then WaitForSingleObject(OverRead.hEvent, INFINITE) else raise Exception.Create(‘Error waiting port event’); end; if not ClearCommError(hPort, dwError, @ComStat) then raise Exception.Create(‘Error clearing port’); dwRead := ComStat.cbInQue; if dwRead > 0 then begin if not ReadFile(hPort, Buf, dwRead, dwRead, @OverRead) then raise Exception.Create(‘Error reading port’); // В Buf находятся прочитанные байты // Далее идет обработка принятых байтов end; end;
В приведенном примере в потоке крутится цикл, тем самым инициируется ожидание события порта вызовом функции WaitCommEvent(), ожидание же самого этого события задается функцией WaitForSingleObject(). Для определения количества принятых символов используется функция ClearCommError(). Когда количество принятых символов (dwRead) известно, непосредственное чтение символов выполняется функцией ReadFile().
Используя вышеописанные выкладки, я написал на Borland Delphi 7 класс TComPort для работы с COM-портами. К классу прилагается пример приложения, использующий его. Для проверки работоспособности программы я просто соединил нуль-модемным кабелем два COM-порта на своем компьютере и запустил два экземпляра программы для каждого порта. Данные передаются через один порт и одновременно принимаются через другой. Скриншот программы прилагается.
Для передачи и приема данных предусмотрены отдельные окна. Формат передаваемых данных — строка. Принимаемые данные представляются в виде массива байт.
Практически любому компьютеру приходится связываться с внешними устройствами. Практически любому программисту приходилось (приходится, придется) ваять программы под эти устройства. Огромное количество внешних устройств общаются с компьютером посредством RS-232. Отсюда и огромное количество вопросов от начинающих разработчиков. Количество вопросов на тему "как мне записать/принять данные с com-порта" на форумах по программированию не убывает, а скорее растет. Именно количество этих вопросов побудило меня к написанию статьи. Хотелось бы подчеркнуть, что статья предназначена именно для новичков в этом вопросе, и соответственно я старался упростить изложение материала.
Итак, у нас имеется в компьютере com-порт. И хорошо бы по нему передавать и принимать байты. В общем случае можно поставить задачу так: необходимо отправлять нужную строку и необходимо реагировать на приход строки в порт от внешнего устройства.
Пример содержит интерфейсную форму, а так же модуль для работы с портом. Модуль взят из реально работающей программы, но (опять же для упрощения), из него были выкинуты всевозможного вида проверки, и я постарался оставить один костяк. В реальной программе надо ставить кучу проверок (в идеале после каждой API-функции), очищать буфер порта и задавать его емкость и т.д. и т.п. Но для начала (например, чтоб послать что-нибудь в модем) данного материала хватит. Отличительная особенность данного примера — организация работы приема данных из порта по событию, которого программа ждет в параллельном потоке.
Модуль для работы с портом содержит четыре процедуры:
- PortInit — инициализация работы и запуск потока приема данных с порта.
- KillComm — собственно убийство потока приема и самого порта.
- WriteComm — запись в порт.
Процедура PortInit.
Для начала необходимо создать порт и получить его идентификационный номер (хотя, строго говоря, создать файл и получить хэндл). Делается это одной функцией CreateFile:
CommHandle — хэндл, то есть номер созданного безобразия. Тип — THandle. В дальнейшем работаем в программе только с ней.
Первый параметр ‘COM1’ — собственно имя порта. Его соответственно можно менять (обратите внимание это не тип string, а тип PChar). Остальные установки достаточно стандартны и менять их часто не придется. Хотя конечно можно (и нужно) и по хэлпу полазить и дать волю любопытству.
Теперь настраиваем параметры порта, а так же маску. Маска — это описание такого события, которое порт будет ждать, и по которому будет вестись обработка событий. В данном примере рассматриваем частный случай — приход символа "возврат каретки". Но потрудитесь посмотреть описание функции SetCommMask (клавиша F1 находиться сверху слева клавиатуры). Очень полезно знать, по каким событиям можно еще организовать обработку.
SetCommMask(CommHandle, EV_RXFLAG); — устанавливаем маску EV_RXFLAG — "обработка по определенному символу". Иными словами, как только в порт придет необходимый символ — то программа начнет обрабатывать данное событие. При отслеживании нескольких событий они задаются через or (логическое или).
Сам символ задаем в DCB-структуре. DCB- структура — это управляющая структура Вашего порта. Ключевая штуковина. Необходимо ее заполнить. Собственно в ней определяются настройки порта.
GetCommState(CommHandle,DCB); — получаем текущее DCB.
DCB.BaudRate:=CBR_9600; — устанавливаем скорость работы.
DCB.Parity:=NOPARITY; — устанавливаем отсутствие проверки на четность
DCB.ByteSize:=8; — 8 бит в передаваемом байте.
DCB.StopBits:=OneStopBit; — одиночный стоп-бит.
DCB.EvtChar:=chr(13); — вот собственно задаем символ для SetCommMask. В данном случае — возврат каретки.
SetCommState(c > Естественно это не все параметры DCB, а только самые важные и часто используемые. Всю структуру DCB, а так же все значения параметров можно получить с помощью все той же красивой клавиши под названием F1.
Теперь займемся организацией приема информации из порта. Для того чтобы не зацикливать программу на постоянном считывании порта с одной стороны и в то же время всегда быть готовым принять информацию, запускаем процедуру чтения порта в отдельном потоке: Переменная CommThread — хэндл, но уже на поток.
ReadComm — собственно процедура, по которой и производиться обработка. "Собака" означает, что передаем не имя процедуры, а ее адрес. Thread > Теперь пристально рассмотрим процедуру ReadComm. Во-первых, это обычная процедура, входя состав модуля. Во-вторых, все ее содержимое зацикливается с помощью while true do. Но это не смертельно. Все-таки запускаем процедуру исключительно в отдельном потоке, так что беды не будет. Поток же убивается по мере необходимости.
Итак, процедура содержит цикл и в цикле ждет событие Тут, собственно, наш поток останавливается и ждет какого-либо события прописанного в SetCommMask (в процедуре PortInit). Как только событие произошло программа идет дальше
(TransMask and EV_RXFLAG)=EV_RXFLAG — этим выражением мы проверяем, а то ли событие произошло, что нам надо. Вроде то самое. Тут надо отметить, что в SetCommMask можно поставить с помощью оператора or несколько событий. Тогда в обработчике после ожидания надо соответственно предусмотреть и несколько if then. Что бы прописать реакцию на каждое событие.
Идем дальше.
ClearCommError(CommHandle,Errs,@Stat); — вопреки названию, здесь эта функция очищает не ошибки, а собственно факт прихода события. Без нее RXFLAG так и останется висеть. Можете попробовать угадать, что будет на следующем цикле приема.
Ну а потом идет собственно прием Kols := Stat.cbInQue; — берем количество байт в буфере порта
ReadFile(CommHandle,Resive,Kols,Kols,@Ovr); — считываем все в массив Resive.
Далее все полученное в Resive надо обработать. Как это делается — вопрос конкретного разработчика, удобства и конечно протокола обмена. Одно могу сказать точно — НЕ ДЕЛАЙТЕ как сделано в примере. Не надо из потока обращаться к визуальным компонентам (например, к Panel1 J). Лучшее решение — чтобы юнит по работе с портом вообще не видел главный юнит и главную форму. Вывод данных на экран или на обработку можно сделать или по таймеру или, например, послав пользовательское сообщение (message), ну а в обработчике организовать вывод принятой информации на экран.
Процедура WriteComm.
Собственно она производит запись в порт. В примере — один байт. Как послать несколько байт? Ну, уже должны догадаться :), что переменная Kolbyte — это и есть собственно количество посылаемых байт. Послать строку еще проще. Заполняете KolByte количеством символов в строке, а вместо массива Transmite используем строковый тип, только не в паскалевском формате, а в PChar (он же си-шный формат строки, он же null-terminated string). Надо отметить, что во всех функциях API используется именно этот строковый стандарт, очевидно по причине написания самой операционки на Си. Еще раз позволю себе намекнуть на использования хелпов и на этот раз отошлю на описание функции StrPcopy. Это поможет, надеюсь.
Процедура KillComm
Завязываем использования порта и все подметаем за собой. TerminateThread(CommThread,0); — "убиение" параллельного потока приема
CloseHandle(CommHandle); — "убиение" собственно файла-порта.