見出し画像

二足歩行ロボット動作データ作成支援4

PCプログラムの作成

ArduinoUNOからシリアルで送られてくるADCのデータを角度データに変換して、モニターに表示してBluetoothシリアルでロボットに送信する。(Bluetoothシリアルは、プログラム上からは通常のCOMポートに見える。)画面にグラフィック表示をしたい為、「DXライブラリ」を使用しています。
コンパイラは、Visual Studio Community 2019のC++を使用。2022がリリースされているのを最近知った為、2022では未検証。

PCプログラムメイン画面

メインプログラム

#include "DxLib.h"
#include "DxLib_multi_win.h"							// DxLibにて別コンソールウインドウ(printf等使える。)
#include "my_serial.h"									// シリアル通信
#include "srl_conf.h"									// シリアル通信初期化
#include "DxLib_sub.h"									// DxLibを使った描画サブルーチン
#include "rx_adc.h"										// シリアル受信
////////////////////////////////
// 変数
////////////////////////////////
// 動作モード変数
bool	quit_flg = false;								// メインループから出るフラグ
bool	robot_online = false;							// ロボットとの通信モード
// 送信時間間隔計算用
int		now_time = 0, last_tx_time = 0;					// mS単位の時間
// 角度データ
int		in_angle_data[12];								// 入力角度
int		out_angle_data[12];								// 出力角度
////////////////////////////////
// データ処理関数
////////////////////////////////
// 角度データ初期化
void	init_angle_data(void)
{
	for (int i = 0; i < 12; i++) {
		out_angle_data[i] = in_angle_data[i] = 90;
	}
}
// ADCデータから角度データへ変換(0~1023 → 0~180)
void	adc_to_angle(void)
{
	for (int i = 0; i < 12; i++) {
		in_angle_data[i] = (rx_adc_data[i] / 4) - 38;
		if (in_angle_data[i] < 0)			in_angle_data[i] = 0;
		else if (180 < in_angle_data[i])	in_angle_data[i] = 180;
	}
}
////////////////////////////////
// タスク処理関数
////////////////////////////////
// キーボードパッド入力処理タスク
// F10とF12等Visual Studioでデバックに使っている物がある。
void	key_pad_task()
{
	// プログラム終了?
	if (key_buf[KEY_INPUT_ESCAPE])		quit_flg = true;			// Escキーが押されたら終了
	// ロボットのオンライン/オフラインモード処理
	if (key_buf[KEY_INPUT_F1])			robot_online = true;
	else if (key_buf[KEY_INPUT_F2])		robot_online = false;
	// 終わり
	return;
}
// 角度データ送信処理
void	tx_angle_task(void)
{
	char	ss[80];
	// オフラインなら送信しない
	if (!robot_online)	return;
	// 送信処理
	if ((now_time = GetNowCount()) >= last_tx_time + 200) {			// 200mSごと
		for (int i = 0; i < 12; i++) {								// データ変換コピー
			out_angle_data[i] = in_angle_data[i];
		}
		out_angle_data[0] = 180 - in_angle_data[0];					// 4個データ反転
		out_angle_data[1] = 180 - in_angle_data[1];
		out_angle_data[10] = 180 - in_angle_data[10];
		out_angle_data[11] = 180 - in_angle_data[11];
		// 送信
		sprintf_s(ss, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,\n",		// 文字変換ここで改行付加
			out_angle_data[0], out_angle_data[1],
			out_angle_data[2], out_angle_data[3],
			out_angle_data[4], out_angle_data[5],
			out_angle_data[6], out_angle_data[7],
			out_angle_data[8], out_angle_data[9],
			out_angle_data[10], out_angle_data[11]);
		my_srl_fputs(ss, 1);										// 文字列送信改行なし(ID1)
		last_tx_time = now_time;									// 送信時間保存
	}
}
////////////////////////////////
// 表示処理関数
////////////////////////////////
// メーターの表示
void	my_instruments_draw(void)
{
	// 可変サイズメーター全画面表示テスト
	int		m_size = 120;					// メーターサイズ
	// 最大メーター表示数計算
	int		x_max = my_size_x / m_size;		// サイズに対応したx方向の可能表示個数
	int		y_max = my_size_y / m_size;		// サイズに対応したy方向の可能表示個数
	// メーターの表示         size,   y, x, *title,ofst, cw, angle_data
	my_dxlib_instrument_draw(m_size, 1, 2, "LH-R", 0, true, in_angle_data[0]);
	my_dxlib_instrument_draw(m_size, 2, 2, "LH-P", 0, true, in_angle_data[1]);
	my_dxlib_instrument_draw(m_size, 3, 2, "LA-P", 0, true, in_angle_data[2]);
	my_dxlib_instrument_draw(m_size, 4, 2, "LA-R", 0, true, in_angle_data[3]);
	my_dxlib_instrument_draw(m_size, 0, 2, "LA-P", 0, true, in_angle_data[4]);
	my_dxlib_instrument_draw(m_size, 0, 3, "LA-R", 0, true, in_angle_data[5]);
	my_dxlib_instrument_draw(m_size, 0, 0, "RA-R", 0, true, in_angle_data[6]);
	my_dxlib_instrument_draw(m_size, 0, 1, "RA-P", 0, true, in_angle_data[7]);
	my_dxlib_instrument_draw(m_size, 4, 1, "RA-R", 0, true, in_angle_data[8]);
	my_dxlib_instrument_draw(m_size, 3, 1, "RA-P", 0, true, in_angle_data[9]);
	my_dxlib_instrument_draw(m_size, 2, 1, "RH-P", 0, true, in_angle_data[10]);
	my_dxlib_instrument_draw(m_size, 1, 1, "RH-R", 0, true, in_angle_data[11]);
/*
	// 可変サイズメーター全画面表示テスト
	int		m_size = 120;
	// 最大メーター表示数計算
	int		x_max = my_size_x / m_size;
	int		y_max = my_size_y / m_size;
	// 表示(表示される部分のみ)
	for (int i = 0; i < y_max; i++) {
		for (int j = 0; j < x_max; j++) {
			my_dxlib_instrument_draw(m_size, i, j, "test", 0, true, in_angle_data[j % 12]);
		}
	}
*/
}
// 数値データ文字表示
void	data_disp(void)
{	
	char	ss[80];
	// テキスト画面クリア(フレームバッファ)
	my_dxlib_txt_flush();
	// robot online/offline表示
	if (robot_online)	sprintf_s(ss, "robot : ONLINE");
	else				sprintf_s(ss, "robot : OFFLINE");
	my_dxlib_txt_fputs(0, 100, ss);
	// 出力角度表示
	sprintf_s(ss, "output angle data");															// タイトル
	my_dxlib_txt_fputs(2, 80, ss);
	sprintf_s(ss, "sv00 sv01 sv02 sv03 sv04 sv05 sv06 sv07 sv08 sv09 sv10 sv11");				// サーボ名
	my_dxlib_txt_fputs(3, 80, ss);
	sprintf_s(ss, "LH-R LH-P LA-P LA-R LS-P LS-R RS-R RS-P RA-R RA-P RH-P RH-R");				// 関節/軸名
	my_dxlib_txt_fputs(4, 80, ss);
	sprintf_s(ss, " %03d  %03d  %03d  %03d  %03d  %03d  %03d  %03d  %03d  %03d  %03d  %03d",	// データ
		out_angle_data[0], out_angle_data[1], out_angle_data[2], out_angle_data[3],
		out_angle_data[4], out_angle_data[5], out_angle_data[6], out_angle_data[7],
		out_angle_data[8], out_angle_data[9], out_angle_data[10], out_angle_data[11]);
	my_dxlib_txt_fputs(5, 80, ss);
	/*
	// 全画面文字表示テスト
	// 最大文字数計算
	int		x_max = my_size_x / 9;
	int		y_max = my_size_y / 14;
	// 表示(表示される部分のみ)
	for (int i = 0; i < y_max; i++) {
		for (int j = 0; j < x_max; j++) {
			my_dxlib_txt_fputc(i, j, '0');	// 半角キャラクタ
		}
	}
	*/
}
// Esc, funcキー機能説明文字描画
void	my_dxlib_func_keys_text_draw(void)
{
	// Escキー
	if (!my_dxlib_func_key_text_draw(0, "QUIT"))		return;					// 書けなくなったら終わり

	// F1キー
	if (!my_dxlib_func_key_text_draw(2, "ONLINE"))		return;					// 書けなくなったら終わり
	// F2キー
	if (!my_dxlib_func_key_text_draw(3, "OFFLINE"))		return;					// 書けなくなったら終わり
}
////////////////////////////////
// メイン関数
////////////////////////////////
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
	// DxLib及び表示関係初期化
	SetOutApplicationLogValidFlag(FALSE);		// ログLog.txtを出力しない。
	init_DxLib_multi_console_win();				// 別コンソールウインドウ初期化(printf使用可、DxLibウィンドウモード)
	DxLib_Init();								// DXライブラリ初期化
	init_my_dxlib_graph_mode(3);				// 解像度設定
   SetDrawScreen(DX_SCREEN_BACK);				// DXライブラリー描画先を裏画面に設定
	// その他初期化
	if (!my_srl_open_by_file()) {				// シリアル初期化
		// DxLib及び表示関係解放
	    DxLib_End();							// DXライブラリ終了処理
	    close_DxLib_multi_console_win();		// 別コンソールウィンドウ解放
   	return 0;
	}
	rx_adc_task_init();							// ADC受信処理初期化
	init_angle_data();							// 角度データ初期化
	// スタートアップ
	// アイドルループ
   while (1) {
		// 入力処理
		get_key_pad();							// キーボードとジョイパッド状態保存
		// 各種タスク
		key_pad_task();							// キーボードとジョイパッド入力対応処理
		rx_adc_task();							// ADC受信処理
		adc_to_angle();							// ADCデータから角度データへ変換
		tx_angle_task();						// 角度送信タスク
		data_disp();							// robot : ONLINE/OFFLINE 表示と数値データ表示
		// DxLibのメインウィンドウ表示処理
       ClsDrawScreen();						// 描画画面クリア(オフスクリーンバッファー)
		my_instruments_draw();					// メーターの表示
		my_dxlib_txt_draw();					// 文字表示
		my_dxlib_func_keys_draw();				// ファンクションキー描画
		my_dxlib_func_keys_text_draw();			// ファンクションキー機能説明描画
       ScreenFlip();							// 画面フリップ
		// ウインドウズのメッセージ処理
		if (ProcessMessage() == -1)	break;
		// フラグチェック
		if (quit_flg)				break;
	}
	// 解放
	my_srl_close(0);							// シリアルID0クローズ
	my_srl_close(1);							// シリアルID1クローズ
	// DxLib及び表示関係解放
   DxLib_End();								// DXライブラリ終了処理
   close_DxLib_multi_console_win();			// 別コンソールウィンドウ解放
   return 0;
}

