termios tcdrain(fd) fonksiyonu FTDI ile çalışmıyor mu?

Başlatan Tagli, 05 Haziran 2014, 21:29:07

Tagli

Söz konusu tcdrain(fd) fonksiyonunun TX tampon belleğindeki tüm verinin aktarılmasını beklemesi ve aktarma bittikten sonra dönmesi gerekiyor. Ancak bu fonksiyon gördüğüm kadarıyla FTDI dönüştürücüler ile çalışmıyor. İnternette de bu durumdan yakınanlar var. Birileri kernel'deki sürücüyü kurcalayarak çözmeye çalışmış ama oralara girmek istemiyorum.

Bu durumun yarattığı sıkıntı şu: Gönderme sırasında seri port ayarlarını değiştirmek istiyorum. Daha önce bir başlıkta bahsetmiştim "9 bit veri göndermede sorun yaşıyorum" diye. Sorun bununla ilgili gibi. Başlarında adres byte'ı olan iki veri paketi göndereceğimi varsayalım. İkinci paketin başında portu space parity'den çıkarıp mark parity'e ayarlamam gerekiyor ikinci veri paketinin adres byte'ını göndermek için. Ama bunu yaptığımda, o sırada halen gönderilmekte olan birinci paketin son byte'ı etkileniyor ve adres byte'ı gibi mark parity olarak yola çıkıyor.

İnternette 9 bit örneği yok pek. Sorun yaşayanlar daha çok RTS hattı kullanırken benzer durumla karşılaşmışlar.

Araya zorlama bir delay kodu yazmadan - ki bunun adam gibi çalışacağını sanmıyorum - bu sorunu nasıl çözebilirim? TX tampon belleğinde veri kalıp kalmadığını anlamanın başka bir yolu var mı?

Programı bir ara fırsat bulursam eski bir bilgisayarda klasik seri port (dönüştürücüsüz) ile deneyeceğim. Sorun FTDI'dan mı ondan bir emin olmakta fayda var.
Gökçe Tağlıoğlu

Tagli

Aslında benim kullandığım yerde tcdrain() kullanmaya da gerek yok normalde. tcsetattr() fonksiyonuna verilen TCSADRAIN argümanı o işe yarıyor. Bu fonksiyon seri port ayarlarını değiştirmeye yarıyor ancak TCSADRAIN argümanı "önce gönderme işinin bitmesini bekle, sonra yeni ayarlar yürürlüğe girsin" anlamına geliyor. Ama sürücüde hata var belli ki, çünkü bu da çalışmıyor.

Neyse, hoşuma gitmese de sorunu kontrollü bir gecikme koyarak çözdüm mecburen. Pek düzenli yazılmamış olan seri port class'ını paylaşmıyorum, zaten biraz konu dışı kalıyor. Ancak düzgün bir bekleme için kullandığım kod parçaları şunlar:

#include <sys/time.h>
#include <unistd.h>
// Burada tabi başka header'lar da gerekebilir

// Global değişkenler:
int bytePeriod; // mikrosaniye, baudrate'e bağlı olmalı
timeval lastTxTime;
int lastTxSize;

// İlgili Fonksiyonlar

void SerialPort::registerTx(int nBytes)
{
	if (isTransmiting()) {
		lastTxSize += nBytes;
	}
	else {
		gettimeofday(&lastTxTime, 0);
		lastTxSize = nBytes;
	}
}

void SerialPort::waitForTx()
{
	while (isTransmiting()) {
		usleep(bytePeriod);
	}
}

bool SerialPort::isTransmiting()
{
	long txTime = lastTxSize * bytePeriod; // An estimated value
	timeval now;
	gettimeofday(&now, 0);
	long timePassed = timevalDiff(&lastTxTime, &now);
	
	if (timePassed > txTime) return false;
	else return true;
}

long SerialPort::timevalDiff(timeval* start, timeval* finish)
{
  long usec;
  usec = (finish->tv_sec - start->tv_sec) * 1e6;
  usec += (finish->tv_usec - start->tv_usec);
  return usec;
}

// Genel amaçlı gönderme fonksiyonu
int SerialPort::write(const uint8_t* source, int size)
{
	registerTx(size); // Böylece kaç byte'ın yolda olduğunu tahmin edebiliyoruz
	return ::write(fd, source, size);
}

// Bu veya ayar değiştiren başka bir fonksiyon
void SerialPort::setParity(parity_t p)
{
	waitForTx();
	// Asıl yapılması gereken işi yap
}


Özetle şöyle: Gönderme yapacağımız zaman kaç byte göndereceğimizi registerTx() ile bildiriyoruz. Bu sırada göndermenin başladığı zaman işleniyor. Göndermenin devam edip etmediğini anlamak için isTransmitting() fonksiyonunu kullanıyoruz ki o da ilk göndermenin başladığı zamandan şimdiye ne kadar süre geçtiğine bakıp, daha sonra da bu süre içinde o kadar byte'ın gönderilmiş olup olamayacağını inceliyor. Herhangi bir seri port ayar değişikliğinden önce de seri portun veri gönderme yapmadığından emin olmamız gerekiyor.
Gökçe Tağlıoğlu