rogy Advent Calendar 2015も余すところ二つとなりました。どうもこんにちはめかとろの玩具箱の州すけです。

PCとマイコンの間の通信は組み込みでのデバッグでとても便利です。そこでよく使われる通信方式がRS232Cという方式でマイコン側はUARTを利用して送受信を行います。PC側のソフトはというと、組み込み向けの本には「ターミナルソフトを使ってね」とTeraTermやHyperTerminalが挙げられます。
これらのソフトウェアを利用すれば手軽にマイコンとPCが通信できるのですが、受信したデータをグラフにしたりPCで計算したデータをマイコンに送信したりするのにはひと手間かかります。

そこで他の処理と組み合わせられるようにRS232C通信のプログラムを書こうというわけです。

RS232Cプログラムを書く方法ですが、
・C#やVBで.NETFrameworkを利用する
・C++でWin32APIを利用する
の二つの方法があるようですが、私がCしか触れたことがないのでWin32APIをつかって作ります。
またWindowsアプリケーションはよくわからないのでコンソールアプリケーションを想定しています。

全体の流れは
1.ファイルとしてCOMポートを開く
2.送信バッファの設定
3.送受信バッファの初期化
4.COMポート構成情報の初期化
5.タイムアウトの設定
でRS232C通信が使えるようになります。
今回の通信の仕様は
RTS,CTSフロー制御なし
9600bps
データサイズ8bit
パリティビットなし
ストップビット1bit
main()関数内での受信処理
です。

まずはいくつかのヘッダファイルを読み込んでおきましょう。
#include <string>//良く知らん
#include <stdio.h>//printfなどの標準入出力関数を使うためのヘッダファイル
#include <windows.h>//Wi32APIを使うためのヘッダファイル
#include <tchar.h>//_T()使うのに要る

1.COMポートを開く
 HANDLE hComPort;//COMポートのハンドルいろいろ使うのでグローバル変数にしておくとよい
 hComPort = CreateFile(    //ファイルとしてポートを開く
      _T("COM4"),      // ポート名を指すバッファへのポインタ:COM4を開く(デバイスマネージャでどのポートが使えるか確認)
      GENERIC_READ | GENERIC_WRITE, // アクセスモード:読み書き両方する
      0,        //ポートの共有方法を指定:オブジェクトは共有しない
      NULL,       //セキュリティ属性:ハンドルを子プロセスへ継承しない
      OPEN_EXISTING,     //ポートを開き方を指定:既存のポートを開く
      0,   //ポートの属性を指定:同期 非同期にしたいときはFILE_FLAG_OVERLAPPED
      NULL       // テンプレートファイルへのハンドル:NULLって書け
  );
 if (hComPort == INVALID_HANDLE_VALUE){//ポートの取得に失敗
  printf("指定COMポートが開けません.\n\r");
  CloseHandle(hComPort);//ポートを閉じる
 return 0;
 }
 else{
  printf("COMポートは正常に開けました.\n\r");
 }

ポートをファイルとみなしCreateFile()関数を用いて開きます。失敗するとINVALID_HANDLE_VALUEを返します。
ポートの属性は非同期(FILE_FLAG_OVERLAPPED)にした方がいいらしいですが上手くいかなかったので同期通信にしてあります。

2.送受信バッファの設定
 int check;//エラーチェック用の変数
 check = SetupComm(
       hComPort,//COMポートのハンドラ
       1024,//受信バッファサイズ:1024byte
       1024//送信バッファ:1024byte
  );
 if (check == FALSE){
     printf("送受信バッファの設定ができません.\r\n");
  CloseHandle(hComPort);
     return 0;
 }
 else{
      printf("送受信バッファの設定が完了しました.\r\n");
 }

SetupComm()関数を用いて送受信バッファの設定をします。

3.送受信バッファの初期化
 check = PurgeComm(
       hComPort,//COMポートのハンドラ
       PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR//出入力バッファをすべてクリア
 );
 if (check == FALSE){
     printf("送受信バッファの初期化ができません.\r\n");
     CloseHandle(hComPort);
     return 0;
 }
 else{
      printf("送受信バッファの初期化が完了しました.\r\n");
 }
PurgeComm()関数を用いて出入力すべてのバッファをクリアします。

