Staj projem kapsamında araştırdığım ve öğrendiğim bu konuyu sizlerle de paylaşmak istedim..
Windows’ta Seri İletişim
Bilgisayarın seri iletişim yapabilmesi için seri bir porta sahip olması gerekmektedir. Bilgisayarların çoğu COM port olarak bilinen ve genellikle COM1, COM2 olarak adlandırılan en azından bir seri porta sahiptir. Seri portların aygıt sürücüleri vardır. Diğer bir deyişle, seri porta girdi/çıktı(IO) yapılmaktadır. Aynı IO disk tabanlı dosyalarla yapılır. Bu nedenle bir dosyayı okumak – yazmak için kullanılan API’lerin seri portlarda kullanılması sürpriz olmamalıdır. Seri porta veri yollandığında baytlar halinde, veri terk ederken ise bitler formundadır. Buna benzer olarak, veri seri porta ulaştığında bit formatında, veriyi alırken ise bayt formundadır.
COM Port Açmak
COM port aşağıdaki API kullanılarak açılabilir:
HANDLE hCommPort = ::CreateFile(szPortName,
GENERIC_READ|GENERIC_WRITE,// erişim
0,//(share) 0: COM Port paylaşılamaz
0,// güvenlik
OPEN_EXISTING,//yaratma: var olanı aç)
FILE_FLAG_OVERLAPPED,
0, // COM Port için şablon dosya yok
);
Üçüncü, beşinci ve yedinci parametreler yasayla belirlenmiş yukarıdaki örnekteki gibi olmalıdırlar. Altıncı parametrenin FILE_FLAG_OVERLAPPED olmasının sebebi COM Port’un örtüşmeli olarak açılmak istenmesidir. CREATE_FILE API’si ise isminden de tahmin edildiği üzere bir dosya yaratmak ya da var olanı açmak için konulmuştur.
Windows için, bir seri portun ya da disk tabanlı bir dosyanın her ikisi de IO aygıtlarıdır. Bu nedenle, varolan bir dosyayı (COM Port) açmak için gerekli olan dosyanın ismidir, OPEN_EXISTING olarak parametre geçildi.
Eğer bir COM Port başarılı bir şekilde açıldıysa, API tanıtıcıyı(handle) COM Port’a geri döner. Yine de, eğer sistem COM Port u açamazsa INVALID_HANDLE_VALUE değeri geri döner. GetLastError çağrılarak ise sorunun sebebi anlaşılabilir. COM Port’u açarken çok karşılaşılan problemlerden biri daha önce başka bir program tarafından açılmış olmasıdıri bu durumda ERROR_ACCESS_DENIED hatası alınır. Buna benzer olarak eğer yanlışlıkla var olmayan bir COM Port’u açmaya çalışırsak da ERROR_FILE_NOT_FOUND hatası son hara olarak alınır.
Yazma ve Okuma
Dosyaya yazma için aşağıdaki API kullanılır:
iRet = WriteFile (hCommPort, data, dwSize, &dwBytesWritten, &ov);
Burada birinci parametre COM Port’un tanıtıcısını, ikinci parametre veriyi, üçüncü parametre veri yazılacak arabelleğin boyutunu, dördüncü parametre ise arabelleğin adresini vermektedir.
Veriyi okumak için ise aşağıdaki API kullanılır:
abRet = ::ReadFile(hCommPort, szTmp, sizeof(szTmp), &dwBytesRead, &ovRead);
Burada ise birinci parametre yine COM Port’un tanıtıcısını göstermektedir. İkinci parametre okunmak için verinin aktarılacağı bellek birimini, üçüncü parametre bunun boyutunu dördüncü parametre ise okunacak verinin bayt olarak miktarını gösterir.
Seri İletişimle İlgili Durumlar
Örnek olarak, “Hello” yollandığını ve aygıtın cevap olarak “Hi” yolladığını varsayarsak burada problem aygıtın ne zaman cevap vereceğini ya da hiç cevap verecek mi bilinmemesidir. Seçeneklerden biri WriteFile çağrısı yapılana kadar ReadFile’ın çağrılmasıdır. Eğer veri yoksa, sonra tekrar okuma yapılmalıdır. Bu ‘polling’ e yol açmaktadır. Bu iyi bir model olarak önerilmez. Eğer veri geldiğinde sistem tarafından onaylanılsa ve ReadFile’ a çağrı yapılabilse iyi bir model olabilirdi. Bu olaya dayalı yaklaşımdır ve Windows programlamaya çok iyi uyar.
Seri iletişimle ilgili bir diğer durum, daime iki aygıt arasında olduğundan, iki aygıtın birbirleri arasında nasıl konuşacakları konusunda anlaşmaya varmasıdır. Her taraf işi yürütmek için kesin protokollere ihtiyaç duyar. Bu nedenle iletişimi sağlayan seri portun yapılandırılması gerekmektedir. Bu amaç için de mevcut bir API bulunur.
SetCommState(HANDLE hFile, LPDCB lpDCB);
İlk parametre COM Port’un tanıtıcısıdır, ikinci parametre ise aygıt kontrol bloğudur(DCB). DCB, winbase.h’da tanımlı bir yapıdır ve 28 veri üyesine sahiptir. Her iki aygıt için parametreler ayarlanmalıdır. Aşağıdaki kod o anki DCB alır ve bazı alanlarını ayarlar.
DCB dcb = {0};
dcb.DCBlength = sizeof(DCB);
if (!::GetCommState (hCommPort,&dcb))
{
TRACE (“CSerialCommHelper : Failed to Get Comm State Reason: %d”,
GetLastError());
return E_FAIL;
}
dcb.BaudRate = dwBaudRate;
dcb.ByteSize = byByteSize;
dcb.Parity = byParity;
if ( byStopBits == 1 )
dcb.StopBits = ONESTOPBIT;
else if (byStopBits == 2 )
dcb.StopBits = TWOSTOPBITS;
else
dcb.StopBits = ONE5STOPBITS;
if (!::SetCommState (m_hCommPort,&dcb))
{
ASSERT(0);
TRACE ( “CSerialCommHelper : Failed to Set Comm State Reason: %d”,
GetLastError());
return E_FAIL;
}
TRACE ( “CSerialCommHelper : Current Settings, (Baud Rate %d; Parity %d; “
“Byte Size %d; Stop Bits %d”, dcb.BaudRate,
Çoğu zaman diğer alanların değiştirilmesine gerek yoktur. Fakat eğer yapı değiştirilecekse çok dikkatli olunması gerekir.
Olaya Dayalı Yaklaşım
Eğer COM Port herhangi bir veri için ‘polling’ durumunda tutulmak istenmiyorsa bir çeşit olay mekanizması kullanılmalıdır. Kendimizi sisteme onaylatmak için sorabileceğimiz bir yol bulunmaktadır. Bunun için API:
SetCommMask(HANDLE hHandle, DWORD dwEvtMask)
İlk parametre COM Port’un açıldığında dönen tanıtıcısıdır. İkinci parametre ise bir olaylar dizisini belirlemek için kullanılır.
Maskede belirlenecek olan olaylar uygulamanın ihityaçlarına bağlıdır. Örnek olarak eğer karakterlerin ulaşmasıyla ilgileniliyorsa, EV_RXCHAR olay maskesi olarak belirlenmelidir. Benzer olarak eğer veri yollandığında bilinmesi isteniyorsa EV_TXEMPTY bayrağı belirlenmelidir. Böyle bir örnek için çağrı şu şekilde olmaktadır:
SetCommMask(hCommPort, EV_TXEMPTY|EV_RXCHAR);
WaitCommEvent ise bir olay meydana gelene kadar bloklar. Kullanımı aşağıdaki gibidir :
BOOL WaitCommEvent(HANDLE hCommPort, LPDWORD dwEvtMask,LPOVERLAPPED lpOverlapped);
Burada anahtar üçüncü parametredir.
Örtüşmeli IO, asenkron IO olarak düşünülebilir. Bir fonksiyon çağrı yaptığında ve örtüşmeli IO yapısı belirlendiğinde, bu o anki işlemin yapılmasının deneneceği fakat eğer tamamlanamazsa hemen işlem yapıldığı anda haber verileceği anlamına gelir. Sistemin bize tamamlandığında haber vermesinin yolu lpOverlapped yapısının parçası olan kernel olay nesnesinin ayarlanmasıyla sağlanabilir. Bu nedenle yapılması gereken bir thread yaratmak ve thread’in WaitForSingleObject API’lerinden birini kullanan olay nesnesini beklemesini sağlamaktır.
Örtüşmeli yapı:
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
Son parametre yaratmak için ihtiyaç duyulan olay tanıtıcısıdır. WaitCommEvent örtüşmeli yapıya parametre olarak geçilen bir çağrı yapıldığında, ve sistem çağırma işlemini tamamlayamazsa; bu portta hiç karakter görmediği anlamına gelir. Bu nedenle FALSE geri döncektir. Eğer GetLastError çağrısı yapılırsa, ERROR_IO_PENDING geri döner. Bu da çağrının kabul edildiğini fakat hiçbir karakterin henüz COM Port’a ulaşmadığı anlamına gelir. Aynı zamanda karakterler ulaştığında, sistem örtüşmeli yapının hEvent’ini ayarlayacağı anlamına da gelir. Öyleyse, eğer thread hEvent’deki tek nesne için beklerse ve INFINITE parametre geçilirse Wait fonksiyonu WAIT_OBJECT_0 dönene kadar beklenir ki bu da birkaç karakterin ulaştığı ya da çıktı arabelleğinde bütün verinin gönderildiği anlamına gelir.
Bir değil de birden çok olayla ilgilenilmek istendiğinde hangi olayın yapıldığı kontrol edilmelidir. Bu ise GetCommMask’e çağrı yapılarak ve her olaya karşılık DWORD’ü kontrol edilerek yapılmalıdır. Bu duruma örnek kod:
unsigned __stdcall CSerialCommHelper::ThreadFn(void*pvParam)
{
OVERLAPPED ov;
memset(&ov,0,sizeof(ov));
ov.hEvent = CreateEvent( 0,true,0,0);
HANDLE arHandles[2];
arHandles[0] = apThis->m_hThreadTerm;
DWORD dwWait;
SetEvent(apThis->m_hThreadStarted);
while ( abContinue )
{
BOOL abRet = ::WaitCommEvent(apThis->m_hCommPort,&dwEventMask, &ov) ;
if ( !abRet )
{
ASSERT( GetLastError () == ERROR_IO_PENDING);
}
arHandles[1] = ov.hEvent ;
dwWait = WaitForMultipleObjects (2,arHandles,FALSE,INFINITE);
switch ( dwWait )
{
case WAIT_OBJECT_0:
{
_endthreadex(1);
}
break;
case WAIT_OBJECT_0 + 1:
{
DWORD dwMask;
if (GetCommMask(apThis->m_hCommPort,&dwMask) )
{
if ( dwMask & EV_TXEMPTY )
TRACE(“Data sent”);
ResetEvent ( ov.hEvent );
continue;
}
else
{
//read data here and reset ov.hEvent
}
}
}//switch
}//while
return 0;
}
İlerlemeden önce dikkate alınması gereken bir diğer önemli husus da iletişimdeki zaman aşımlarıdır. Bunun için API:
SetCommTimeouts ( HANDLE hCommPort, LPCOMMTIMEOUTS lpCommTimeOuts)
COMTIMEOUTS aşağıdaki bir yapıdır:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout;
DWORD ReadTotalTimeoutMultiplier;
DWORD ReadTotalTimeoutConstant;
DWORD WriteTotalTimeoutMultiplier;
DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
Leave a Reply