0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

電通研アドベントカレンダーAdvent Calendar 2022

Day 1

C++でArduinoと通信

Last updated at Posted at 2022-11-30

記念すべき関西大学 電気通信工学研究会のアドベントカレンダー1日目です!!
初日の今日は、先日行われた学園祭でC++でArduinoと通信した話を書こうと思います。

学園祭でした事

今回の学園祭での展示物のひとつとして、子供向けに3色LEDの発光パターンを自分で作って持って帰ろう!!(¥500) という擬似的なプログラミング体験をしてもらいました。GUI上のマウス操作で手元のLEDを見ながら色、順番、点灯時間を操作してもらいました。今回の話はプレビューを手元のLEDで光らせるという話です。

使用技術

GUIプログラムからRGB値をArduinoにシリアル通信で送信しています。なお言語はC/C++です。

Arduinoとシリアル通信

今回はこちらを参考にしました。
Arduinoとの通信の基本的な流れは以下の通りです。

Arduinoとシリアル通信

今回はこちらを参考にしました。
Arduinoとの通信の基本的な流れは以下の通りです。
image.png

初期化

Arduinoと通信するにはCOMポートを使用します。まずはこのCOMポートを初期化しないといけません。

serial_init.cpp
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バイトずつ送ります。

serial_send.cpp
/// @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からどれだけデータが送られてきたかを確認します

reciev.cpp
/// @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バイトだけ読み取ります。

reciev.cpp
/// @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との通信を遮断します。ここも特に解説することはありません。

おわりに

突貫で書いた記事なのでいろいろ抜けがありそうですが、とりあえずカレンダー初日の記事です。なんかあればコメント、編集リクエストください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?