別ウインドウでコンソールを表示させる。"DxLib_multi_win.h"
「この記事」を参照させていただき作成しています。

#if !defined(_DXLIB_MULTI_WIN_H_)
#define _DXLIB_MULTI_WIN_H_
////////////////////////////////
// 変数
////////////////////////////////
FILE* out = 0;			// stdout
FILE* in = 0;			// stdin
////////////////////////////////
// 初期化関数
////////////////////////////////
// DxLibにて別コンソールウインドウ初期化(コンソールにてprintf等使える。DxLibウィンドウモードも設定)
bool	init_DxLib_multi_console_win(void)
{
	// DXライブラリーとは別にコンソールウインドウ初期化
   bool	ready = AllocConsole();				// Win APIコンソール初期化
	if (ready) {
   	freopen_s(&out, "CON", "w", stdout);	// stdout
   	freopen_s(&in, "CON", "r", stdin);		// stdin
   	// DXライブラリーウィンドウモード
   	ChangeWindowMode(TRUE);					// DxLib ウィンドウモード
	}
	return ready;
}
// コンソール解放
bool	close_DxLib_multi_console_win(void)
{
	bool	ready = true;
   fclose(out); fclose(in); FreeConsole();
	return ready;
}
#endif	// _DXLIB_MULTI_WIN_H_

