RTSをめぐる混乱

シリアルポートのRTS信号をめぐる混乱について。

僕「RTSって、受信可能なときに相手に送信許可を与える信号ですよね?」
上司「(゚Д゚)ハァ? 送信したいときに相手に許可を求める信号やろ」
僕「えー? それだとRTSを相手のCTSに接続するの、おかしくないです?」
上司「でも、本にもRTSは送信要求(Request To Send)って書いてあるやろ?」
( ´・ω) (´・ω・) (・ω・`) (ω・` )

そう、RTS信号の意味、世間には2通りの解釈があるのです。

(1)「送りたい」…送信バッファにデータが入ったら立て、送信完了したら下げる。
(2)「送っていいよ」…受信バッファに余裕あるなら立て、余裕ないなら下げる。

これに関する次のような説明を見つけました。
http://www15.atpages.jp/limz80msx/program/rs232c.html

つまり、本来のRS-232Cの規格では(1)が正しいのだけど、ハードウェアフロー制御のために(2)の使い方をするのがいつの間にか広まったというのです。

WindowsのWIN32APIは(1),(2)の両方をサポートしています。ただし(2)がデフォルトです。SetCommState関数に渡すDCB構造体のfRtsControlに、下記いずれかを指定できます。

(1) RTS_CONTROL_TOGGLE ...「送りたい」ときに上げる 。
(2) RTS_CONTROL_HANDSHAKE …「送っていいよ」のときに上げる。
(3) RTS_CONTROL_ENABLE …常に上げる。
(4) RTS_CONTROL_DISABLE …常に下げる。

Linuxは規格外の(2)はサポートしていますが、本来の規格どおりの(1)はサポートしてないように見受けられます。tcsetattr関数に渡すtermios構造体のc_cflagにCRTSCTSフラグを立てるとRTS/CTS ハードウェアフロー制御が有効になるのですが、これは(2)を意味するようです。The Linux Serial HOWTO の 16.4 を読むと、「本来のRTSの使い方とは違うので紛らわしいけど、むしろ現在では本来の使い方をする機器はあんまり無い」みたいなことが書かれています。
http://archive.linux.or.jp/JF/JFdocs/Serial-HOWTO.html (日本語訳)

たしかに、僕も今までハードウェアフロー制御といえばもっぱら(2)の方法をとってきました。しかしながら、RS-232Cを半二重であるRS-485に変換するときに、(1)の用法のRTSを利用してトランシーバーの方向を切り替えるという使い方が現在でもあるのです。

ためしにXP上のVC++2010で簡単なプログラムを組んで実験したところ、ちゃんと(1)のRTS_CONTROL_TOGGLEが機能しました。下図は、9600ボーで80バイト送信したときのRTSとTxDです。RTS立ち下がりタイミングもほぼ一定でした。

RTSとTxD

// 実験に使ったテキトーなプログラムのソース

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

int main(void)
{
	HANDLE comPort = CreateFile(_T("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

	DCB dcb;
	GetCommState(comPort, &dcb); // 現在の設定
	dcb.BaudRate = 9600;
	dcb.ByteSize = 8;
	dcb.Parity = NOPARITY;
	dcb.StopBits = ONESTOPBIT;
	dcb.fOutxCtsFlow = FALSE;
	dcb.fRtsControl = RTS_CONTROL_TOGGLE; // RTSは送信時に立てる
	SetCommState(comPort, &dcb); // 設定を変更

	while(1){
		// 80バイト送信
		const char* sentData = "01234567890123456789012345678901234567890123456789012345678901234567890123456789";
		DWORD lengthOfSent = 80;
		DWORD numberOfPut;
		WriteFile(comPort, sentData, lengthOfSent, &numberOfPut, NULL);
		// 1秒ウェイト
		Sleep(1000);
	}

	CloseHandle(comPort);

	system("pause");
	return 0;
}