WIN32API
WIN32APIでシリアル(RS-232C / EIA-232D)通信
WIN32APIを使ったシリアル通信の手順です。
APIの実態はC(C++ではなく)で書かれた関数群なので、最近の「クラスのインスタンスを生成してオブジェクトのメソッドを叩く」というような使い方はできません。
とはいえWindowsAPIは純粋なCでありながら、オブジェクト指向ライクにプログラミングができる作りになっています。
クラスをnewしてインスタンスを生成する代わりに、Create関数で「ハンドル」を生成し、そのハンドルを第一引数に取って各関数を呼び出します。
こうすることで(Cなのでさすがにメソッドチェーンは無理ですが)、「obj.methodname(arg, ...)」とまでは書けなくても、「funcname(hdl, arg, ...)」と近い形で書くことができるようになっています。
Windows7 (32bit) と Windows 10 (64bit) 上で一応送受信とも通信は成功しました。
/*
* WIN32APIでRS-232C通信
* COM1ポートをオープンし、2400bps,データ長8ビット,パリティなし,ストップビット長1ビット,
* RTSフローONで5秒間受信し、アドレスrecievedDataに受信したデータを格納します。
* その後、「dummy data」という文字列を同じポートへ送信して終了します。
*/
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
void rs232c() {
HANDLE comPort = CreateFile(_T("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // シリアルポートを開き、ハンドルを取得(※10番以上のCOMポート名は _T("\\\\.\\COM10") としないと認識してくれない)
DCB dcb; // シリアルポートの構成情報が入る構造体
GetCommState(comPort, &dcb); // 現在の設定値を読み込み
dcb.BaudRate = 2400; // 速度
dcb.ByteSize = 8; // データ長
dcb.Parity = NOPARITY; // パリティ
dcb.StopBits = ONESTOPBIT; // ストップビット長
dcb.fOutxCtsFlow = FALSE; // 送信時CTSフロー
dcb.fRtsControl = RTS_CONTROL_ENABLE; // RTSフロー
SetCommState(comPort, &dcb); // 変更した設定値を書き込み
Sleep(5000); // 受信バッファにメッセージが溜まるまで待つ
DWORD errors; // エラーが起きた場合、エラーコードが入る
COMSTAT comStat; // 通信状態バッファ構造体
ClearCommError(comPort, &errors, &comStat); // 入出力バッファの情報を通信状態バッファへ取り込む
int lengthOfRecieved = comStat.cbInQue; // 通信状態バッファ内の受信したメッセージ長を取得する
// char *recievedData[lengthOfRecieved + 1]; // 受信したメッセージ長分のデータバッファをスタック上に確保
HANDLE hhp = HeapCreate(0, 0, 0); // ヒープハンドルを生成
char *recievedData = (char *)HeapAlloc(hhp, 0, sizeof(char) * (lengthOfRecieved + 1)); // 受信したメッセージ長 + 終端文字分のデータバッファをヒープ上に確保
DWORD numberOfGot; // 受信バッファから実際に読み取ったバイト数が入る
ReadFile(comPort, recievedData, lengthOfRecieved, &numberOfGot, NULL); // 受信バッファからデータバッファへ取り込む
recievedData[lengthOfRecieved] = '\0'; // 受信したデータに終端文字を追加
puts(recievedData); // 受信したデータを表示
const char *sentData = "dummy data"; // 送信する文字列
DWORD lengthOfSent = 10; // 送信するバイト数
DWORD numberOfPut; // 実際に送信したバイト数が入る
WriteFile(comPort, sentData, lengthOfSent, &numberOfPut, NULL); // ポートへ送信
CloseHandle(comPort); // シリアルポートを閉じる
HeapDestroy(hhp); // ヒープを解放
}
int main(void) {
rs232c();
return 0;
}
※受信したメッセージ長分のデータバッファ確保部分でC99のVLA(可変長配列)を使っていましたが、C11でオプション扱いに変更され、C++14では仕様に含まれていないので環境依存になります。念のためHeapAllocするように変更しました。VC++やMinGW使ってるならVLAで問題なく動くと思います。他の環境はよく分からないので一応。
以下、解説です。
* tchar.hとwindows.hをインクルードしておく必要があります。
1. CreateFile関数で通信リソースを作成する
HANDLE CreateFile(
LPCTSTR lpFileName, // ファイル名
DWORD dwDesiredAccess, // アクセスモード
DWORD dwShareMode, // 共有モード
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // セキュリティ記述子
DWORD dwCreationDisposition, // 作成方法
DWORD dwFlagsAndAttributes, // ファイル属性
HANDLE hTemplateFile // テンプレートファイルのハンドル
);
例)
HANDLE comPort = CreateFile(_T("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // シリアルポートを開き、ハンドルを取得
※10番以上のCOMポート名は _T("\\\\.\\COM10") としないと認識してくれません。
2. GetCommState関数でDCB構造体にシリアルポートの現在の情報を読み込む
BOOL GetCommState(
HANDLE hFile, // 通信デバイスのハンドル
LPDCB lpDCB // DCB( デバイス制御ブロック)構造体へのポインタ
);
例)
DCB dcb; // シリアルポートの構成情報が入る構造体
GetCommState(comPort, &dcb); // 現在の設定値を読み込み
3. DCB構造体に変更したい情報を設定する
/* DCBの中身
DWORD DCBlength; // DCBサイズ (Do not touch!!)
DWORD BaudRate; // ボーレート(通信速度)
DWORD fBinary; // バイナリーモード(Windowsでは常にTrue)
DWORD fParity; // 可能化パリティチェック
DWORD fOutxCtsFlow; // CTSアウトプットフロー制御
DWORD fOutxDsrFlow; // DSRアウトプットフロー制御
DWORD fDtrControl; // DTRフロー制御タイプ
DWORD fDsrSensitivity; // DSR敏感さ
DWORD fTXContinueOnXoff; // XOFFはTxを続ける
DWORD fOutX; // XON/XOFF外部フロー制御
DWORD fInX; // XON/XOFF内部フロー制御
DWORD fErrorChar; // エラー置換可
DWORD fNull; // NULLストリッピング可
DWORD fRtsControl; // RTSフロー制御
DWORD fAbortOnError; // リード/ライトエラーでアボート
DWORD fDummy2; // リザーブ
WORD wReserved; // 現在未使用
WORD XonLim; // 送信XONスレッショルド
WORD XoffLim; // 送信XOFFスレッショルド
BYTE ByteSize; // 1バイトのビット数、4-8
BYTE Parity; // 0,1,2,3,4=パリティなし、奇数、偶数、マーク、スペース
BYTE StopBits; // 0,1,2=1, 1.5, 2
char XonChar; // 送受信 XON文字
char XoffChar; // 送受信 XOFF文字
char ErrorChar; // エラー置換文字
char EofChar; // 入力終了文字
char EvtChar; // 受信イベント文字
WORD wReserved1; // リザーブ、使用不可
*/
例)
dcb.BaudRate = 2400; // 速度
dcb.ByteSize = 8; // データ長
dcb.Parity = NOPARITY; // パリティ
dcb.StopBits = ONESTOPBIT; // ストップビット長
dcb.fOutxCtsFlow = FALSE; // 送信時CTSフロー
dcb.fRtsControl = RTS_CONTROL_ENABLE; // RTSフロー
4. SetCommState関数でシリアルポートの設定を書き込む
BOOL SetCommState(
HANDLE hFile, // 通信デバイスのハンドル
LPDCB lpDCB // DCB( デバイス制御ブロック)構造体へのポインタ
);
例)
SetCommState(comPort, &dcb); // 変更した設定値を書き込み
5. 受信バッファにデータが溜まるまでウェイトする
例)
Sleep(5000); // 受信バッファにメッセージが溜まるまで待つ
6. ClearCommError関数で受信バッファに溜まっているバイト数を取得する
BOOL ClearCommError(
HANDLE hFile, // 通信デバイスのハンドル
LPDWORD lpErrors, // エラーコードを受け取る変数へのポインタ
LPCOMSTAT lpStat // 通信状態バッファへのポインタ(この構造体に受信したメッセージ長が入る)
);
例)
DWORD errors; // エラーが起きた場合、エラーコードが入る
COMSTAT comStat; // 通信状態バッファ
ClearCommError(comPort, &errors, &comStat); // 入出力バッファの情報を通信状態バッファへ取り込む
int lengthOfRecieved = comStat.cbInQue; // 通信状態バッファ内の受信したメッセージ長を取得する
7. ReadFile関数で受信バッファからデータを読み込む
BOOL ReadFile(
HANDLE hFile, // ファイルのハンドル
LPVOID lpBuffer, // データバッファへのポインタ
DWORD nNumberOfBytesToRead, // 読み取り対象のバイト数
LPDWORD lpNumberOfBytesRead, // 実際に読み取ったバイト数が入る変数へのポインタ
LPOVERLAPPED lpOverlapped // オーバーラップ構造体のバッファ
);
例)
HANDLE hhp = HeapCreate(0, 0, 0); // ヒープハンドルを生成
char *recievedData = (char *)HeapAlloc(hhp, 0, sizeof(char) * (lengthOfRecieved + 1)); // 受信したメッセージ長 + 終端文字分のデータバッファをヒープ上に確保
DWORD numberOfGot; // 受信バッファから実際に読み取ったバイト数が入る
ReadFile(comPort, recievedData, lengthOfRecieved, &numberOfGot, NULL); // 受信バッファからデータバッファへ取り込む
8. WriteFile関数でデータを送信する
BOOL WriteFile(
HANDLE hFile, // ファイルのハンドル
LPCVOID lpBuffer, // データバッファ
DWORD nNumberOfBytesToWrite, // 書き込み対象のバイト数
LPDWORD lpNumberOfBytesWritten, // 実際に書き込んだバイト数が入る変数へのポインタ
LPOVERLAPPED lpOverlapped // オーバーラップ構造体のバッファ
);
例)
const char *sentData = "dummy data"; // 送信する文字列
DWORD lengthOfSent = strlen(sentData); // 送信するバイト数
DWORD numberOfPut; // 実際に送信したバイト数が入る
WriteFile(comPort, sentData, lengthOfSent, &numberOfPut, NULL); // ポートへ送信
9. CloseHandle関数でポートを閉じる
BOOL CloseHandle(
HANDLE hObject // オブジェクトのハンドル
);
例)
CloseHandle(comPort); // シリアルポートを閉じる
|