シリアル通信用のプログラム。"my_serial.cpp"と"my_serial.h"
「この記事」を参照させていただき作成しています。

#include <windows.h>
#include <stdio.h>                  // for sprintf, EOF
#include "my_serial.h"

////////////////////////////////
//マクロ
////////////////////////////////

////////////////////////////////
// 変数
////////////////////////////////
// シリアルポート関係変数
// 全体
HANDLE  port_handle[PORT_ID_MAX];   // シリアルポート       (HANDLEは任意型式へのポインタ)
DCB     dcb[PORT_ID_MAX];           // デバイス制御ブロック (DCBは構造体)
char    err_mes_str[80];            // エラーメッセージ表示用
// 初期化

// 送受信
COMSTAT ComStat[PORT_ID_MAX];       // シリアル通信の状態を受け取る構造体

////////////////////////////////
// 初期化関数
////////////////////////////////
// シリアルポート初期化
bool    my_srl_open(int port_id, int com_port_num, DWORD baud, BYTE my_bytesize, BYTE my_parity, BYTE my_stopbits)
{
   char com_port_str[20];
   bool    ready;            // 汎用シリアルAPIエラーフラグ(APIが成功すると0以外。bool型なので0以外はtrue(=1)に変換される)
   DWORD   lastError;
   // 引数エラーチェック
   if (port_id >= PORT_ID_MAX) {
       sprintf_s(err_mes_str, "ERR: port_id=%d\n", port_id); return false;
   }
   // シリアルポート初期化
   sprintf_s(com_port_str, "\\\\.\\COM%d", com_port_num);        // 文字列へ変換、又ポート名がCOM10以降対応の本来\\.\COMXXが特殊記号の為"\\\\.\\COMXX"になる。(COM1~9でも使える。)
   port_handle[port_id] = CreateFile(com_port_str, (GENERIC_READ | GENERIC_WRITE), 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);     //ポート設定
   if (port_handle[port_id] == INVALID_HANDLE_VALUE) {
       // エラー処理(ここではCreateFile()が失敗しているからCloseHandle()する必要は無い。)
       lastError = GetLastError(); sprintf_s(err_mes_str, "CreateFile(): error=%lu, 0x%08X\n", lastError, lastError);
       return false;
   }
   //バッファ初期化
   ready = SetupComm(port_handle[port_id], 1024, 1024);                                                                                 // バッファ確保(ハンドル, IN, OUT)
   if (!ready) {
       // エラー処理
       lastError = GetLastError(); sprintf_s(err_mes_str, "SetupComm(): error=%lu, 0x%08X\n", lastError, lastError);
       CloseHandle(port_handle[port_id]);
       return ready;
   }
   // 入出力バッファー破棄と未処理の読み書き処理中止
   ready = PurgeComm(port_handle[port_id], PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
   if (!ready) {
       // エラー処理
       lastError = GetLastError(); sprintf_s(err_mes_str, "PurgeComm(): error=%lu, 0x%08X\n", lastError, lastError);
       CloseHandle(port_handle[port_id]);
       return ready;
   }
   // 指定した通信デバイスの設定をデバイス制御ブロックに格納する
   GetCommState(port_handle[port_id], &dcb[port_id]);                                                                                  // DCB指定して通信デバイスを構成する
   dcb[port_id].DCBlength = sizeof(DCB);
   dcb[port_id].BaudRate = baud;
   dcb[port_id].fBinary = TRUE;
   dcb[port_id].ByteSize = 8;
   dcb[port_id].fParity = my_parity;
   dcb[port_id].StopBits = my_stopbits;
   // デバイス制御ブロックに従って通信デバイスを構成する。
   ready = SetCommState(port_handle[port_id], &dcb[port_id]);
   if (!ready) {
       // エラー処理
       lastError = GetLastError(); sprintf_s(err_mes_str, "SetCommState(): error=%lu, 0x%08X\n", lastError, lastError);
       CloseHandle(port_handle[port_id]);
       return ready;
   }
   // シリアルポート初期化ここまで
   sprintf_s(err_mes_str, "Srl Open: port=%s, %dbps\n", com_port_str, baud);
   return ready;
}
// シリアルクローズ
bool    my_srl_close(int port_id)
{
   // 引数エラーチェック
   if (port_id >= PORT_ID_MAX) {
       sprintf_s(err_mes_str, "ERR: port_id=%d\n", port_id); return false;
   }
   // クローズ
   CloseHandle(port_handle[port_id]);
}

////////////////////////////////
// 送受信関数
////////////////////////////////
// シリアル受信
int     my_srl_fgetc(int port_id)
{
   DWORD   dwErrors;                                                       // シリアル通信のエラーマスクを受け取る変数
   BYTE    Rxdata;                                                         // 受信キャラクター(BYTEはunsigned char)
   DWORD   dwRead;
   // 引数エラーチェック
   if (port_id >= PORT_ID_MAX) {
       sprintf_s(err_mes_str, "ERR: port_id=%d\n", port_id); return EOF;
   }
   // 受信チェック
   ClearCommError(port_handle[port_id], &dwErrors, &ComStat[port_id]);     // 通信エラー情報取得(受信文字数等の情報も取得)
   DWORD   dwCount = ComStat[port_id].cbInQue;                             // 受信文字数チェック
   if (dwCount > 0) {
       bool    ready = ReadFile(port_handle[port_id], &Rxdata, sizeof(Rxdata), &dwRead, NULL);     // rxdataがBYTEなので1文字受信
       if (!ready) {
           // こんなかんじ。加えて、FormatMessage APIを使えば説明付きで情報が得られる。
           DWORD   lastError = GetLastError();
           sprintf_s(err_mes_str, "ReadFile(): error=%lu, 0x%08X\n", lastError, lastError);
           return EOF;
       }
       return (int)Rxdata;
   }
   return EOF;
}
// シリアル送信(送信はEOFを使っていない為、charを使ってもOK)
int     my_srl_fputc(BYTE ch, int port_id)
{
   DWORD   dwWrite;
   // 引数エラーチェック
   if (port_id >= PORT_ID_MAX) {
       sprintf_s(err_mes_str, "ERR: port_id=%d\n", port_id); return EOF;
   }
   // 送信
   WriteFile(port_handle[port_id], &ch, sizeof(ch), &dwWrite, NULL);
   return (int)ch;
}
// 0終端を持つ文字列を送信(fputsは改行なしなのでなし)
int     my_srl_fputs(char *ss, int port_id)
{
	while (*ss != '\0') my_srl_fputc(*ss++, port_id);
	return 0;
}
// バイナリーデータ列送信
int     my_srl_bin_fputs(char *ss, int port_id, int n)
{
	int i;
	for (i = 1; i <= n; i++) {
		my_srl_fputc(*ss++, port_id);
	}
	return i;
}
#if !defined(_MY_SERIAL_H_)
#define _MY_SERIAL_H_
////////////////////////////////
// マクロ
////////////////////////////////
#define	PORT_ID_MAX	2
////////////////////////////////
// 外部参照変数
////////////////////////////////
extern	char	err_mes_str[80];               // エラーメッセージ表示用(外から書き換えてバッファーオーバーフローしない様にして下さい)
////////////////////////////////
// 関数プロトタイプ
////////////////////////////////
// シリアルポート初期化
extern	bool    my_srl_open(int port_id, int com_port_num, DWORD baud, BYTE my_bytesize, BYTE my_parity, BYTE my_stopbits);
// シリアルクローズ
extern	bool    my_srl_close(int port_id);
// シリアル受信
extern	int     my_srl_fgetc(int port_id);
// シリアル送信(送信はEOFを使っていない為、charを使ってもOK)
extern	int     my_srl_fputc(BYTE ch, int port_id);
// 0終端を持つ文字列を送信(fputsは改行なしなのでなし)
extern	int     my_srl_fputs(char *ss, int port_id);
// バイナリーデータ列送信
extern	int     my_srl_bin_fputs(char *ss, int port_id, int n);

#endif	// _MY_SERIAL_H_

テキストファイルにオープンするシリアルの設定を記述してシリアルをオープンするプログラム"srl_conf.h"と設定例ファイル"serial_conf.txt"
自分の環境では、ArduinoUNOかCOM4になり、ロボットのBluetoothシリアルがCOM5に割り振られていた。

#if !defined(_SRL_CONF_H_)
#define _SRL_CONF_H_

#include <fstream>          // ファイル操作関係
#include <string>           // string型操作、ファイル操作に使用
#include <cstdlib>			// for atoi()
// シリアル2ポートをCSVファイル(serial_conf.txt)を読んで初期化
// Visual Studioのデバック環境だとプロジェクトファイルと同じディレクリー?がカレントになる。
// Debug/ReleaseフォルダのEXEファイルを実行すると、Debug/Releaseフォルダがカレントになる。
bool	my_srl_open_by_file(void)
{
	// 2行x5項目x10文字 カンマ区切り
	char	srl_para[2][5][10];
	for (int i = 0; i < 2; i++) {				// 行
		for (int j = 0; j < 5; j++) {			// 項目
			for (int k = 0; k < 10; k++) {		// 文字
				srl_para[i][j][k] = '\0';		// 配列初期化
			}
		}
	}
   // ファイル初期化
   std::ifstream fin;
   std::string filename = "serial_conf.txt";
   // ファイルオープン
   fin.open(filename, std::ios::in);
	if (!fin)	return false;					// エラー
	// 読込
	int	i, j, k;	i = j = k = 0;
	char	ch;		bool	l_flg = true;
	do {
		if ((ch = fin.get()) == EOF)	l_flg = false;		// ファイル終わったら終了
		else if (ch == '\n') {								// 改行
			i++;	j = k = 0;								// 行を進めて、項目と文字インデックスを0
			if (i >= 2)					l_flg = false;		// 今回は、2行が終わったら終了
		}
		else if (ch == ',') {								// カンマ
			j++;	k = 0;									// 項目進めて、文字インデクスを0(仮に今回の5項目目を超えてもOKにした。改行待ち状態)
		}
		else if (j < 5 && k < 9) {							// 文字数と項目もチェック(文字数は終端文字'\0'ぶん残す。文字数が超えても項目は変えない。カンマ待ち状態)
			srl_para[i][j][k++] = ch;							// 配列書込み
		}
	} while (l_flg);
   fin.close();
	// シリアル初期化
	int com_port_num = atoi(srl_para[0][0]);
	DWORD baud = (DWORD)atoi(srl_para[0][1]);		if (baud < CBR_110 || CBR_256000 < baud)				baud = CBR_9600;
	BYTE bytesize = (BYTE)atoi(srl_para[0][2]);		if (bytesize < 7 || 8 < bytesize)						bytesize = 8;
	BYTE parity = (BYTE)atoi(srl_para[0][3]);		if (parity < NOPARITY || SPACEPARITY < parity)			parity = NOPARITY;
	BYTE stopbits = (BYTE)atoi(srl_para[0][4]);		if (stopbits < ONESTOPBIT || TWOSTOPBITS < stopbits)	stopbits = ONESTOPBIT;
	// Debug
	printf("com_port_num = %d\n", com_port_num);
	printf("baud = %d\n", baud);
	printf("bytesize = %d\n", bytesize);
	printf("parity = %d\n", parity);
	printf("stopbits = %d\n", stopbits);
	my_srl_open(0,									// シリアルID0初期化
		com_port_num,								// com port
		baud,										// BaudRate
		bytesize,									// ByteSize
		parity,										// Parity
		stopbits);									// StopBits

	com_port_num = atoi(srl_para[1][0]);
	baud = (DWORD)atoi(srl_para[1][1]);				if (baud < CBR_110 || CBR_256000 < baud)				baud = CBR_9600;
	bytesize = (BYTE)atoi(srl_para[1][2]);			if (bytesize < 7 || 8 < bytesize)						bytesize = 8;
	parity = (BYTE)atoi(srl_para[1][3]);			if (parity < NOPARITY || SPACEPARITY < parity)			parity = NOPARITY;
	stopbits = (BYTE)atoi(srl_para[1][4]);			if (stopbits < ONESTOPBIT || TWOSTOPBITS < stopbits)	stopbits = ONESTOPBIT;
	// Debug
	printf("com_port_num = %d\n", com_port_num);
	printf("baud = %d\n", baud);
	printf("bytesize = %d\n", bytesize);
	printf("parity = %d\n", parity);
	printf("stopbits = %d\n", stopbits);
	my_srl_open(1,									// シリアルID1初期化
		com_port_num,								// com port
		baud,										// BaudRate
		bytesize,									// ByteSize
		parity,										// Parity
		stopbits);									// StopBits
	// 初期化終了
	return true;
}
#endif	// _SRL_CONF_H_
4, 38400, 8, 0, 0,			// ID0	COM4, 38400bps, 8bit data, No parity, 1 stop bit
5, 115200, 8, 0, 0,			// ID1	COM5, 115200bps, 8bit data, No parity, 1 stop bit
// com port, BaudRate, ByteSize, Parity, StopBits
// 3行目以降は無視。
// Parity
// EVENPARITY:2, MARKPARITY:3, NOPARITY:0, ODDPARITY:1, SPACEPARITY:4
// StopBits
// ONESTOPBIT:0, ONE5STOPBITS:1, TWOSTOPBITS:2
// Visual Studioのデバック環境だとプロジェクトファイルと同じディレクリー?がカレントになる。
// Debug/ReleaseフォルダのEXEファイルを実行すると、Debug/Releaseフォルダがカレントになる。

DXライブラリを利用してグラフィック画面に文字やメーターやファンクションキー等を表示するプログラム"DxLib_sub.h"

#if !defined(_DXLIB_SUB_H_)
#define _DXLIB_SUB_H_

#include <cmath>									// for sin, cos
#define rad(x)	((x) * 3.14159265 / 180.0)			// 度からラジアン
////////////////////////////////
// 変数
////////////////////////////////
// 入力系変数
char	key_buf[256];								// 全てのキーの入力状態
int		pad1_in;									// pad1の入力状態
// 各種表示モード
int		my_dxlib_graph_mode = 0;					// 解像度モード
int		my_size_x = 640, my_size_y = 480;			// 保存した解像度数値
// 表示バッファー
char	dxlid_txt_buf[1080 / 14][(1920 / 9) + 1];	// 文字配列(最大1920x1080pixel)77行 x 213文字(+'\0')デフォルトフォント設定の半角
////////////////////////////////
// 入力系関数
////////////////////////////////
// キーボードとジョイパッドの状態を読んでおく
void	get_key_pad(void)
{
	GetHitKeyStateAll(key_buf);
	pad1_in = GetJoypadInputState(DX_INPUT_PAD1);
	return;
}
////////////////////////////////
// 画面解像度及びモード設定
////////////////////////////////
// ディスプレイの解像度以上になるとフルスクリーン表示の様になっている。(枠やタイトル等が見えない。)
// ただし、ほかのウインドウを手前に表示出来る。
int		init_my_dxlib_graph_mode(int mode)
{
	// 今までのモード/解像度保存
	int mode_n1 = my_dxlib_graph_mode;
	int	last_x = my_size_x, last_y = my_size_y;
	// モード確認/設定
	if (0 <= mode && mode <= 5)	my_dxlib_graph_mode = mode;
	else						my_dxlib_graph_mode = mode_n1;
	// 解像度選択 
	my_size_x = 640;	my_size_y = 480;
	if (my_dxlib_graph_mode == 1)		{my_size_x = 800;	my_size_y = 600;}
	else if (my_dxlib_graph_mode == 2)	{my_size_x = 1024;	my_size_y = 768;}
	else if (my_dxlib_graph_mode == 3)	{my_size_x = 1280;	my_size_y = 720;}
	else if (my_dxlib_graph_mode == 4)	{my_size_x = 1280;	my_size_y = 1024;}
	else if (my_dxlib_graph_mode == 5)	{my_size_x = 1920;	my_size_y = 1080;}
	// 解像度設定
	int retdt = SetGraphMode(my_size_x, my_size_y, 32);			// 32bitColor固定
	if (retdt == DX_CHANGESCREEN_RETURN) {						// 失敗戻された。
		my_dxlib_graph_mode = mode_n1;
		my_size_x = last_x;	my_size_y = last_y;
	}
	else if (retdt == DX_CHANGESCREEN_DEFAULT) {				// 失敗標準に設定された。
		my_dxlib_graph_mode = 0;
		my_size_x = 640;	my_size_y = 480;
	}
	// ウィンドウモードタイトル
	char	ss[80];
	sprintf_s(ss, "graph_mode = %d: Size X = %d, Size Y = %d, 32bit Color", my_dxlib_graph_mode, my_size_x, my_size_y);
	SetMainWindowText(ss);
	// 終わり
	return retdt;
}
////////////////////////////////
// 文字表示
////////////////////////////////
void	my_dxlib_txt_flush(void)
{
	// 最大文字数計算
	int		x_max = my_size_x / 9;
	int		y_max = my_size_y / 14;
	// バッファークリア(表示される部分のみ)
	for (int i = 0; i < y_max; i++) {
		for (int j = 0; j < x_max; j++) {
			dxlid_txt_buf[i][j] = ' ';		// スペースキャラクタ
		}
		dxlid_txt_buf[i][x_max] = '\0';		// 行ごとの終端
	}
}
bool	my_dxlib_txt_fputc(int y, int x, char ch)
{
	// 最大文字数計算
	int		x_max = my_size_x / 9;
	int		y_max = my_size_y / 14;
	// バッファーオーバーフロー対策
	if ((y < 0 || (y_max - 1) < y) || (x < 0 || (x_max - 1) < x))	return false;
	// 書込み
	dxlid_txt_buf[y][x] = ch;										return true;
}
// 0終端を持つ文字列を書込み(改行なし出来ない)
bool	my_dxlib_txt_fputs(int y, int x, char *ss)
{
	while (*ss != '\0')	my_dxlib_txt_fputc(y, x++, *ss++);
	return true;
}
// 画面一括描画
bool	my_dxlib_txt_draw(void)
{
	// 最大文字数計算
	int		y_max = my_size_y / 14;
	// 表示(表示される部分のみ)
	for (int i = 0; i < y_max; i++) {
   	DrawString(0, i * 14,				// 文字列を書く x, y,
			dxlid_txt_buf[i],				// 文字列
			GetColor(255, 255, 255));		// カラー
	}
	return true;
}
////////////////////////////////
// メーターの表示
////////////////////////////////
// 大きさ可変m_size x m_size pixel、表示場所横X(0~X-1)個x縦Y(0~Y-1)個、90度(中央)のオフセット角度、回転方向、角度(0~180)
bool	my_dxlib_instrument_draw(int m_size, int y, int x, char *title, int offset, bool cw, int data)
{
	// 最小30pixel~最大メーターサイズをウインドウの縦のサイズまでにする。なんとか見えるのが40pixelくらいから。
	if (m_size < 30 || my_size_y < m_size)		return false;
	// 最大メーター表示数計算
	int		x_max = my_size_x / m_size;
	int		y_max = my_size_y / m_size;
	// エラーチェック(表示場所のみ)
	if ((y < 0 || (y_max - 1) < y) || (x < 0 || (x_max - 1) < x))	return false;
	// 座標計算
	int		x0 = x * m_size, y0 = y * m_size;											// 左上の座標
	int		x1 = (x + 1) * m_size, y1 = (y + 1) * m_size;								// 右下の座標に+1
	int		x05 = (x * m_size) + (m_size / 2), y05 = (y * m_size) + (m_size / 2);		// 中央の座標
	double	r1 = m_size / 3.0, r2 = m_size / 4.0;										// メーター半径、目盛り内側と中心との距離
	// 全体の枠
	DrawBox(x0, y0, x1, y1, GetColor(255, 255, 255), FALSE);
	// メーター枠の円
	DrawCircle(x05, y05, (int)(r1), GetColor(255, 255, 255), FALSE);
	// 目盛り
	DrawLine(x05 + (int)(sin(rad(offset)) * r2), y05 - (int)(cos(rad(offset)) * r2),
			x05 + (int)(sin(rad(offset)) * r1), y05 - (int)(cos(rad(offset)) * r1),
			GetColor(255, 255, 255));
	DrawLine(x05 + (int)(sin(rad(offset - 90)) * r2), y05 - (int)(cos(rad(offset - 90)) * r2),
			x05 + (int)(sin(rad(offset - 90)) * r1), y05 - (int)(cos(rad(offset - 90)) * r1),
			GetColor(255, 255, 255));
	DrawLine(x05 + (int)(sin(rad(offset + 90)) * r2), y05 - (int)(cos(rad(offset + 90)) * r2),
			x05 + (int)(sin(rad(offset + 90)) * r1), y05 - (int)(cos(rad(offset + 90)) * r1),
			GetColor(255, 255, 255));
	// 針描画
	int	dt = data;	if (!cw)	dt = (dt * -1) + 180;									// 針の回転方向
	DrawLine(x05, y05,
			x05 + (int)(sin(rad(offset + dt - 90)) * r1), y05 - (int)(cos(rad(offset + dt - 90)) * r1),
			GetColor(255, 255, 255));
	// 文字描画
   DrawString(x0 + 1, y0 + 1, title, GetColor(255, 255, 255));							// タイトル左上
	char	ss[10];	sprintf_s(ss, "%d", data);
	int		StrWidth = GetDrawStringWidth(ss, strlen(ss));
   DrawString(x1 - (StrWidth + 1), y1 - (14 + 1), ss, GetColor(255, 255, 255));		// 角度右下
	// 描画終了
	return true;
}
////////////////////////////////
// ファンクションキーの表示
////////////////////////////////
// 単体の表示
bool	my_dxlib_func_key_draw(int x, char *title, bool on_flg)
{
	// 最大キー表示数計算
	int		x_max = my_size_x / 72;			// 空白1+キー7で72pixel(8文字 * 9pixel)
	// エラーチェック(表示場所のみ)
	if (x < 0 || (x_max - 1) < x)	return false;
	// 座標計算
	int		key_x0 = (x * 72) + 9, key_y0 = ((my_size_y / 14) - 1) * 14;		// キーのBOX左上
	int		key_x1 = (x + 1) * 72, key_y1 = (my_size_y / 14) * 14;				// キーのBOX右下+1
	int		str_x0 = (x * 72) + 27, str_y0 = ((my_size_y / 14) - 1) * 14;		// キーの文字の左上
	// キーの枠描画
	DrawBox(key_x0, key_y0, key_x1, key_y1, GetColor(255, 255, 255), on_flg);	// キーが押されていたら塗りつぶし
	// 文字描画
	unsigned int	str_color;
	if (on_flg)		str_color = GetColor(0, 0, 0);								// キーが押されていたら黒抜き
	else			str_color = GetColor(255, 255, 255);						// 押されてなければ白
	DrawString(str_x0, str_y0, title, str_color);
	// 描画終了
	return true;
}
// ファンクションキーの機能説明
bool	my_dxlib_func_key_text_draw(int x, char *title)
{
	// 最大キー表示数計算
	int		x_max = my_size_x / 72;			// 空白1+キー7で72pixel(8文字 * 9pixel)
	// エラーチェック(表示場所のみ)
	if (x < 0 || (x_max - 1) < x)	return false;
	// 座標計算
	int		str_x0 = (x * 72) + 9, str_y0 = ((my_size_y / 14) - 2) * 14;		// キーの文字の左上
	DrawString(str_x0, str_y0, title, GetColor(255, 255, 255));
	// 描画終了
	return true;
}
// Esc, funcキー全体描画
void	my_dxlib_func_keys_draw(void)
{
	// 書けなくなったら終わり
	if (!my_dxlib_func_key_draw(0, "Esc", (bool)key_buf[KEY_INPUT_ESCAPE]))	return;

	if (!my_dxlib_func_key_draw(2, "F1", (bool)key_buf[KEY_INPUT_F1]))		return;
	if (!my_dxlib_func_key_draw(3, "F2", (bool)key_buf[KEY_INPUT_F2]))		return;
	if (!my_dxlib_func_key_draw(4, "F3", (bool)key_buf[KEY_INPUT_F3]))		return;
	if (!my_dxlib_func_key_draw(5, "F4", (bool)key_buf[KEY_INPUT_F4]))		return;

	if (!my_dxlib_func_key_draw(7, "F5", (bool)key_buf[KEY_INPUT_F5]))		return;
	if (!my_dxlib_func_key_draw(8, "F6", (bool)key_buf[KEY_INPUT_F6]))		return;
	if (!my_dxlib_func_key_draw(9, "F7", (bool)key_buf[KEY_INPUT_F7]))		return;
	if (!my_dxlib_func_key_draw(10, "F8", (bool)key_buf[KEY_INPUT_F8]))		return;

	if (!my_dxlib_func_key_draw(12, "F9", (bool)key_buf[KEY_INPUT_F9]))		return;
	if (!my_dxlib_func_key_draw(13, "F10", (bool)key_buf[KEY_INPUT_F10]))	return;
	if (!my_dxlib_func_key_draw(14, "F11", (bool)key_buf[KEY_INPUT_F11]))	return;
	if (!my_dxlib_func_key_draw(15, "F12", (bool)key_buf[KEY_INPUT_F12]))	return;
}

#endif	// _DXLIB_SUB_H_

ArduinoUNOからADコンバータのデータを受信するプログラム"rx_adc.h"

#if !defined(_RX_ADC_H_)
#define _RX_ADC_H_

#include <cctype>					// for is~
#include <cstdlib>					// for atoi()
////////////////////////////////
// 変数
////////////////////////////////
char	rx_adc_str[16][10];			// 受信キャラクタ
int		rx_adc_str_num;				// 受信中チャンネル
int		rx_adc_str_index;			// 受信中文字数
int		rx_adc_data[16];			// ADC数値データ(12個受信完了する毎にアップデート)
int		rx_adc_sqc;					// 受信処理実行中シーケンス
////////////////////////////////
// 関数
////////////////////////////////
// 変数初期化(文字列関係)
void	rx_adc_init(void)
{
	for (int i = 0; i < 16; i++) {
		for (int j = 0; j < 10; j++) {
			rx_adc_str[i][j] = '\0';
		}
	}
	rx_adc_str_num = 0;
	rx_adc_str_index = 0;
}
// ADC受信処理初期化(シーケンスと数値データ)
void	rx_adc_task_init(void)
{
	rx_adc_sqc = 0;
	for (int i = 0; i < 16; i++) {
		rx_adc_data[i] = 0;
	}
}
// ADC受信処理(定期的に実行する。60Hzで実行する為、受信したデータは全て処理しておく)
void	rx_adc_task(void)
{
	int	ch;											// 一文字受信用バッファ
	while ((ch = my_srl_fgetc(0)) != EOF) {			// 一文字受信
		putchar(ch);								// debug
		// シリアルから文字列を受信して16個分受信したら数値データに変換する
		switch (rx_adc_sqc) {
			case 0:									// 改行待ち(前の文字列の終端)エラー及び初回のみ実行
				if (ch == '\n') {
					rx_adc_init();					// 文字受信バッファー初期化
					rx_adc_sqc++;
				}
				break;
			case 1:									// 文字受信処理
				if (ch == '\n') {					// 改行?
					for (int i = 0; i < 16; i++) {	// データ数値化
						rx_adc_data[i] = atoi(rx_adc_str[i]);
					}
					rx_adc_init();					// 文字受信バッファー初期化
				}
				else if (ch == ',') {				// カンマ?
					if (rx_adc_str_num >= 16)		// エラー(17個目のカンマを受信)
						rx_adc_sqc = 0;				// シーケンス初期化
					else {
						rx_adc_str_num++;			// ブロックを進める
						rx_adc_str_index = 0;		// 文字最初
					}
				}
				else if (isdigit(ch)) {				// 数字なら真
					// 文字保存バッファーオーバーフロー対策入り
					if ((0 <= rx_adc_str_num && rx_adc_str_num <= 15) &&
						(0 <= rx_adc_str_index && rx_adc_str_index <= 8)) {
						rx_adc_str[rx_adc_str_num][rx_adc_str_index++] = ch;
					}
				}
				break;
			default:								// エラー
				rx_adc_sqc = 0;						// エラー/初期化処理へ
				break;
		}
	}
}
#endif	// _RX_ADC_H_


以上。

この記事が気に入ったらサポートをしてみませんか?