- •Методичні вказівки
- •Лабораторна робота №1 Передача та прийом даних через com-порт.
- •1.1. Мета роботи
- •1.2. Теоретичні відомості
- •1.3. Програма роботи
- •1.4. Обладнання та програмне забезпечення
- •1.5. Порядок виконання роботи
- •1.6. Контрольні запитання.
- •Лабораторна робота №2 Реалізація потоку та синхронізація даних, настроювання пріоритетів завдань на Builder (Delphi)
- •2.1. Мета роботи
- •2.2. Теоретичні відомості
- •2.3. Програма роботи
- •2.4. Обладнання та програмне забезпечення
- •2.5. Порядок виконання роботи і опрацювання результатів Варіант 1 (Порядок виконання лабораторної роботи
- •Варіант 2 (Порядок виконання лабораторної роботи на мові програмування Delphi)
- •2.5. Контрольні запитання.
- •Література:
Міністерство освіти і науки України
Національний університет водного господарства та природокористування
Кафедра автоматизації, електротехнічних та комп’ютерно-інтегрованих технологій
04-03-
Методичні вказівки
до виконання лабораторних робіт №1-2
з навчальної дисципліни
“Технології об’єктно-орієнтованого та web програмування“
студентам за напрямом підготовки 6.050202
«Автоматизація та комп’ютерно-інтегровані технології»
денної та заочної форм навчання
Рекомендовано методичною комісією
за напрямом підготовки 6.050202 «Автоматизація та комп’ютерно-інтегровані технології»
Протокол № 1
від “31” серпня 2015 р.
Рівне – 2015
Методичні вказівки до виконання лабораторних робіт №1-2 з навчальної дисципліни «Технології об’єктно-орієнтованого та web програмування» студентам за напрямом підготовки 6.050202 «Автоматизація та комп’ютерно-інтегровані технології» денної та заочної форм навчання / А.П.Сафоник - Рівне: НУВГП, 2015. – 56 с.
Упорядник: Сафоник А.П., к.т.н., доц., доцент кафедри автоматизації, електротехнічних та комп’ютерно-інтегрованих технологій
Відповідальний за випуск: Древецький В.В., д.т.н., професор, зав. кафедри автоматизації, електротехнічних та комп’ютерно-інтегрованих технологій
Зміст:
Лабораторна робота №1……………………………………………….3
Лабораторна робота №2……………………………………………...29
© Сафоник А.П., 2015
© НУВГП, 2015
Лабораторна робота №1 Передача та прийом даних через com-порт.
1.1. Мета роботи
Ознайомитись з принципом передачі та прийому даних через com-порт. Навчитися програмувати com-порт.
1.2. Теоретичні відомості
Com-port (communication port) — Один із найперших; двонаправлений послідовний інтерфейс, призначений для обміну байтовою інформацією; найстарший і найрозповсюдженіший.з послідовних портів ПК (рис. 1). «Послідовний» означає те, що дані в такому інтерфейсі передаються по одному провіднику. Послідовні інтерфейси можна розділити на два основні різновиди — синхронні й асинхронні. Передача інформації на фізичному рівні — це зміна електричних сигналів. І коли ми передаємо послідовність одиничних або нульових бітів, фізично цей процес представляється у виді електричного імпульсу (рис. 2). Причому, у залежності від швидкості передачі, в імпульсах однакової тривалості може бути різна кількість одиничок. Отож, для одержання інформації з таких імпульсів використовують синхронізацію. Виходить, що паралельно з інформаційним потоком генерується послідовність імпульсів, що вказують, у який момент часу необхідно знімати інформацію. Ці імпульси і визначають швидкість обміну, адже якщо за одиницю часу подати більше синхроімпульсів, виходить, більше інформаційних даних виділиться з потоку.
Рис. 1. Com-port (communication port)
Рис. 2. Передача інформації на фізичному рівні
Якщо ці синхроімпульси передаються від одного пристрою іншому, то така передача називається синхронною. Асинхронної ж вважається така передача, коли з фіксованою швидкістю пересилається тільки інформація, а приймач і передавач синхронізують процес обміну даними самостійно. Різниця між імпульсами, по яких синхронізується передача, і імпульсами, що синхронізують прийом, не повинний перевищувати 5% від їхньої частоти. Так що навряд чи не основною проблемою для асинхронних інтерфейсів є одночасність запуску синхронізуючих генераторів. Для Сом-порту стандартними є наступні швидкості: 50, 75, 110, 150, 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 біт/с. Максимальна довжина кабелю-з'єднувача — 15 метрів. Є спеціальні кабелі, що дозволяють збільшити довжину з'єднання до 150 метрів. А якщо використовувати пристрою, називані «репітерами», та відстань можна ще додатково збільшити.
Комп'ютерний Сом-порт працює по стандарті RS-232C, що визначає електричні рівні сигналів і протокол обміну. Порт містить дві лінії для обміну інформацією (прийом і передача), і 9 ліній для керування обміном. Якщо для керування обміном задіяти ці лінії, то обмін буде називатися «апаратним» (протокол RTS/CTS). Однак обмін інформацією можна організувати, використовуючи тільки лінії прийому і передачі, тоді він буде називатися «програмним» (протокол XON/XOFF). У такому режимі посилається символ, що сигналізує про початок передачі, називається він XON, закінчення передачі сигналізується символом XOFF.
Тепер розберемося, як з ланцюжка переданих біт виділяються байти. Початок байта сигналізує старт-біт, що має завжди визначене значення — 0, закінчення — стоп-біт.
Апаратною основою Сома-порту є мікросхема UART (Universal Asynchronous Receiver/Transmitter — універсальний асинхронний прийомопередавач), що з моменту своєї появи пройшла тривалий процес еволюції (таблиця 1).
Таблиця 1
Якщо ви подивитесь у вікно настроювання послідовного порту, то побачите, що список швидкостей явно не обмежується 115.2 Кбіт/с. Це зв'язано з тим, що крім стандартних, можна використовувати ще і так називані високошвидкісні Сом-порти — Enhanced Serial Ports (ESP) і Super High Speed Serial Ports. Це варіанти, що базуються на мікросхемах 16550AF, 16650, 16750. Вони забезпечують обмін на швидкості до 921.6 Кбіт/с. У принципі, усі високошвидкісні модеми xDSL містять у своєму складі таку мікросхему, що забезпечує зв'язок на 230–460 Кбіт/с.
Тепер розглянемо настройки сом-порту. Відкривши вікно властивостей порту і вибравши вкладку Настроювання, побачимо декілька опцій (рис. 3).
Рис. 3. Вікно властивостей порту СОМ1.
Ну, зі швидкістю, думаю, усім зрозуміло — у цьому пункті вибираємо швидкість обміну між пристроєм і ПК.
Біти даних — скільки біт передавати за один раз (між старт-бітом і стоп-бітом).
Парність — вибір способу контролю парності. Контроль парності — спосіб перевірки прийнятого числа на помилковість. При передачі до числа додається ще один біт, що доповнює кількість одиниць у числі до парного або непарного (це вже як обрано в режимі передачі). Цей біт стає молодшим розрядом переданого числа і приймає значення 1, якщо в нас непарне число одиниць, і 0, якщо парне. При перевірці на парність, у випадку якщо ми прийняли непарне число одиниць (при перевірці на непарність — навпаки), порт передає пристроєві інформацію про помилку і просить повторити передачу.
Стопові біти — кількість стоп-біт, необхідних для правильного розпізнавання кінця байта.
Керування потоком — вибір режиму керування потоком (апаратного або програмного). У режимі програмного керування, при визначенні помилки, потрібно якийсь час, щоб відправити сигнал XOFF і призупинити прийом, але за цей час може відбутися передача декількох байт, що будуть загублені (у випадку відсутності буфера прийнятих даних).
У пункті Додатково можна вибрати обсяг буферів FIFO або відключити їх узагалі (що не рекомендується).
Для комунікаційних портів стандартні перераховані нижче ресурси. Діапазон введення-виведення — 3F8-3FFh для COM1, 2F8-2FFh — для COM2, 3E8-3Efh — для COM3, 2E8-2Efh — для COM4. З перериваннями небагато складніше — для СОМ1 (3) використовується IRQ4, для СОМ2 (4) — IRQ3.
Два ПК можна з'єднати не за допомогою мережної карти, або скориставшись модемним кабелем. Причому, нуль-модемний кабель можна зробити, маючи три провідника і з'єднавши лінії: прийомо-передавача і землю (у цьому випадку використовується програмний протокол керування потоком даних) (Рис. 4а). Ну а для повноти картини розпаювання повного нуль-модемного кабелю (для апаратного протоколу керування потоком даних) (Рис. 4б).
Рис. 4. Схема з’єднання за допомогою модемного кабелю.
Дуже часто програмістові приходиться керувати за допомогою комп'ютера керувати за допомогою комп’ютера зовнішнім пристроєм, або просто аналізувати стан цього пристрою. Порти введення/виведення — найпоширеніший спосіб сполучення комп'ютера і зовнішнього пристрою.
Для визначення встановлених на комп’ютері СОМ портів можна використати процедуру:
Лістинг на Delphi:
uses Registry;
procedure (Sender: TObject); var reg : TRegistry; ts : TStrings; i : integer; begin reg := TRegistry.Create; reg.RootKey := HKEY_LOCAL_MACHINE; reg.OpenKey('hardware\devicemap\serialcomm', false); ts := TStringList.Create; reg.GetValueNames(ts); for i := 0 to ts.Count -1 do begin Memo1.Lines.Add(reg.ReadString(ts.Strings[i])); end; ts.Free; reg.CloseKey; reg.free; end;
Лістинг на Builder:
#include <registry.hpp>
TRegistry *Reg;
TStrings *Ts;
Reg= new TRegistry;
Ts= new TStringList;
Reg->RootKey=HKEY_LOCAL_MACHINE;
AnsiString key="HARDWARE\\DEVICEMAP\\SERIALCOMM";
Reg->OpenKey(key,false);
Reg->GetValueNames(Ts);
for (int i=0;i<=(Ts->Count-1);i++)
{
ComboBox1->Items->Add(Reg->ReadString(Ts->Strings[i]));
}
Ts->Free();
Reg->CloseKey();
Reg->Free();
З портами 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.
З усіх параметрів функції 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('ПОМИЛКА ВІДКРИТТЯ ПОРТА');
Лістинг на Builder:
HANDLE hPort;
…
hPort = CreateFile(“COM1”,GENERIC_READ|GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if ((hPort==INVALID_HANDLE_VALUE)||(hPort==NULL))
{
ShowMessage("ПОМИЛКА ВІДКРИТТЯ ПОРТА");
};
Функція повертає дескриптор порту (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 (Builder) це виглядає так:
Лістинг на Delphi:
var
Dcb: TDcb;
…
if not GetCommState(hPort, Dcb) then
raise Exception.Create('ПОМИЛКА ОТРИМАННЯ ПАРАМЕТРІВ ПОРТУ');
Dcb.BaudRate := CBR_9600;
Dcb.Parity := NOPARITY;
Dcb.ByteSize := 8;
Dcb.StopBits := ONESTOPBIT;
if not SetCommState(hPort, Dcb) then
raise Exception.Create('ПОМИЛКА ВСТАНОВЛЕННЯ ПАРАМЕТРІВ ПОРТУ');
Лістинг на Builder:
DCB dcb;
…
if (!GetCommState(hPort, &dcb))
{
ShowMessage("ПОМИЛКА ОТРИМАННЯ ПАРАМЕТРІВ ПОРТУ");
};
dcb.BaudRate=CBR_9600;
dcb.ByteSize=8;
dcb.Parity= NOPARITY;
dcb.StopBits=ONESTOPBIT;
if (!SetCommState(hPort,&dcb))
{
ShowMessage("ПОМИЛКА ВСТАНОВЛЕННЯ ПАРАМЕТРІВ ПОРТУ");
};
Ще одна операція, що нам знадобиться відразу після відкриття порту — його скидання.
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('ПОМИЛКА ОЧИЩЕННЯ ПОРТА');
Лістинг на Builder:
if (!PurgeComm(hPort, PURGE_TXCLEAR| PURGE_RXCLEAR))
{
ShowMessage("ПОМИЛКА ОЧИЩЕННЯ ПОРТА");
};
На цьому підготовча фаза закінчується, і можна приступати безпосередньо до прийому/передачі даних. Прийом даних у нас буде відбуватися по подійній схемі; програма буде очікувати прийом одного або декількох символів (байт). Для того щоб перевести порт в цей режим необхідно викликати функцію SetCommMask() із прапором EV_RXCHAR:
Лістинг на Delphi:
if not SetCommMask(hPort, EV_RXCHAR) then
raise Exception.Create('ПОМИЛКА ВСТАНОВЛЕННЯ МАСКИ ПОРТУ');
Лістинг на Builder:
if (!SetCommMask (hPort, EV_RXCHAR))
{
ShowMessage("ПОМИЛКА ВСТАНОВЛЕННЯ МАСКИ ПОРТУ");
};
Прийом і передача даних виконується функціями ReadFile() і WriteFile(), тобто тобто функціями, що використовуються для роботи з дисковими файлами, що використовуються для роботи з дисковими файлами. От їхній опис:
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumOfBytesToRead,
LPDWORD lpNumOfBytesRead,
LPOVERLAPPED lpOverlapped
);
BOOL WriteFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumOfBytesToWrite,
LPDWORD lpNumOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
hFile — дескриптор відкритого порту.
lpBuffer — адреса буфера.
nNumberOfBytesToRead/nNumberOfBytesToWrite — число очікуваних до прийому або призначених для передачі байт.
lpNumberOfBytesRead/lpNumberOfBytesWritten — число фактично прийнятих або переданих байт.
lpOverlapped — адреса структури OVERLAPPED, використовуваної для асинхронних операцій.
Передача даних є досить швидкою операцією, тому як правило її виконують з головного потоку додатка. Це виглядає так:
Лістинг на Delphi:
var
dwWrite: DWORD;
strWrite: string;
OverWrite: TOverlapped;
WriteBytes: array[0..$FF] of Byte;
i: Integer;
begin
strWrite := edit1.Text;
for i := 0 to length(strWrite) do
begin
WriteBytes[i] := Ord(strWrite[i+1]);
end;
OverWrite.hEvent := CreateEvent(nil, True, False, nil);
if OverWrite.hEvent = Null then
raise Exception.Create('Помилка при спробі запису порт');
if (not WriteFile(hport, WriteBytes, length(strwrite),
dwWrite, @OverWrite))
and (GetLastError <> ERROR_IO_PENDING) then
raise Exception.Create('Помилка передачі даних');
//WriteBytes := nil;
end;
Лістинг на Builder:
{
BYTE chWriteBuff[1024];
OVERLAPPED Sync;
DWORD dwWrite;
AnsiString str_buf;
int i;
str_buf = Edit1->Text;//.c_str()-48;
for(i=0;i<str_buf.Length();i++)
{
chWriteBuff[i]=char(str_buf[i+1]);
}
Sync.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if (Sync.hEvent==NULL)
{
ShowMessage("ПОМИЛКА СТВОРЕННЯ ПОДІЇ");
};
if (!WriteFile(hPort, &chWriteBuff,str_buf.Length(), &dwWrite, &Sync))
{
if (GetLastError()!=ERROR_IO_PENDING)
{
ShowMessage("ПОМИЛКА ЗАПИСУ ДАНИХ");
}
}
}
У даному прикладі функція WriteFile() виконує асинхронний запис масиву байтів chWriteBuff у порт. Вона відразу повертає керування, і запис у порт відбувається паралельно з виконанням основного коду потоку. Якщо результат WriteFile() дорівнює False, то це значить, що на момент повернення керування передача масиву байтів ще не закінчилася. Тому код помилки виконання WriteFile() у даному випадку повинен дорівнювати ERROR_IO_PENDING. Змінна OverWrite — overlapped-структура, необхідна для асинхронних операцій.
От так виглядає головна процедура, що очікує появу декількох символів і зчитує їх:
Лістинг на Delphi:
var
ComStat: TComStat;
dwError: DWORD;
OverRead: TOverlapped;
MyBuff:Array Of dword;
Buf: array[0..$FF] of Byte;
dwRead: DWORD;
strWrite: string;
i: byte;
begin
OverRead.hEvent := CreateEvent(nil, True, False, nil);
if OverRead.hEvent = Null then
raise Exception.Create('Помилка при спробі зчитування порту');
if not ClearCommError(hport, dwError, @ComStat) then
raise Exception.Create('Помилка ініціалазації порту');
dwRead := ComStat.cbInQue;
if dwRead > 0 then
begin
if not ReadFile(hport, Buf, dwRead, dwRead, @OverRead) then
raise Exception.Create('Помилка читання порту');
// В Buf находятся прочитанные байты
// Далее идет обработка принятых байтов
end;
for i :=0 to dwRead-1 do
begin
form1.memo1.Text := form1.memo1.Text + inttostr(buf[i]);//chr(buf[i]);
end;
form1.memo1.Lines.Add('');
end;
Лістинг на Builder:
{
COMSTAT CommStat;
DWORD dwError;
DWORD dwRead;
BYTE *Buf;
OVERLAPPED OverRead;
int i;
AnsiString STR;
OverRead.hEvent = CreateEvent(NULL, True, False, NULL);
if (OverRead.hEvent == Null)
{ShowMessage("Помилка при спробі зчитування порту");}
if(!ClearCommError(hPort,&dwError,&CommStat)) {
MessageBox(NULL,"Помилка очищення порта","Помилка",MB_ICONERROR);}
dwRead = CommStat.cbInQue;
if (dwRead > 0)
{
Buf = new BYTE[dwRead];
if (!ReadFile(hPort,Buf,dwRead,&dwRead,&OverRead))
{
MessageBox(NULL,"Помилка читання порта","Помилка",MB_ICONERROR);
}
// В chReadBuff знаходяться прочитані байти
// Далі йде обробка отриманих байтів
for(i=0; i<dwRead; ++i)
{
STR=STR+char(Buf[i]);/* IntToStr(short(Buf[i])) + " "; */
}
Form1->Memo1->Lines->Add(STR);
delete Buf;
}
}
Зчитування символів виконується функцією ReadFile().
