В этой статье мы рассмотрим пример асинхронного приема данных из СОМ порта средствами Delphi. То-есть опрос порта будет производиться непрерывно, до тех пор пока мы его не остановим, а прочитанную информацию она будет записывать в Memo. Чтобы процедура опроса не «подвешивала» наше приложение, мы организуем прослушивание порта в отдельном потоке. Поэтому прежде чем Вам начать практиковаться по настоящей статье, автор предполагает что читатель уже знаком с темой потоков в Delphi. Пример конечно примитивен, поскольку демонстрирует лишь минимум функций, которые можно реализовать для связи по трем проводкам (GND, RX и TX), но эта простота вовсе не снижает интереса к нашей с Вами теме. По скольку дает «живое» представление о принципе асинхронного чтения, да еще и на «сквозном» примере.
Исходник: скачать
Приступим:
1 2 3 4 5 6 7 8 9 10 11 12 |
var Form1:TForm1; Adr:PWideChar; //Переменная номера COM порта; W:WideString; //Промежуточная переменная; ComFile:THandle; //Хендл ком порта; Dcb:TDCB; //Структура настроек порта; ComStat:TComStat; //Переменная состояния порта; Timeouts:TCommTimeouts; //Переменная таймаутов; OverRead:TOverlapped; Buffer:array [0..255] of AnsiChar; //Массив данных AnsiChar; Btr, Temp, Mask, Signal:DWORD; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
procedure TForm1.Button1Click(Sender: TObject);{Сканируем порты, найденные помещаем в listBox;} Var i:Integer; begin for i:=0 to 10 do begin ComFile:=CreateFile(Pchar('COM'+intToStr(i+1)), Generic_Read or Generic_Write, 0, nil, open_existing, file_flag_overlapped,0); if ComFile<>invalid_handle_value then begin Listbox1.Items.Add('COM'+ IntToStr(i+1)); CloseHandle(ComFile); end; Button1.Enabled:=False;//Деактивируем клавишу сканирования; end; end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
procedure TForm1.ListBox1DblClick(Sender: TObject); begin W:=Form1.ListBox1.Items.Strings[form1.ListBox1.ItemIndex]; {Загружаем выбранную запись в переменную} Adr:=PWideChar(W); {Преобразуем ее в тип годный для использования в функции открытия порта (WideChar);} ComFile:=CreateFile(Adr, Generic_Read+Generic_Write, 0, nil, Open_Existing, File_Flag_Overlapped, 0); {Открываем порт для асинхронной работы;} if ComFile=Invalid_Handle_Value then begin ShowMessage('Не удалось открыть порт'); {Если не удается, выводим сообщение об ошибке;} exit; end; 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; |
Что мы тут сделали?
Buffer:=»; //Очищаем переменную буфера;
1 2 3 |
public { Public declarations } end; |
Объявим наш поток:
1 2 3 4 5 6 |
MyThread=class(Tthread) private{ private declarations } protected procedure execute; override; // Главная процедура потока; procedure OutToMemo; //Процедура вывода в Мемо содержимого переменной буфера; end; |
1 |
MyThr:MyThread; //Переменная потока чтения; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
procedure MyThread.execute; begin OverRead.Hevent:=CreateEvent(Nil, True, True, Nil); //Сигнальный объект событие для ассинхронных операций; While not MyThr.Terminated do //Пока поток не остановлен; begin WaitCommEvent(ComFile, Mask, @OverRead); //Ожидаем события (поступление байта); Signal:=WaitForSingleObject(OverRead.hEvent, Infinite); {Приостанавливаем поток до тех пор пока байт не поступит;} if (Signal=Wait_Object_0) then //Если байт поступил; begin if GetOverlappedResult(ComFile, OverRead, Temp, true) then {Проверяем успешность завершения операции;} begin if ((Mask and EV_RXchar)<>0) then //Если маска соответствует, begin ClearCommError(ComFile, Temp, @ComStat); //Заполняем структуру ComStat; Btr:=ComStat.CbInQue; //Получаем из структуры количество байт; If Btr.Size<>0 then //Если байты присутствуют, begin ReadFile(ComFile, Buffer, SizeOf(Buffer), Temp, @OverRead); //Читаем порт; Synchronize(OutToMemo); {Делаем синхронный вызов процедуры загрузки буфера в Memo;} end; end; end end; end; CloseHandle(OverRead.Hevent); end; |
1 2 3 4 5 |
procedure MyThread.OutToMemo; //Процедура вывода в Memo; begin Form1.Memo1.Lines.Text:=Form1.Memo1.Lines.Text+(String(buffer)); {Загружаем в Memo содержимое буфера;} Buffer:=''; //Очищаем переменную буфера; end; |
1 2 3 4 5 6 7 |
procedure TForm1.Button3Click(Sender: TObject); //Запуск потока чтения; begin MyThr:=MyThread.Create(false); //Создаем поток чтения; MyThr.FreeOnTerminate:=true; //Запускаем поток чтения; MyThr.Priority:=tpNormal; //Устанавливаем приоритет; Button3.Enabled:=False; //Делаем клавишу чтения неактивной; end; |
1 2 3 4 5 6 7 8 9 10 11 |
procedure TForm1.Button2Click(Sender: TObject); {Очищаем listBox, Прерываем поток, Закрываем соединение;} begin if MyThr <> nil then //Если поток чтения запущен, MyThr.Terminate; //Тогда уничтожаем его; ListBox1.Clear; //Очищаем содержимое ListBox1; CloseHandle(ComFile); //Закрываем порт; Button1.Enabled:=True; //Делаем активной кнопку "Сканировать"; Button2.Enabled:=False; //Деактивируем кнопку "Разъединить"; Button3.Enabled:=False; //Деактивируем кнопку "Начать чтение"; ListBox1.Enabled:=True; //ListBox1 делаем активным; end; |
1 2 3 4 5 6 |
procedure TForm1.Button4Click(Sender: TObject); {Очистка Memo, буферов приема, передачи и очередей.} begin PurgeComm(ComFile, Purge_TXabort or Purge_RXabort or Purge_TXclear or Purge_RXclear); Buffer:=''; Memo1.Lines.Clear; end; |
1 2 3 4 5 6 |
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 (скорость, количество бит информации, контроль четности, количество стоповых бит) такие же как у нашей программки:
или символы,