AP Blok 3: Rozdiel medzi revíziami
Zo stránky SensorWiki
Riadok 163: | Riadok 163: | ||
''Tip:'' Aby ste mohli meniť parametre, musíte port najprv otvoriť. | ''Tip:'' Aby ste mohli meniť parametre, musíte port najprv otvoriť. | ||
=== | === '''3.3 Vysielanie''' === | ||
'''3.3 Vysielanie''' === | |||
Zápis na port má na starosti funkcia <code>WriteFile</code>, pričom sa použijú aktuálne prenosové parametre. Funkcia uloží do premennej Pocet počet skutočne odvysielaných znakov, ktorý treba kontrolovať. V prípade zlyhania buď prenos zopakujeme, alebo skončíme s chybou. | |||
Zápis na port má na starosti funkcia WriteFile, pričom sa použijú aktuálne prenosové parametre. Funkcia uloží do premennej Pocet počet skutočne odvysielaných znakov, ktorý treba kontrolovať. V prípade zlyhania buď prenos zopakujeme, alebo skončíme s chybou. | |||
Nasledujúca ukážka samozrejme predpokladá, že port je otvorený a správne nakonfigurovaný. | Nasledujúca ukážka samozrejme predpokladá, že port je otvorený a správne nakonfigurovaný. | ||
Riadok 190: | Riadok 188: | ||
</source> | </source> | ||
Tip: | '''Úloha:''' Napíšte program, ktorý bude na zvolený port stále dokola vysielať znak 'A'. Overte funkciu prijímaním v bežnom terminálovom programe. | ||
''Tip:'' Použite napr. Terminal by Bray++, neinštaluje sa, len spustí. | |||
Riadok 199: | Riadok 199: | ||
Prijímanie znakov zabezpečí funkcia ReadFile, ktorá načítaný znak uloží do buffera Data (v našom príklade 1 byte). Zároveň vráti aj počet prijatých bytov. Táto funkcia čaká, až kým nejaký znak nepríde. Takže ak nepríde nič, program "zamrzne". Tomu sa dá vyhnúť použitím timeoutov, ktoré uvedieme ďalej. | Prijímanie znakov zabezpečí funkcia <code>ReadFile</code>, ktorá načítaný znak uloží do buffera <code>Data</code> (v našom príklade 1 byte). Zároveň vráti aj počet prijatých bytov. Táto funkcia čaká, až kým nejaký znak nepríde. Takže ak nepríde nič, program "zamrzne". Tomu sa dá vyhnúť použitím timeoutov, ktoré uvedieme ďalej. | ||
V nasledujúcej ukážke sme navyše vyčistili prijímací a vysielací zásobník (buffer) funkciou PurgeComm. | V nasledujúcej ukážke sme navyše vyčistili prijímací a vysielací zásobník (buffer) funkciou <code>PurgeComm</code>. | ||
Riadok 228: | Riadok 228: | ||
</source> | </source> | ||
Úloha: Napíšte program, ktorý bude na čakať na zvolenom porte a stále dokola zobrazovať všetky prijaté znaky. Overte funkciu vysielaním v bežnom terminálovom programe, ako je napr. Hyperterminál, alebo Terminal. | |||
'''Úloha:''' Napíšte program, ktorý bude na čakať na zvolenom porte a stále dokola zobrazovať všetky prijaté znaky. Overte funkciu vysielaním v bežnom terminálovom programe, ako je napr. Hyperterminál, alebo Terminal. | |||
=== '''3. 5. Timeouty''' | === '''3. 5. Timeouty''' === | ||
Aby funkcia ReadFile nezostala čakajúca na znak, používajú sa tzv. timeouty, maximálne časy, ktoré má daná funkcia trvať. Konkrétne hodnoty sú uložené v štruktúre COMMTIMEOUTS, ktorá má tieto prvky: | Aby funkcia <code>ReadFile</code> nezostala čakajúca na znak, používajú sa tzv. timeouty, maximálne časy, ktoré má daná funkcia trvať. Konkrétne hodnoty sú uložené v štruktúre <code>COMMTIMEOUTS</code>, ktorá má tieto prvky: | ||
ReadIntervalTimeout = 20; Max. doba v [ms] medzi dvoma príchodzími znakmi | ReadIntervalTimeout = 20; Max. doba v [ms] medzi dvoma príchodzími znakmi | ||
Riadok 251: | Riadok 251: | ||
sa ešte pripočíta táto hodnota v [ms]. | sa ešte pripočíta táto hodnota v [ms]. | ||
Ak je hodnota niektorého parametra 0, potom sa daný timeout nevyhodnotí. Ako vidno, máme vlastne dve hodnoty timeoutu. Prvá je IntervalTimeout, druhá je (TotalTimeoutConstant + TotalTimeoutMultiplier * Počet bytov) - aplikuje sa vždz tá hodnota, ktorá nastane skôr. | Ak je hodnota niektorého parametra 0, potom sa daný timeout nevyhodnotí. Ako vidno, máme vlastne dve hodnoty timeoutu. Prvá je <code>IntervalTimeout</code>, druhá je <code>(TotalTimeoutConstant + TotalTimeoutMultiplier * Počet bytov)</code> - aplikuje sa vždz tá hodnota, ktorá nastane skôr. | ||
Po naplnení štruktúry vhodnými hodnotami sa ešte musia stať platnými pre daný komunikačný port, čo urobíme funkciou SetCommTimeouts. | Po naplnení štruktúry vhodnými hodnotami sa ešte musia stať platnými pre daný komunikačný port, čo urobíme funkciou <code>SetCommTimeouts</code>. | ||
<source lang="c++" style="background: LightYellow;"> | <source lang="c++" style="background: LightYellow;"> |
Verzia z 12:56, 3. apríl 2021
Tretí blok cvičení: Sériové komunikačné rozhranie
Poslednú tretinu semestra budeme komunikovať. To preto, lebo komunikácia je neoddeliteľnou súčasťou súčasných počítačov. A aj preto, lebo doteraz sme riešili jednoduché, niekoľkoriadkové programy. Teraz je čas na rozsiahlejší projekt, v ktorom sa naučíte niečo nové ale aj využijete mnoho z predošlých zadaní.
Tento blok úloh na seba nadväzuje, takže výsledok jednej budete potrebovať v nasledujúcej. Preto odpúčame riešiť ich postupne. Na konci vášho snaženia by mal byť jednoduchý program ("talker"), ktorým si budete môcť písať s kamarátom (frajerkou) pri susednom počítači.
Už onedlho budete mať vďaka pochopeniu pracovníkov VS prepojené vždy dva susedné počítače cez sériovú linku, vždy na porte COM1. Prepojenie je realizované káblom typu tzv. null-modem.
Pozn: Dnes, 24. apríla 2006 by už mali byť všetky počítače prepojené.
Literatúra
Teóriu a popis sériového komunikačného rozhrania nájdete napr. tuto:
- Veľmi stručný úvod
- Popis RS-232 na serveri HW.cz
- Decription of the RS-232 (www.lookrs232.com)
- Václav Vacek: Sériová komunikace ve Win32. Nakladatelství BEN, Praha, 2003. (kniha)
Na testovanie a prvé pokusy sa vám určite zíde aj nejaký terminálový program, napríklad tento
- Terminal by Bray++, neinštaluje sa, len spustí.
- Terminal -- lokálna kópia
Tí, čo chcú pracovať doma, si môžu prepojiť dva porty káblom typu null-modem (prekrížený), alebo dokonca len na jednom porte prepojiť piny RxD a TxD. Ešte jednoduchšia alternatíva je nainštalovať tzv. virtuálny sériový port, resp. pár takýchto portov, ktoré sa prepoja tiež virtuálnym null-modem káblom. Tým v počítači vzniknú dva nové porty, s ktorými pracujete ako s klasickými.
- VSP by Constellations - Funkč né demo, otravuje registráciou
- Advanced Virtual COM Port - 15 days trial
- Virtual Serial Port Kit - 15 days trial,
- HW Virtual Serial Port - český, Freeware, treba spustiť 2x
- Null-modem emulator (com0com) - Freeware, GPL
- N8VB_vCOM Virtual Null Modem Cable - Open Source
Toto riešenie sa ale nedá použiť v CPU, pretože nemáte dostatočné oprávnenia na inštaláciu.
A pre tých, ktorí by potrebovali podrobnejšie preskúmať, ako porty fungujú, alebo nefungujú, odporúčame použiť monitor všetkej komunikácie PortMon. Oceníte hlavne vtedy, keď niečo nefunguje, alebo pri ladení zložitejších protokolov.
Programovanie
Úplný popis toho, ako sa programuje sériová linka pod Win32 nájdete na stránkach MSDN Microsoftu:
- Allen Denver: Serial Communications in Win32, Microsoft Windows Developer Support, 1995.
- Norbert Környi: Sériová komunikácia. Výňatok z diplomovej práce FEI STU v Bratislave, 2006.
Pre záujemcov o hlbšie štúdium možno odporučiť aj knihu
- Václav Vacek: Sériová komunikace ve Win32. Nakladatelství BEN, Praha, 2003.
Vo všeobecnosti možno povedať, že Win API poskytuje prístup k sériovému komunikačnému rozhraniu na podobnom princípe ako k súborom a ďalším sekvenčným zariadeniam. Základný postup je takýto:
- Port otvoriť
- Nastaviť komunikačné parametre a timeouty
- Zápis na port
- Čítanie z portu
- Zatvorenie portu
3.1 Otvorenie portu
Otvorenie portu má na starosti funkcia CreateFile
, pričom sa tvárime, že náš port COM1: je existujúci súbor, do ktorého ideme normálne zapisovať, prípadne z neho aj čítať.
HANDLE hCom; // Handle na objekt typu súbor
hCom = CreateFile(
"COM1:", // Názov portu, ktorý otvárame
GENERIC_READ | GENERIC_WRITE, // Otvárame na čítanie aj na zápis
0, // Zdieľanie nebude žiadne
NULL, // Bezpečnostné info žiadne
OPEN_EXISTING, // Otvárame existujúci (port)
0, // Žiadne atribúty súboru
NULL); // Musí byť NULL
if ( hCom == INVALID_HANDLE_VALUE )
printf("\n Chyba: Port sa neda otvorit.\n");
// potom nasleduje CloseHandle(hCom); a potom asi exit...
else
printf("\n OK, port je uspesne otvoreny.\n");
3.2 Komunikačné parametre.
Dve zariadenia (počítače) budú spolu komunikovať len vtedy, ak budú mať oba nastavené rovnaké komunikačné parametre. Aktuálne nastavenie si môžete pozrieť tak, že v konzolovom okne napíšete príkaz
mode com1:
Dostanete výstup podobný tomu na obrázku, zobrazia sa najdôležitejšie komunikačné parametre:
Význam parametrov je nasledovný:
- Baud
- prenosová rýchlosť - z nej vyplýva dĺžka trvania jedného bitu. Aj keď teoreticky môže byť rýchlosť ľubovoľná, podľa medzinárodných noriem sa používajú len niektoré typizované rýchlosti (600, 1200, 2400, 4800, 9600, 19 200 Bd). Udáva sa rýchlosť v Bd (Baud), čo je bit za sekundu.
- Parity
- pre zvýšenie spoľahlivosti prenosu môžu byť údaje zabezpečené paritným bitom, ktorý je doplnený za prenášané bity tak, aby celkový počet jednotiek bol párny (parity even), alebo nepárny (parity odd). Prijímateľ správy si ľahko môže overiť, či pri prenose nedošlo k chybe. Neošetrí sa tým veľa chýb, ale je to často lepšie ako nič. V príklade na obrázku sa zabezpečenie prenosu paritou nepoužíva.
- Data Bits
- Počet prenášaných bitov. Napriek tomu, že sa zdá zrejmé, že to bude 8, nemusí to tak byť a bežne sa používa aj 7 (ak nám stačí prenášať len čistý ASCII text) a niekedy aj 9.
- Stop Bits
- Počet stop bitov. Stop bit býva najčastejšie len jeden, ale pri problémoch so synchronizáciou sa používajú aj dva, niekedy jeden a pol.
- Timeouts
- Uvádza, či je použité sledovanie timeoutov. V našom prípade áno.
- CTS, DTR, RTS, ...
- Ostatné parametre uvádzajú stav a parametre pre handshaking - potvrdzovanie pre zvýšenie spoľahlivosti prenosu. Nebudeme používať.
Význam jednotlivých parametrov je aj na nasledujúcom obrázku. Pripomíname, že podľa RS-232 je log. 0 (space) +5 až +15 V, log. 1 (mark) -5 až -15 V. Ak sa na linke nič nedeje (idle) je v stave log. 1, štart bit je vždy log. 0 (space) a stop bit naopak log. 1 (mark). Na obrázku je ako príklad uvedené vysielanie znaku 'U' (85, 0x55, 0101 01012). Všimnite si, že pri prenose protokolom RS-232 sa prenáša najnižší bit (LSB) ako prvý. Pri bežnom vysielaní údajov cez RS-232 po doplnení štart- a stop-bitu vyzerá signál takto:
Horeuvedené parametre môžete zmeniť napríklad už spomenutým príkazom mode v konzolovom okne:
mode com1: 9600,n,8,1
Uvedený príkaz nastaví port COM1 na rýchlosť 9600 Baudov, bude sa prenášať vždy 8 bitov ukončených 1 stopbitom a nezabezpečené paritou.
Samozrejme, že parametre je možné (a vhodné) meniť aj priamo z vášho programu v jazyku C. Parametre sú uložené v štruktúre typu DCB, ktorej jednotlivé položky zodpovedajú jednotlivým parametrom komunikácie. Pomocou funkcie GetCommState
naplníme štruktúru aktuálnymi parametrami, funkciou SetCommState
zasa nastavíme komunikačné parametre podľa zodpovedajúcich položiek štruktúry. Samotný zápis do štruktúry DCB nikde nič nezmení.
/* *** V ďalšom predpokladáme, že port sme už úspešne otvorili... *** */
DCB PortDCB; // Štruktúra, v ktorej sú uložené parametre
PortDCB.DCBlength = sizeof(DCB); // Inicializuj položku DCBlength
GetCommState(hCom,&PortDCB); // Načítaj aktuálne nastavenia
printf("\nBaud rate: %d",PortDCB.BaudRate);
...
PortDCB.BaudRate = 9600; // Zmeníme nejaký parameter v DCB
...
int err;
err = SetCommState(hCom,&PortDCB); // Nastav aktuálne nastavenia na port
if(!err)
printf("Chyba: Port sa neda nakonfigurovat.");
// nasleduje CloseHandle(hCom); a asi exit...
else
printf("OK, port je uspesne nakonfigurovany.");
Úloha: Preštudujte si na stránkach MSDN ako je vytvorená štruktúra "databázy" DCB (Structure Members) s parametrami sériového komunikačného rozhrania. Napíšte program (funkciu), ktorá vypíše obsah jednotlivých členov štruktúry. Nemusíte vypisovať všetko, stačí tie parametre, ktoré sme uviedli vyššie (rýchlosť, parita, počet bitov a stop bitov).
Tip: Výpis položiek urobte ako funkciu, zíde sa vám v ďalšej úlohe.
Úloha: Napíšte program, ktory zmení komunikačné parametre vybraného portu (COM1,COM2) na 9600,n,8,1. Dokumentujte výpisom DCB (viď predošlá úloha).
Tip: Aby ste mohli meniť parametre, musíte port najprv otvoriť.
3.3 Vysielanie
Zápis na port má na starosti funkcia WriteFile
, pričom sa použijú aktuálne prenosové parametre. Funkcia uloží do premennej Pocet počet skutočne odvysielaných znakov, ktorý treba kontrolovať. V prípade zlyhania buď prenos zopakujeme, alebo skončíme s chybou.
Nasledujúca ukážka samozrejme predpokladá, že port je otvorený a správne nakonfigurovaný.
DWORD Pocet=0;
unsigned char Data='A';
WriteFile(
hCom, // Handle portu, ktory ste otvorili CreateFile
&Data, // Smernik (pointer) na data, ktore chcem vysielat
1, // Pocet bytov, ktore chcem vyslat
&Pocet, // Smernik (pointer) na pocet vyslanych dat
NULL); // Musi byt NULL
if( Pocet != 1 ) // Ak pocet prenesenych dat nesuhlasi
printf("\n Chyba: Zapis na port sa nepodaril.\n");
// a zasa nasleduje CloseHandle(hCom); a potom exit...
else
printf("\n OK.");
Úloha: Napíšte program, ktorý bude na zvolený port stále dokola vysielať znak 'A'. Overte funkciu prijímaním v bežnom terminálovom programe.
Tip: Použite napr. Terminal by Bray++, neinštaluje sa, len spustí.
3. 4. Prijmi znak
Prijímanie znakov zabezpečí funkcia ReadFile
, ktorá načítaný znak uloží do buffera Data
(v našom príklade 1 byte). Zároveň vráti aj počet prijatých bytov. Táto funkcia čaká, až kým nejaký znak nepríde. Takže ak nepríde nič, program "zamrzne". Tomu sa dá vyhnúť použitím timeoutov, ktoré uvedieme ďalej.
V nasledujúcej ukážke sme navyše vyčistili prijímací a vysielací zásobník (buffer) funkciou PurgeComm
.
DWORD Pocet=0; // Počet prijatých dát
unsigned char Data; // Sem ulož prijatý znak
PurgeComm(hCom,PURGE_TXCLEAR | PURGE_RXCLEAR);
ReadFile(
hCom, // Handle portu, z ktorého čítame
&Data, // Pointer na buffer, kam ukladáme prijaté dáta
1, // Počet bytov, na ktoré čakáme
&d, // Pointer na skutočný počet prijatých dát
&Pocet, // Pointer na skutočný počet prijatých dát
NULL); // Musí byť NULL
// Funkcia ReadFile tu teraz čaká, až kým niečo
// nepríde, ale môže aj skončiť bez zachyteného znaku,
// ak medzitým vypršia timeouty.
if( Pocet != 1 ) // Ak počet prenesených dát nesúhlasí
printf("\n Chyba: Citanie z portu nedalo nic.\n");
else
printf("\n OK, z portu sme uspesne prijali [%c].\n",Data);
Úloha: Napíšte program, ktorý bude na čakať na zvolenom porte a stále dokola zobrazovať všetky prijaté znaky. Overte funkciu vysielaním v bežnom terminálovom programe, ako je napr. Hyperterminál, alebo Terminal.
3. 5. Timeouty
Aby funkcia ReadFile
nezostala čakajúca na znak, používajú sa tzv. timeouty, maximálne časy, ktoré má daná funkcia trvať. Konkrétne hodnoty sú uložené v štruktúre COMMTIMEOUTS
, ktorá má tieto prvky:
ReadIntervalTimeout = 20; Max. doba v [ms] medzi dvoma príchodzími znakmi pri čítaní. Ak je táto doba prekročená, operácia čítania sa predčasne ukončí. ReadTotalTimeoutMultiplier = 10; Max. doba v [ms] ktorá sa vynásobí počtom prijímaných bytov a takto dá celkovú max. dobu prijímania. ReadTotalTimeoutConstant = 100; K predošlej hodnote (ReadTotalTimeoutMultiplier) sa ešte pripočíta táto hodnota v [ms]. WriteTotalTimeoutMultiplier = 10; Max. doba v [ms] ktorá sa vynásobí počtom vysielaných bytov a takto dá celkovú max. dobu vysielania. WriteTotalTimeoutConstant = 100; K predošlej hodnote (WriteTotalTimeoutMultiplier) sa ešte pripočíta táto hodnota v [ms].
Ak je hodnota niektorého parametra 0, potom sa daný timeout nevyhodnotí. Ako vidno, máme vlastne dve hodnoty timeoutu. Prvá je IntervalTimeout
, druhá je (TotalTimeoutConstant + TotalTimeoutMultiplier * Počet bytov)
- aplikuje sa vždz tá hodnota, ktorá nastane skôr.
Po naplnení štruktúry vhodnými hodnotami sa ešte musia stať platnými pre daný komunikačný port, čo urobíme funkciou SetCommTimeouts
.
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = 20;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
timeouts.WriteTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
if (!SetCommTimeouts(hCom,&timeouts))
printf("\n Chyba: Timeouty nenastavene.\n");
// a asi exit...
else
printf("\n OK, nastavene Timeouty\n");
3. 6. Zatvor port
Po skončení práce so sériovou linkou je slušné uvoľniť ju pre ďalšie programy a zatvoriť port.
CloseHandle(hCom); // zatvorime port
Úloha -- projekt (12b.)
Naprogramujte jednoduchý terminálový program, pomocou ktorého si budete môcť písať so susedným počítačom prepojeným sériovým káblom. Program bude mať dve okienka na prijatý a odosielaný text, možnosti voľby prenosovej rýchlosti a portu, archiváciu do súboru.
Možné modifikácie: len jedno okno, ale s farebným odlíšením prijatého a odoslaného textu, konfigurácia zo súboru, posielanie a prenášanie súborov,
V princípe je možné zadanie splniť akýmkoľvek vlastným komunikačným programom, napr. komunikácia s mobilom cez virtuálny sériový port a rozhranie Bluetooth, alebo infračervený port, je možné komunikovať s jednočipovým mikropočítačom (Atmel AVR, Microchip PIC, Intel 8051, BasicStamp,...), prípadne riadiť robota (protokol na požiadanie poskytne Ing. Balogh) a pod.
Bodovanie (každá položka cca 1 bod, spolu max. 12 bodov)
- Otvorenie portu
- Výpis položiek DCB
- Nastavenie parametrov, kontrola nastavenia, overenie
- Odskúšanie komunikácie terminálom
- Vysielanie znaku dokola, celého textu
- Prijatie jedného znaku
- Prijatie celého textu
- Timeouty, ošetrenie chýb
- Logovanie do súboru
- Rozlíšenie farebné-priestorové
- Možnosť konfigurácie
- Písomná dokumetácia s popisom programu (rozsah 1 x A4).
Tip 1: Všetky úlohy budeme programovať ako konzolové aplikácie, ale pokročilejší študenti sa môžu pokúsiť aj o okienka. Tu nájdete video, ako naprogramovať jednoduché okno, môžete ho pouziť ako základ, na ktorom budete stavať.
Inštruktážne video Zdrojáky pre tento projekt (c) Norbert Kornyi
Tip 2: Komu sa sériová linka celkom protiví, môže to isté urobiť so socketmi. Teda komunikovať by mali dva počítače so zadanou IP adresou.
Inšpiračné zdrojáky I (c) Norbert Kornyi Inšpiračné zdrojáky II (c) Pavel Petrovič