この記事は2018年12月11日に投稿しました。
目次
リンク
1. はじめに
こんにちは、iOSのエディタアプリPWEditorの開発者の二俣です。
今回は業務で使用しているMFCでシリアル通信を行う方法についてです。
2. MFCでシリアル通信を行う
MFCでシリアル通信を行う方法ですが、今回は実装例で示します。
実装例
CSampleSerialDlg.h
#pragma once // CSampleSerialDlg ダイアログ class CSampleSerialDlg : public CDialogEx { // コンストラクション public: CSampleSerialDlg(CWnd* pParent = NULL); // 標準コンストラクター // ダイアログ データ enum { IDD = IDD_SAMPLESERIAL_DIALOG }; // 実装 protected: virtual BOOL OnInitDialog(); virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV サポート // メッセージハンドラ DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedBnSendData(); afx_msg void OnBnClickedBnClear(); afx_msg void OnBnClickedBnClose(); afx_msg LRESULT OnCopyData(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnRecvThreadTerminated(WPARAM wParam, LPARAM lParam); // 値変数 private: CString m_sendData; CString m_recvData; // 内部関数 private: BOOL InitSerial(); BOOL UninitSerial(); BOOL SendData(CString sendData); BOOL StartRecvThread(); static UINT CallRecvThread(LPVOID pParam); void RecvThread(); // 内部変数 private: HANDLE m_hSerial; CWinThread* m_pRecvThread; BOOL m_bIsRecvThreadTerminated; };
CSampleSerialDlg.cpp
#include "stdafx.h" #include "SampleSerial.h" #include "SampleSerialDlg.h" #include "afxdialogex.h" #ifdef _DEBUG #define new DEBUG_NEW #endif // CSampleSerialDlg ダイアログ // ユーザ定義メッセージ #define WM_USER_READ_THREAD_TERMINATE (WM_USER + 100) // 受信スレッド停止メッセージ /** * @brief コンストラクタ */ CSampleSerialDlg::CSampleSerialDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CSampleSerialDlg::IDD, pParent) , m_sendData(_T("")) , m_recvData(_T("")) , m_bIsRecvThreadTerminated(FALSE) { } /** * @brief ダイアログデータを交換する時に呼び出されます。 * * @param [in] pDX CDataExchangeオブジェクトへのポインタ― */ void CSampleSerialDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EN_SEND_DATA, m_sendData); DDX_Text(pDX, IDC_EN_RECV_DATA, m_recvData); } // メッセージマップ BEGIN_MESSAGE_MAP(CSampleSerialDlg, CDialogEx) ON_BN_CLICKED(IDC_BN_SEND_DATA, &CSampleSerialDlg::OnBnClickedBnSendData) ON_BN_CLICKED(IDC_BN_CLEAR, &CSampleSerialDlg::OnBnClickedBnClear) ON_BN_CLICKED(IDC_BN_CLOSE, &CSampleSerialDlg::OnBnClickedBnClose) ON_MESSAGE(WM_COPYDATA, CSampleSerialDlg::OnCopyData) ON_MESSAGE(WM_USER_READ_THREAD_TERMINATE, CSampleSerialDlg::OnRecvThreadTerminated) END_MESSAGE_MAP() // CSampleSerialDlg メッセージ ハンドラー /** * @brief ダイアログの初期化の時に呼び出されます。 * * @return 処理結果 TRUE:成功/FALSE:失敗 */ BOOL CSampleSerialDlg::OnInitDialog() { // 親クラスの関数を呼び出します。 CDialogEx::OnInitDialog(); // シリアル通信の初期化処理を行ないます。 if (!InitSerial()) { // シリアル通信の初期化処理に失敗した場合、終了します。 return FALSE; } // 受信スレッドを開始します。 StartRecvThread(); return TRUE; } /** * @brief 送信ボタンが押下された時に呼び出されます。 */ void CSampleSerialDlg::OnBnClickedBnSendData() { // 入力された送信データを取得します。 UpdateData(TRUE); CString sendData = m_sendData.GetString(); // データを送信します。 SendData(sendData); } /** * @brief クリアボタンが押下された時に呼び出されます。 */ void CSampleSerialDlg::OnBnClickedBnClear() { m_recvData = _T(""); UpdateData(FALSE); } /** * @brief 閉じるボタンが押下された時に呼び出されます。 */ void CSampleSerialDlg::OnBnClickedBnClose() { // シリアル受信スレッドを停止します。 m_bIsRecvThreadTerminated = TRUE; } /** * @brief OnCopyDataメッセージが送信された時に呼び出されます。 * * @param [in] wParam パラメータ * @param [in] lParam パラメータ * @return 処理結果 0:成功/0以外:失敗 */ LRESULT CSampleSerialDlg::OnCopyData(WPARAM wParam, LPARAM lParam) { // 取得した受信データを受信データ表示用テキストボックスに追加します。 COPYDATASTRUCT* pData = (COPYDATASTRUCT*)lParam; char* pRecvData = (char*)pData->lpData; CString recvData; recvData.Format(_T("%s"), pRecvData); CString data = m_recvData.GetString(); m_recvData.SetString(data + recvData); UpdateData(FALSE); return 0; } /** * @brief 受信スレッドが停止した時に呼び出されます。 * * @param [in] wParam パラメータ(未使用) * @param [in] lParam パラメータ(未使用) * @return 処理結果 0:成功/0以外:失敗 * ※ここでは常に成功にしています。 */ LRESULT CSampleSerialDlg::OnRecvThreadTerminated(WPARAM wParam, LPARAM lParam) { // シリアル通信の終了処理を行います。 UninitSerial(); // ダイアログを閉じます。 EndDialog(0); return 0; } // 内部関数 /** * @brief シリアル通信の初期化処理を行います。 * * @return 処理結果 TRUE:成功/FALSE:失敗 */ BOOL CSampleSerialDlg::InitSerial() { // シリアルポートをオープンする。 m_hSerial = CreateFile( _T("COM1"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); // シリアルポートがオープンできない場合 if (m_hSerial == INVALID_HANDLE_VALUE) { // 終了します。 return FALSE; } // シリアルポートの通信条件を取得します。 DCB dcb; ZeroMemory(&dcb, sizeof(dcb)); if (!GetCommState(m_hSerial, &dcb)) { // シリアルポートの通信条件が取得できない場合、終了します。 CloseHandle(m_hSerial); m_hSerial = NULL; return FALSE; } // シリアル通信の入出力バッファをクリアします。 PurgeComm(m_hSerial, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR); // シリアルポートの通信条件を設定します。 dcb.fBinary = TRUE; dcb.BaudRate = 9600; dcb.fParity = FALSE; dcb.Parity = NOPARITY; dcb.ByteSize = 8; dcb.StopBits = ONESTOPBIT; dcb.fOutxCtsFlow = TRUE; dcb.fOutxDsrFlow = FALSE; dcb.fDsrSensitivity = FALSE; dcb.fTXContinueOnXoff = FALSE; dcb.fOutX = FALSE; dcb.fInX = FALSE; dcb.fDtrControl = DTR_CONTROL_ENABLE; dcb.fRtsControl = RTS_CONTROL_HANDSHAKE; dcb.fNull = TRUE; // シリアル通信の設定を行います。 if (!SetCommState(m_hSerial, &dcb)) { // シリアル通信の設定でエラーの場合、終了します。 CloseHandle(m_hSerial); m_hSerial = NULL; return FALSE; } // シリアル通信のタイムアウトパラメータを取得します。 COMMTIMEOUTS timeouts; ZeroMemory(&timeouts, sizeof(timeouts)); if (!GetCommTimeouts(m_hSerial, &timeouts)) { // シリアル通信のタイムアウトパラメータの取得でエラーの場合、終了します。 CloseHandle(m_hSerial); m_hSerial = NULL; return FALSE; } // シリアル通信のタイムアウトパラメータを再設定します。 timeouts.ReadIntervalTimeout = MAXDWORD; timeouts.ReadTotalTimeoutMultiplier = 0; timeouts.ReadTotalTimeoutConstant = 100; timeouts.WriteTotalTimeoutMultiplier = 0; timeouts.WriteTotalTimeoutConstant = 10; if (!SetCommTimeouts(m_hSerial, &timeouts)) { // シリアル通信のタイムアウトパラメータの再設定でエラーの場合、終了します。 CloseHandle(m_hSerial); m_hSerial = NULL; return FALSE; } // シリアル通信の拡張機能を設定します。 EscapeCommFunction(m_hSerial, SETRTS); return TRUE; } /** * @brief シリアル通信の終了処理を行います。 * * @return 処理結果 TRUE:成功/FALSE:失敗 * ※ここでは常に成功としています。 */ BOOL CSampleSerialDlg::UninitSerial() { // シリアルのハンドルをクローズします。 CloseHandle(m_hSerial); m_hSerial = NULL; return TRUE; } /** * @brief データを送信します。 * * @param [in] sendData 送信するデータ * @return 処理結果 TRUE:成功/FALSE:失敗 */ BOOL CSampleSerialDlg::SendData(CString sendData) { // 送信するデータをCString型からchar型に変換します。 int writeLen = sendData.GetLength(); char* buf = new char[writeLen + 1]; strcpy_s(buf, writeLen + 1, (char*)sendData.GetBuffer()); // データを全て送信するまで繰り返します。 int totalWriteBytes = 0; do { int writeBytes = 0; BOOL bRet = WriteFile(m_hSerial, buf, writeLen - totalWriteBytes, (LPDWORD)&writeBytes, NULL); // エラーまたはデータを送信できなかった場合 if (!bRet || (writeBytes == 0)) { // 終了します。 break; } totalWriteBytes += writeBytes; } while (totalWriteBytes != writeLen); return TRUE; } /** * @brief 受信スレッドを開始します。 * * @return 処理結果 TRUE:成功/FALSE:失敗 */ BOOL CSampleSerialDlg::StartRecvThread() { // 受信スレッドを生成します。 m_pRecvThread = AfxBeginThread(CallRecvThread, (LPVOID)this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED, NULL); // 受信スレッドが生成できない場合 if (!m_pRecvThread) { // エラーで終了します。 return FALSE; } // 受信スレッドを開始します。 m_pRecvThread->m_pMainWnd = this; m_pRecvThread->m_bAutoDelete = TRUE; m_pRecvThread->ResumeThread(); return TRUE; } /** * @brief 受信スレッド本体を呼び出します。 * * @param [in] pParam パラメータ * @return 処理結果 0:成功/0以外:失敗 */ UINT CSampleSerialDlg::CallRecvThread(LPVOID pParam) { // 受信処理はCSampleSerialDlgのメンバ関数で行います。 CSampleSerialDlg* pDlg = dynamic_cast<CSampleSerialDlg*>(reinterpret_cast<CWnd*>(pParam)); if (pDlg) { pDlg->RecvThread(); } return 0; } /** * @brief 受信スレッド本体 * 10ms間隔でポーリングします。 */ void CSampleSerialDlg::RecvThread() { // 受信スレッド停止フラグが未設定の間、繰り返します。 do { // 10ms待ちます。 Sleep(10); // 受信データを1バイト読み出します。 char achar = 0; DWORD dwRead = 0; BOOL bRet = ReadFile(m_hSerial, (LPVOID)&achar, 1, &dwRead, NULL); // 受信データの読み出しでエラーの場合 if (!bRet) { // エラーをクリアして、終了します。 ULONG portError = 0; COMSTAT cs; ZeroMemory(&cs, sizeof(cs)); ClearCommError(m_hSerial, &portError, &cs); break; } // 受信データがある場合 if (dwRead > 0) { // 受信データをUIに反映します。 char recvData[1]; recvData[0] = achar; COPYDATASTRUCT data; memset(&data, 0, sizeof(data)); data.dwData = 0; data.lpData = recvData; data.cbData = sizeof(recvData); this->SendMessage(WM_COPYDATA, 0, (LPARAM)&data); } } while (!m_bIsRecvThreadTerminated); // スレッド停止処理を呼び出します。 this->PostMessage(WM_USER_READ_THREAD_TERMINATE); }
3. おわりに
別の人が作ったコードを元に、シリアル通信部分のみ抜き出しました。
個人的には受信処理はポーリングではなく、イベント待ちなどがいいのですが、イベント待ちのサンプルは今後試したいと思います。
リンク
紹介している一部の記事のコードはGitlabで公開しています。
興味のある方は覗いてみてください。
私が勤務しているニューラルでは、主に組み込み系ソフトの開発を行っております。
弊社製品のハイブリッドOS Bi-OSは高い技術力を評価されており、特に制御系や通信系を得意としています。
私自身はiOSモバイルアプリやウィンドウズアプリを得意としております。
ソフトウェア開発に関して相談などございましたら、お気軽にご連絡ください。
また一緒に働きたい技術者の方も随時募集中です。
興味がありましたらご連絡ください。
EMAIL : info-nr@newral.co.jp / m-futamata@newral.co.jp
TEL : 042-523-3663
FAX : 042-540-1688