ArduinoでPCと通信する必要が出てきたので、WindowsでC++を使ったシリアル通信を行う方法を調べると結構めんどくさそう。
今回は必死こいて、WindowsでC++を使ったシリアル通信を行うクラスを作成しました。
その備忘録です。
SerialWrapperクラス
Windowsでシリアル通信を行うにはWin32 APIを叩く必要があります。
今回はArduinoで使う人を想定してArduinoのSerialクラスを意識したSerialWrapperクラスを作りました。
以下ソース
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#ifndef __SerialWrapper__ #define __SerialWrapper__ #include <Windows.h> class SerialWrapper { HANDLE com_port; public: // arg1 : COMポートの番号を指定 // 指定したシリアルデバイスを開く SerialWrapper(int comNum); // 開いていたシリアルデバイスを破棄する ~SerialWrapper(); // arg1 : 伝送速度[bps] // シリアル通信の初期化 void begin(unsigned long speed); // arg1 : 送信する1byte // 1byteのデータを送信する // 実際に送信したbyte数を返す virtual size_t write(char byte); // arg1 : 送信するbyte列 // arg2 : 送信するbyte数 // 複数byteのデータを一度に送信する // 実際に送信したbyte数を返す virtual size_t write(const char buf[], int len); // 受信バッファに溜まったbyte数を返す virtual int available(); // 受信バッファから1byte読み込む // 失敗した場合-1を返す virtual int read(); }; #endif __SerialWrapper__ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#include "SerialWrapper.h" #include <tchar.h> SerialWrapper::SerialWrapper(int comNum) { TCHAR com[10]; _stprintf_s(com, 10, _T("COM%d"), comNum); // シリアルポートを開く com_port = CreateFile(com, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); } // 開いていたシリアルデバイスを破棄する SerialWrapper::~SerialWrapper() { // シリアルポートを閉じる CloseHandle(com_port); } void SerialWrapper::begin(unsigned long speed) { DCB dcb; // シリアルポートの構成情報が入る構造体 GetCommState(com_port, &dcb); // 現在の設定値を読み込み dcb.BaudRate = speed; // 速度 dcb.ByteSize = 8; // データ長 dcb.Parity = NOPARITY; // パリティ dcb.StopBits = ONESTOPBIT; // ストップビット長 dcb.fOutxCtsFlow = FALSE; // 送信時CTSフロー dcb.fRtsControl = RTS_CONTROL_ENABLE; // RTSフロー // 変更した設定値を書き込み SetCommState(com_port, &dcb); } size_t SerialWrapper::write(char byte) { char* sentData = &byte; // 送信する1byte DWORD numberOfPut; // 実際に送信したbyte数 // ポートへ送信 WriteFile(com_port, sentData, 1, &numberOfPut, NULL); return numberOfPut; } size_t SerialWrapper::write(const char buf[], int len) { DWORD lengthOfSent = len; // 送信するbyte数 DWORD numberOfPut; // 実際に送信したbyte数 // ポートへ送信 WriteFile(com_port, buf, lengthOfSent, &numberOfPut, NULL); return numberOfPut; } int SerialWrapper::available() { //受信データ数を調べる DWORD errors; COMSTAT comStat; ClearCommError(com_port, &errors, &comStat); int lengthOfRecieved = comStat.cbInQue; // 受信したメッセージ長を取得する return lengthOfRecieved; } int SerialWrapper::read() { //受信データがない場合は読み込まない if (available() < 1) { return -1; } char buf[1]; // 受信データ格納用 DWORD numberOfRead; // 実際に受信したバイト数 //データ受信 bool result = ReadFile(com_port, buf, 1, &numberOfRead, NULL); if (result == FALSE) { return -1; } else { return buf[0]; } } |
少し説明してみる
コンストラクタでCOMポートの番号を受け取って、デバイスを開き初期化します。
エラー処理は特にしていないです。
実装したい場合は、CreateFileの戻り値をチェックしてINVALID_HANDLE_VALUEならエラーです。
COMポートの番号はデバイスマネージャーで確認できます。
その他は、おおよそArduinoのSerialクラスと同様です。
関数名 | 説明 |
---|---|
begin | 引数に伝送速度を与えて設定する |
abailable | バッファに溜まったデータ数を返す |
write | 引数に与えられたデータを送る |
read | バッファに溜まったデータを1byte取得する |
全体的にエラー処理をあまりしていないですが、軽く使う程度には十分だと思います。
参考サイト
デバイスの開き方やデータの送受信など簡単に説明されています。
WIN32APIでシリアル(RS-232C / EIA-232D)通信
WIN32APIでのシリアル通信に関わる各関数について詳細に説明されています。
より突っ込んだことをやりたい場合非常に参考になりそうです。
まとめ
WIN32APIがとにかくめんどくさいので一度使えるようになったら、その部分は触らなくていいようにしたいものです。。。
今回作成したSerialWrapperクラスは、難しい事が必要でなければ、簡単に使えるので重宝しています。
使いやすいのか使いにくいのかよくわからないところが多いですが(特に初期化周り)、「PCでシリアル通信をしてみよう」という方の参考になれば幸いです。
ノシ