Komunikace pomocí sériového portu 3.díl (MFC a C++)

V minulém díle jsme si dokončili třídu CComPort a dnes si vytvoříme aplikaci CHAT, která bude provádět komunikaci mezi dvěma PC pomocí sériového portu. Data budou posílána tak, že se nejdříve zašle délka textu (i s ukončovací nulou) a potom se zašlou samotná textová data. Pro monitorování portu bude vytvořeno speciální vlákno, které bude zasílat zprávu hlavnímu vláknu vždy, když na port přijdou nějaká data.

Ještě bych dodal důležitou informaci, že výsledný program fungoval na mém PC (WinXP) správně jen tehdy, když běžel v konfiguraci RELEASE. Jestliže byla nastavena konf. DEBUG, tak se program tvářil, že zapisuje data, ale ve skutečnosti nic nezapisoval.

Vytvoření aplikace

Jako první věc si vytvoříme aplikaci založenou na dialogu a umístíme na ni potřebné ovládací prvky (viz program ke stažení na konci článku). Dále si vytvoříme proměnné m_zprava, která bude spjata s textovým polem pro zprávu k odeslání a proměnnou m_prijato, která bude reprezentovat víceřádkové textové pole pro ukládání došlých zpráv.

Sledovací vlákno

Dalším krokem bude vytvoření třídy, která bude reprezentovat sledovací činnost. Nejprve si vytvoříme novou třídu odvozenou od CWinThread a definujeme si zprávy, které budou vlákno obsluhovat:

#define WM_WATCHPORT (WM_USER+0x10)
#define WM_RXCHAR    (WM_USER+0x11)

První zprávu použijeme k aktivaci vlákna a druhou zprávu bude zasílat vlákno hlavnímu oknu aplikace vždy, když na port příjdou data. Dále si vytvoříme obsluhu vláknové zprávy WM_WATCHPORT a tuto obsluhu přiřadíme funkci OnWatchPort, která bude mít následující podobu.

LRESULT CWatchThread::OnWatchPort(WPARAM wParam,LPARAM lParam)
{
  CComPort* pPort=(CComPort*)wParam;
  HWND hwnd=(HWND)lParam;

  HANDLE handle;
  pPort->GetComHandle(&handle);

  ::SetCommMask(handle,EV_RXCHAR);

  DWORD mask;
  OVERLAPPED os;
  os.hEvent=::CreateEvent(NULL,TRUE,FALSE,NULL);

  while(pPort->IsConnected())
  {
    if(::WaitCommEvent(handle,&mask,NULL))
    {
      if((mask & EV_RXCHAR)==EV_RXCHAR)
      {
        COMSTAT cs;
        DWORD error;

        ::ClearCommError(handle,&error,&cs);	//Test na obsah vstupního bufferu
        if(cs.cbInQue>0)
        {
          SendMessage(hwnd,WM_RXCHAR,0,0);
        }
      }
    }
  }

  ::CloseHandle(os.hEvent);

  return 0;
}

Jak vidíte, funkce nejprve získá ukazatel na třídu CComPort a handle hlavního okna, které byly uloženy v parametrech zprávy. Poté se spustí smyčka, která trvá tak dlouho, dokud je port připojen. V této smyčce se spouští funkce WaitCommEvent, která zastaví chod vlákna a čeká, až se na portu stane nějaká událost. Když se tak stane, tak otestujeme, co to bylo za událost. EV_RXCHAR znamená, že na port přišla nějaká data. V dalším kroku zjistíme počet bajtů ve vstupním bufferu portu a pokud je tento počet větší než nula, tak se hlavnímu oknu aplikace zašle zpráva WM_RXCHAR.

Hlavní okno

Nejprve si do hlavní třídy okna přidáme ukazatel na vlákno CWatchThread a členskou proměnnou třídy CComPort. Poté se přesuneme do funkce OnInitDialog a vložíme následující kód:

if(!m_port.OpenCom(2,m_hWnd))
{
  MessageBox("Nepodařilo se otevřít port COM2!");
  return FALSE;
}

m_pWatch=new CWatchThread();
m_pWatch->CreateThread();

m_pWatch->PostThreadMessage(WM_WATCHPORT,(WPARAM)&m_port,(LPARAM)this->m_hWnd);	

Myslím, že tento úsek nepotřebuje moc popisovat, jedná se pouze o vytvoření vlákna a odeslání zprávy, která spustí monitorování.

Jako další si vytvoříme obsluhu zprávy WM_CLOSE, abychom mohli ukončit a vymazat vlákno a uzavřít port:

m_pWatch->SuspendThread();
delete m_pWatch;

if(!m_port.CloseCom())
{
  MessageBox("Nepodařilo se uzavřít port COM2!");
}

Dále vytvoříme obsluhu tlačítka Odeslat, které máme na hlavním dialogu a které bude provádět odeslání dat na port:

void CKomunikaceDlg::OnSendData()		//Zapsání na port
{
  CString text;
  m_zprava.GetWindowText(text);	//Zjisti text zprávy

  UINT delka=text.GetLength()+1;		//+Ukončovací nula
  char* pText=new char[delka];
  strcpy(pText,text);
	
  m_port.WriteData((char*)&delka,sizeof(delka));	//Zapiš délku
  m_port.WriteData((char*)pText,delka);		//Zapiš text

  delete[] pText;
}

V tomto úseku programu jsme poprvé použili funkce WriteData. V takto jednoduchém příkladu, kde odesíláme pouze krátké zprávy, je možné zapisovat přímo, ale v praxi se čtení z portu umístí také do zvláštního vlákna, aby nezdržovalo hlavní aplikaci.

Jako poslední krok si vytvoříme obsluhu zprávy WM_RXCHAR, kterou nám bude posílat sledovací vlákno. Obsluha této zprávy bude vypadat následovně:

LRESULT CKomunikaceDlg::OnRxChar(WPARAM wParam,LPARAM lParam)
{
  UINT delka;
  m_port.ReadData((char*)&delka,sizeof(delka));	//Načti délku

  //Načíst data
  char* pText=new char[delka];
  m_port.ReadData((char*)pText,delka);	//Načti text
	
  //Přidat text do editu
  CString old,nove;
  m_prijato.GetWindowText(old);

  nove.Format("%s\r\n%s",pText,old);

  m_prijato.SetWindowText(nove);

  delete[] pText;

  return 0;
}

Testování

Nyní tedy máme vytvořenou celou aplikaci a můžeme přistoupit k jejímu testu. Pokud nemáte dva počítače, dá se to udělat jednoduše tak, že spojíte na portu vždy každý vstup s výstupem a PC tedy bude posílat data samo sobě. Pokud vše funguje, máte vyhráno, pokud ne, tak bych nejprve zkusil zmenšit rychlost portu a potom zkontrolovat, jestli aplikace běží v konfiguraci Release.

Toto je tedy konec tohoto miniseriálu. Pokud máte nějaké problémy s portem nebo programem, klidně napište a pokusím se vám pomoct.

Stáhnout zdrojový kód (35 kb)

UPOZORNĚNÍ: Jedná se o archiv článků z let 2003 - 2005, uvedené technologie či postupy již mohou být neaktuální.