記念すべき関西大学 電気通信工学研究会のアドベントカレンダー1日目です!!
初日の今日は、先日行われた学園祭でC++でArduinoと通信した話を書こうと思います。
学園祭でした事
今回の学園祭での展示物のひとつとして、子供向けに3色LEDの発光パターンを自分で作って持って帰ろう!!(¥500) という擬似的なプログラミング体験をしてもらいました。GUI上のマウス操作で手元のLEDを見ながら色、順番、点灯時間を操作してもらいました。今回の話はプレビューを手元のLEDで光らせるという話です。
使用技術
GUIプログラムからRGB値をArduinoにシリアル通信で送信しています。なお言語はC/C++です。
Arduinoとシリアル通信
今回はこちらを参考にしました。
Arduinoとの通信の基本的な流れは以下の通りです。
Arduinoとシリアル通信
今回はこちらを参考にしました。
Arduinoとの通信の基本的な流れは以下の通りです。
初期化
Arduinoと通信するにはCOMポートを使用します。まずはこのCOMポートを初期化しないといけません。
HANDLE arduino = INVALID_HANDLE_VALUE;
/// @brief arduinoとの通信の初期化
/// @param com 使用するCOMポート
/// @return 初期化に成功したか
bool init(std::string com) {
bool Ret;
if (arduino != INVALID_HANDLE_VALUE) {
CloseHandle(arduino);
}
//1.ポートをオープン
arduino = CreateFile(com.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (arduino == INVALID_HANDLE_VALUE) {
std::cout << "PORT COULD NOT OPEN";
return false;
}
//2.送受信バッファ初期化
Ret = SetupComm(arduino, 1024, 1024);
if (!Ret) {
std::cout << "SET UP FAILED";
return false;
}
Ret = PurgeComm(arduino, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
if (!Ret) {
std::cout "CLEAR FAILED";
return false;
}
//3.基本通信条件の設定
DCB dcb;
GetCommState(arduino, &dcb);
dcb.DCBlength = sizeof(DCB); // DCBのサイズ:
//データ
dcb.BaudRate = 9600; // 伝送速度: ボーレートをbps単位で指定
dcb.fBinary = TRUE; // バイナリモード: 通常TRUEに設定
dcb.ByteSize = 8; // データサイズ : 通常8ビット
dcb.fParity = NOPARITY; // パリティ有無種類: パリティなしの場合はNOPARITYを指定
// 奇数パリティの場合は ODDPARITY 他
dcb.StopBits = ONESTOPBIT; // ストップビットの数: 通常1ビット→ ONESTOPBIT;
//ハードウェアフロー制御
dcb.fOutxCtsFlow = FALSE; // CTSハードウェアフロー制御:CTS制御を使用しない場合はFLASEを指定
// CTS 制御をする場合はTRUEを指定してCTS信号を監視します。
dcb.fOutxDsrFlow = FALSE; // DSRハードウェアフロー制御:使用しない場合はFALSEを指定
dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR有効/無効: 無効なら DTR_CONTROL_DISABLE;ISABLE
dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS制御: RTS制御をしない場合はRTS_CONTROL_DISABLEを指定
// RTS制御をする場合はRTS_CONTROL_ENABLE 等を指定
// ソフトウェアフロー制御
dcb.fOutX = FALSE; // 送信時XON/OFF制御の有無: なし→FLALSE
dcb.fInX = FALSE; // 受信時XON/XOFF制御の有無:なし→FALSE
dcb.fTXContinueOnXoff = TRUE; // 受信バッファー満杯&XOFF受信後の継続送信可否:送信可→TRUE
dcb.XonLim = 512; // XONが送られるまでに格納できる最小バイト数:
dcb.XoffLim = 512; // XOFFが送られるまでに格納できる最小バイト数:
dcb.XonChar = 0x11; // 送信時XON文字 ( 送信可:ビジィ解除 ) の指定:
// 一般に、XON文字として11H ( デバイス制御1:DC1 )がよく使われます
dcb.XoffChar = 0x13; // XOFF文字(送信不可:ビジー通告)の指定:なし→FALSE
// 一般に、XOFF文字として13H ( デバイス制御3:DC3 )がよく使われます
//その他
dcb.fNull = TRUE; // NULLバイトの破棄: 破棄する→TRUE
dcb.fAbortOnError = TRUE; // エラー時の読み書き操作終了: 終了する→TRUE
dcb.fErrorChar = FALSE; // パリティエラー発生時のキャラクタ(ErrorChar)置換: なし→FLALSE
dcb.ErrorChar = 0x00; // パリティエラー発生時の置換キャラクタ
dcb.EofChar = 0x03; // データ終了通知キャラクタ : 一般に0x03(ETX)がよく使われます。
dcb.EvtChar = 0x02; // イベント通知キャラクタ : 一般に0x02(STX)がよく使われます
Ret = SetCommState(arduino, &dcb);
if (!Ret) {
std::cout << "SetCommState FAILED";
return false;
}
//タイムアウト
COMMTIMEOUTS timeout; // COMMTIMEOUTS構造体の変数を宣言
timeout.ReadIntervalTimeout = 500; // 文字読込時の2も時間の全体待ち時間(msec)
timeout.ReadTotalTimeoutMultiplier = 0; // 読込の1文字あたりの時間
timeout.ReadTotalTimeoutConstant = 500; // 読込エラー検出用のタイムアウト時間
//(受信トータルタイムアウト) = ReadTotalTimeoutMultiplier * (受信予定バイト数) + ReadTotalTimeoutConstant
timeout.WriteTotalTimeoutMultiplier = 0;// 書き込み1文字あたりの待ち時間
timeout.WriteTotalTimeoutConstant = 500;// 書き込みエラー検出用のタイムアウト時間
//(送信トータルタイムアウト) = WriteTotalTimeoutMultiplier * (送信予定バイト数) + WriteTotalTimeoutConstant
Ret = SetCommTimeouts(arduino, &timeout);// SetCommTimeOut()関数にポートハンドルおよびCOMMTIMEOUTS構造体の
//アドレスを代入します。
if (Ret == FALSE) //失敗した場合
{
std::cout << "SetCommTimeouts FAILED";
return false;
}
return true;
}
引数のstd::string com
は使用するCOMポートを渡します。例えば3番ポートを使用するのであれば"COM3"
を渡します。(デバイスマネージャーの「ポート(COMとLPT)」から確認できます。)またグローバル空間のarduino
変数を経由してarduinoと通信していきます。
送信
データは1バイトずつ送ります。
/// @brief arduinoにデータを送る
/// @param data 送るデータ(BYTE)
/// @return 送れたか
bool send(BYTE data) {
// 通信ができないときは何もしないで終わる
if (arduino == INVALID_HANDLE_VALUE) {
return false;
}
DWORD dwSendSize; // 送信データサイズ
COMSTAT Comstat; // 通信デバイス情報構造体
DWORD dwErrorMask; // エラーコードを受け取る変数
constexpr auto DataSize = sizeof(data); //データのサイズ取得
define MAX = 64;
while(true) {
ClearCommError( //ポートの状態を取得してエラーをクリアする
arduino, //通信デバイスのハンドル:CreateFile()で取得したハンドルを指定
&dwErrorMask, //下記のエラーコードのポインタを取得→エラーの判別
// CD_BREAK(フレーミングエラー)、
// CE_RXPARITY(パリティエラー)、
// CE_RXOVER(入力バッファーオーバーフロー)他
&Comstat //COMSTAT構造体へのポインタを取得
// Comstat.cbOutQue 送信バッファーの中に残っているデータ数
);
if ((MAX - Comstat.cbOutQue) > DataSize && (Comstat.fCtsHold == FALSE)) {
break;
}
std::cout << "stack over";
switch (dwErrorMask) {
case CE_BREAK: {
std::cout << "break";
break;
}
case CE_RXPARITY: {
std::cout << "parity";
break;
}
case CE_RXOVER: {
std::cout << "over";
break;
}default: {
std::cout << "unknown";
break;
}
}
return false;
}
// 1バイトデータを送信
if (WriteFile(arduino, &data, sizeof(data), &dwSendSize, NULL) == 0) {
std::cout << "SEND FAILED";
return false;
}
return true;
}
特に解説することはありませんが、シリアル通信ではReadFile/WriteFile関数を使用し、ファイルハンドルに初期化したarduinoのハンドルをいれます。
受信
arduinoとGUIプログラムでは動作速度が違いますが、今回はRGBの3つの信号を順番通り送る必要があります。このままではデータの先頭がわからなくなります。なのでarduinoとGUIプログラムで同期をとる必要があります。そこでsend
関数で1バイトデータを送り、GUIプログラムは1バイト受信するまで待機するとった流れになります。
まずはwindowsからどれだけデータが送られてきたかを確認します
/// @brief データ受信する
/// @return 受信したデータ数
DWORD available() {
DWORD dwErrors;
COMSTAT ComStat;
DWORD dwCount;
// COMポートの状況取得
ClearCommError(arduino, &dwErrors, &ComStat);
switch (dwErrors) {
case 0: {
break;
}
case CE_BREAK: {
std::cout << "break";
return 0;
break;
}
case CE_RXPARITY: {
std::cout << "parity";
return 0;
break;
}
case CE_RXOVER: {
std::cout << "over";
return 0;
break;
}default: {
std::cout << "unknown";
return 0;
break;
}
}
// 未処理のデータ数
dwCount = ComStat.cbInQue;
return dwCount;
}
また先頭1バイトだけ読み取ります。
/// @brief データの読み取り
/// @return 受信したデータの先頭1バイト
int read() {
int buf = -1;
auto count = available();
// データを受信していない場合は-1を返却
if (count == 0) {
return buf;
}
DWORD rc;
// 先頭1バイトをbufに格納
if (ReadFile(arduino, &buf, 1, &rc, NULL) == 0) {
std::cout << "failed to recieve";
std::cout << GetLastError();
}
return buf;
}
終了処理
最後にarduinoとの通信を遮断します。ここも特に解説することはありません。
おわりに
突貫で書いた記事なのでいろいろ抜けがありそうですが、とりあえずカレンダー初日の記事です。なんかあればコメント、編集リクエストください。