4.COMポート構成情報の初期化
 DCB dcb;//構成情報を記録する構造体の生成
 GetCommState(hComPort, &dcb);//現在の設定値を読み込み
 dcb.DCBlength = sizeof(DCB);//DCBのサイズ
 dcb.BaudRate = 9600;//ボーレート:9600bps
 dcb.ByteSize = 8;//データサイズ:8bit
 dcb.fBinary = TRUE;//バイナリモード:通常TRUE
 dcb.fParity = NOPARITY;//パリティビット:パリティビットなし
 dcb.StopBits = ONESTOPBIT;//ストップビット:1bit

 dcb.fOutxCtsFlow = FALSE;//CTSフロー制御:フロー制御なし
 dcb.fOutxDsrFlow = FALSE;//DSRハードウェアフロー制御:使用しない
 dcb.fDtrControl = DTR_CONTROL_DISABLE;//DTR有効/無効:DTR無効
 dcb.fRtsControl = RTS_CONTROL_DISABLE;//RTSフロー制御:RTS制御なし

 dcb.fOutX = FALSE;//送信時XON/XOFF制御の有無:なし
 dcb.fInX = FALSE;//受信時XON/XOFF制御の有無:なし
 dcb.fTXContinueOnXoff = TRUE;// 受信バッファー満杯&XOFF受信後の継続送信可否:送信可
 dcb.XonLim = 512;//XONが送られるまでに格納できる最小バイト数:512
 dcb.XoffLim = 512;//XOFFが送られるまでに格納できる最小バイト数:512
 dcb.XonChar = 0x11;//送信時XON文字 ( 送信可:ビジィ解除 ) の指定:XON文字として11H ( デバイス制御1:DC1 )
 dcb.XoffChar = 0x13;//XOFF文字(送信不可:ビジー通告)の指定:XOFF文字として13H ( デバイス制御3:DC3 )

 dcb.fNull = TRUE;// NULLバイトの破棄:破棄する
 dcb.fAbortOnError = TRUE;//エラー時の読み書き操作終了:終了する
 dcb.fErrorChar = FALSE;// パリティエラー発生時のキャラクタ(ErrorChar)置換:なし
 dcb.ErrorChar = 0x00;// パリティエラー発生時の置換キャラクタ
 dcb.EofChar = 0x03;// データ終了通知キャラクタ:一般に0x03(ETX)がよく使われます。
 dcb.EvtChar = 0x02;// イベント通知キャラクタ:一般に0x02(STX)がよく使われます

 check = SetCommState(hComPort, &dcb);  //設定値の書き込み
 if (check == FALSE){//エラーチェック
     printf("COMポート構成情報の変更に失敗しました.\r\n");
     CloseHandle(hComPort);
     return 0;
 }
 else{
      printf("COMポート構成情報を変更しました.\r\n");
 }

DCB構造体で構成情報を設定します。今回すべての変数について書き込んでいますが、GetCommState()関数で基本情報を読み込んでいるため変更する部分だけ書き換えればいいです。
ボーレートやデータサイズ、パリティビット、ストップビットなどはマイコン側とあわせてください。
書き換えた後にSetCommState()関数で再設定を行います。

5.タイムアウト時間の設定
 COMMTIMEOUTS TimeOut; // COMMTIMEOUTS構造体の変数を宣言
 GetCommTimeouts(hComPort, &TimeOut); // タイムアウトの設定状態を取得

 TimeOut.ReadTotalTimeoutMultiplier = 0;//読込の1文字あたりの時間:タイムアウトなし
 TimeOut.ReadTotalTimeoutConstant = 1000;//読込エラー検出用のタイムアウト時間
 //(受信トータルタイムアウト) = ReadTotalTimeoutMultiplier × (受信予定バイト数) + ReadTotalTimeoutConstant
 TimeOut.WriteTotalTimeoutMultiplier = 0;//書き込み1文字あたりの待ち時間:タイムアウトなし
 TimeOut.WriteTotalTimeoutConstant = 1000;//書き込みエラー検出用のタイムアウト時間
 //(送信トータルタイムアウト) = WriteTotalTimeoutMultiplier ×(送信予定バイト数) + WriteTotalTimeoutConstant

 check = SetCommTimeouts(hComPort, &TimeOut);//タイムアウト設定の書き換え
 if (check == FALSE){//エラーチェック
     printf("タイムアウトの設定に失敗しました.\r\n");
     CloseHandle(hComPort);
     return 0;
 }
 else{
      printf("タイムアウトの設定に成功しました.\r\n");
 }

構成情報の設定と似た手順です。COMMTIMEOUTS構造体を使い、GetCommTimeouts()関数で基本情報を取得し、変更したのちSetCommTimeouts()関数で書き換えています。各値はよく知りませんがこんな感じが多いです。
ここまででCOMポートの初期設定ができました次に送信してみましょう。

6.送信
 char SendData[] = "17";//送信データの用意
 int SendSize = strlen(SendData)+1;//送信データサイズを取得
 DWORD writeSize;//実際に送信したデータサイズ
 WriteFile(hComPort,SendData,SendSize,&writeSize,NULL);
WriteFile()関数を用いて送信。送信するデータサイズはNULL文字の分+1するのを忘れないように。

7.受信
 int i = 0;
 char RecieveChar[1];//1文字受信のための変数
 char RecieveData[100];//受信文字列
 unsigned long nn;
 while (1) {
       ReadFile(hComPort, RecieveChar, 1, &nn, 0); // シリアルポートに対する読み込み
       if (nn == 1) {
           if (RecieveChar[0] == 10 || RecieveChar[0] == 13) { // '\r'や'\n'を受信すると文字列を閉じる
               if (i != 0) {
                   RecieveData[i] = '\0';//最後にNULL文字を付加して
                   i = 0;//文字列を先頭に戻す
                   printf("%s\n", RecieveData);//受信した文字列を表示
               }
          }
           else {
                RecieveData[i] = RecieveChar[0];//受信した文字を文字列にする
                i++;
           }
       }
 }

ReadFil()関数で1文字ずつ受信して'\r'や'\n'で文字列の切り分けを行っています。
受信のタイミングは分からないのでスレッド受信にすべきなのですが、スレッドに関して未だ理化していないことが多いので今回はwhileで回し続けています。

VisualStudio2013で作り、PIC32MX440FとRS23C通信を行いました。
数字を文字データとして送信しPICが素数かどうか判定して素数だと「~ is a prime! 」と返すプログラムを書いて実行したところ。
無題 153
無事動いております。一応main()の中に上から順にコピーして貼り付けていけば動くはずです。

次回はエラー処理やスレッド受信をできるようにしたいです。