ホームに戻る
 PIC16F88 で RS232C

0、はじめに

RS-232C は9ピンを使って通信します。
接続部分をCOMポートと呼んだり、通信方法をシリアル通信とか言う。
一方、PICでの通信の呼称はUSARTと呼ぶようです。
今回は PIC16F88 の USART 機能を使ってPCと RS-232C で通信します。
いろいろ細かい設定はできそうですが簡単なやり方でやってます。

1、接続端子について

接続端子は9ピンです。
5本あるほうが上、4本のほうが下とすると、
左上から右へ1番→5番となります。
左下から右へ6番→9番です。

1:DCD Date Carrier Detect
2:RxD Recieved Data
3:TxD Transmitted Data
4:DTR Data Terminal
5:SG Signal Ground
6:DSR Data Set Ready
7:RTS Request To Send
8:CTS Clear To Send
9:RI Ring Indicator

2番がPIC→PCであり。
3番がPC→PICです。
5番はGND。
7番が送信要求出力で、8番が送信許可入力。

2番はPICの出力に繋ぎ。
3番はPICの入力に繋ぎます。
5番はPIC側のGNDに繋ぎ。
7番と8番は直結します。
7番と8番を繋ぐことで、送信要求に即許可を出します。

USBと違いPCからの電源供給はできません。

2、ADM3202について

PICの信号はLow:0V、HIGH:5Vです。
ただし、RS-232C のPC側は±12Vの信号になるようです。

この変換にADM3202というICが必要になります。
200円くらいで売っています。
おそらくADM3202はPCとPIC間の変換を見据えて設計されているので、
これを間に入れておけば確実に動作すると思われます。

接続の方法はADM3202の説明書に書いてあります。
5Vでも3.3Vでも動作するようです。

3、通信方式について

簡単な設定として、

 ボーレート:9600bps
 ビット数:8
 パリティ:なし
 ストップビット:1
 フロー制御:なし
 通信方式:非同期

としておきます。

4、PIC

USARTが可能なPICとして今回は PIC16F88 を使います。
主なピンは RB2 が入力、RB5 が出力となります。
今回は内部クロックで 8MHz で使用することにします。

5、PICの初期設定

TXSTA と RCSTA と SPBRG を設定する必要があります。

TXSTA は低速の場合 0x20 、高速の場合 0x24 とします。

低速は 9600bps 未満、高速は9600bps 以上です。

RCSTA は継続受信可の場合は 0x90 とし、不可の場合は 0x80 とします。

SPBRG はボーレートの設定です。

低速の場合、PICの周波数/(64×通信速度)−1
高速の場合、PICの周波数/(16×通信速度)−1

例えば、PICの周波数 8MHz 通信速度 9600bps のとき、

8000000 / (16 * 9600) - 1 = 51.08333

よって、SPBRG には 51 を設定します。

6、PICからの送信

送信は TXSTA の TRMT を調べてバッファが空なら TXREG にデータを入れる。
送信バッファは1段である。
以下のような方法になる。

send
 BSF STATUS,RP0   ; バンク1へ
 BTFSC TXSTA,TRMT ; 送信バッファ空き?
 GOTO send_q      ; 送信バッファが空
 BCF STATUS,RP0   ; バンク0へ
 RETURN

send_q
 BCF STATUS,RP0   ; バンク0へ
 MOVLW 51h        ; 051h = 'Q'
 MOVWF TXREG      ; Wレジスタから送信バッファへ
 RETURN

7、PICの受信

受信は PIR1 の RCIF を調べてバッファにデータがあれば RCREG に取り出す。
受信のバッファは2段。
以下のような方法となる。

loop
 BTFSS PIR1,RCIF ; 受信データあり?
 GOTO loop       ; 受信データなし
 MOVF RCREG,W    ; 受信データ取り出し
 MOVWF PORTA     ; 受信結果をポートAへ
 GOTO loop

8、PCの設定

Win32APIを使用します。
ファイルの入出力と同じような方法で通信が可能です。
PCはPICと違ってバッファが十分確保できるのが特徴です。

9、PC側のCOMの開始

  HANDLE hCom = NULL;

  hCom = CreateFile((LPCSTR)"COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

  if(hCom == INVALID_HANDLE_VALUE){
    return 0;
  }

10、PCの送受信の設定

通信の制御にはハードウェア的な方法とソフトウェア的な方法があります。

ハードウェア的な方法として、

DTR は通信を行う準備要求を送り DSR から可能であるとの信号を受ける。
RTS は通信上のデータの送信要求を送り CTS からの許可を待つ方式。

ソフトウェア的な方法として、

受信バッファがいっぱいなら XOFF を送り受信可能なら XON を送る方式。

以上があります。

  DCB comdcb;

  ZeroMemory(&comdcb, sizeof(DCB));
  comdcb.DCBlength = sizeof(DCB);           // DCBサイズ
  comdcb.wReserved = 0;                     // 予約
  comdcb.BaudRate = 9600;                   // ボーレート(bit Per Second)
  comdcb.fParity = FALSE;                   // パリティ無効
  comdcb.Parity = 0;                        // 0-4:パリティなし、奇数、偶数、マーク、スペース
  comdcb.ByteSize = 8;                      // 4-8:4,5,6,7,8のどれか
  comdcb.StopBits = 0;                      // 0-2:1、1.5、2のどれか
  comdcb.fOutxCtsFlow = FALSE;              // Clear to Send フロー制御なし
  comdcb.fOutxDsrFlow = FALSE;              // Data Set Ready フロー制御なし
  comdcb.fDtrControl = DTR_CONTROL_ENABLE;  // Data Terminal Ready 有効
  comdcb.fRtsControl = RTS_CONTROL_ENABLE;  // Request to Send 有効
  comdcb.fDsrSensitivity = FALSE;           // Data Set Ready 制御なし
  comdcb.fOutX = FALSE;                     // XONなし
  comdcb.fInX = FALSE;                      // XOFFなし
  comdcb.fTXContinueOnXoff = TRUE;          // XON送信後も送信を継続
  comdcb.XonLim = 2048;                     // XONが送られるまでに格納できる最小バイト数
  comdcb.XoffLim = 2048;                    // XOFFが送られるまでに格納できる最小バイト数
  comdcb.XonChar = 0x11;                    // XONキャラクタ
  comdcb.XoffChar = 0x13;                   // XOFFキャラクタ
  comdcb.fBinary = TRUE;                    // バイナリモード
  comdcb.fNull = FALSE;                     // NULLバイトは破棄しない
  comdcb.fAbortOnError = TRUE;              // エラー時は読み書き操作を終了
  comdcb.fErrorChar = FALSE;                // パリティエラー時のキャラクタ置換なし
  comdcb.ErrorChar = 0x00;                  // 置換キャラクタ
  comdcb.EofChar = 0x03;                    // データ終了キャラクタ
  comdcb.EvtChar = 0x02;                    // イベントキャラクタ

  if(!SetCommState(hCom, &comdcb)){
    return 0;
  }

11、タイムアウト

タイムアウトとは送受信が予定通りの時間で終わらなかった場合に、
通信中のエラーがあったと判断してエラーを出す仕組みです。
タイムアウトの時間は環境を考慮して適切に設定する必要があります。

ReadIntervalTimeout は受信と受信の間隔です。単位はミリ秒。
ReadTotalTimeoutMultiplier と ReadTotalTimeoutMultiplier は、
1つの受信について ReadTotalTimeoutMultiplier*予定受信バイト+ReadTotalTimeoutMultiplier ミリ秒待ちます。

  comtout.ReadIntervalTimeout = 500;
  comtout.ReadTotalTimeoutMultiplier  = 0;
  comtout.ReadTotalTimeoutConstant    = 0;
  comtout.WriteTotalTimeoutMultiplier = 0;
  comtout.WriteTotalTimeoutConstant   = 0;

  if(!SetCommTimeouts(hCom, &comtout)){
    return 0;
  }

12、PCからの送信

  DWORD dw;
  char w_buf[2048];

  if(hCom != NULL){
    return WriteFile(hCom, w_buf, lstrlen(w_buf), &dw, NULL);
  }

  return 0;

13、PCの受信

  DWORD dwCount;
  DWORD dw;
  DWORD dwErrors;
  COMSTAT ComStat;
  char r_buf[2048];

  if(hCom != NULL){
    ClearCommError(hCom, &dwErrors, &ComStat);
    dwCount = ComStat.cbInQue;
    if(ReadFile(hCom, r_buf, dwCount, &dw, NULL)){
      pszBuf[dw] = 0;
      return dw; // 受信したデータ数を返す
    }
  }

  return 0;

14、COMの終了

  if(hCom != NULL){
    CloseHandle( hCom );
    hCom = NULL;
  }

15、サンプル

RS232C 通信のサンプルを作ってみました。
その使い方の説明。

PC側は2、3、5ピンを使い、7、8ピンは直結。
PICは PIC16F88 を使用。
PIC側は通信にRB2とRB5を使い。、
PIC側の通信確認のためRA0とRA1の出力をLEDに繋ぎます。
PCとPICの間にADM3202を適切に接続。
電源はPICとADM3202とも同じ5Vを繋ぎます。

PICは無限ループ上で受信を繰り返し受信すれば PORTA に送ります。
また、タイマー0の割り込みを使って1秒おきに1バイト文字 'Q' を送ります。

PCは2つのボタンでそれぞれ違うデータを送信できます。
また、3秒おきに 'Q' をいくつ受信したかを表示します。
プログラム内の COM1 や COM2 を環境によって書き換えてください。

16、PICのプログラム

 LIST P=16F88
 INCLUDE P16F88.INC

 __CONFIG  _CONFIG1, _WDT_OFF & _PWRTE_ON & _INTRC_IO & _LVP_OFF & _MCLR_OFF
 __CONFIG  _CONFIG2, _IESO_OFF & _FCMEN_OFF

 CBLOCK 020h
  W_TEMP
  ST_TEMP
  PL_TEMP
  FS_TEMP
  TMR0_COUNT
 ENDC

 ORG 0
 GOTO init

 ORG 4  ;割込み
 MOVWF W_TEMP
 SWAPF STATUS,W
 CLRF STATUS
 MOVWF ST_TEMP
 MOVF PCLATH,W
 MOVWF PL_TEMP
 CLRF PCLATH
 MOVF FSR,W
 MOVWF FS_TEMP
 BTFSS INTCON,TMR0IF ; タイマー0割り込みで無ければ終了
 GOTO isr_end
 BCF INTCON,TMR0IF  

 MOVLW 0Bh
 MOVWF TMR0 ; タイマ0のリセット
 
 DECFSZ TMR0_COUNT,F ; TMR0_COUNT が0ならスキップ
 GOTO isr_end

 MOVLW D'40'
 MOVWF TMR0_COUNT

 GOTO send_Q ; Q を送信

isr_end
 ; この時点でバンク0であること
 MOVF FS_TEMP
 MOVWF FSR
 MOVWF PL_TEMP
 MOVF PCLATH
 SWAPF ST_TEMP,W
 MOVWF STATUS
 SWAPF W_TEMP,F
 SWAPF W_TEMP,W
 RETFIE

init
 BSF STATUS,RP0 ; バンク1へ
 MOVLW 074h
 MOVWF OSCCON ; 内部クロック 8MHz
 MOVLW 0F0h
 MOVWF TRISA  ; RA0-3は出力
 MOVLW 004h
 MOVWF TRISB  ; RB2は入力
 MOVLW 87h
 MOVWF OPTION_REG ; タイマー0 1:256
 CLRF ANSEL   ; アナログ入出力を使用しない
 MOVLW 024h
 MOVWF TXSTA  ; 非同期、8bit、高速
 MOVLW d'51'
 MOVWF SPBRG  ; 9600bps
 BCF STATUS,RP0 ; バンク0へ
 MOVLW 090h
 MOVWF RCSTA  ; 8bit、連続受信可

 CLRF PORTA
 CLRF PORTB

 MOVLW 0Bh
 MOVWF TMR0   ; 25ms タイマーとする

 MOVLW D'40'
 MOVWF TMR0_COUNT ; 25ms * 40 = 1s をカウント

 BSF INTCON,TMR0IE
 BSF INTCON,GIE ; 割り込みを有効に

; 受信ループ
loop
 BTFSS PIR1,RCIF ; 受信データあり?
 GOTO loop       ; 受信データなし
 MOVF RCREG,W    ; 受信データ取り出し
 MOVWF PORTA     ; 受信結果をポートAへ
 GOTO loop

; 送信
send_Q
 BSF STATUS,RP0   ; バンク1へ
 BTFSC TXSTA,TRMT ; 送信バッファ空き?
 GOTO send        ; 送信バッファが空
 BCF STATUS,RP0   ; バンク0へ
 GOTO send_Q

send
 BCF STATUS,RP0   ; バンク0へ
 CALL SND232      ;文字列の1文字を送信バッファに格納
 GOTO isr_end

SND232
 MOVLW 51h        ; 051h = 'Q'
 MOVWF TXREG      ; Wレジスタから送信バッファへ
 RETURN

 END

17、PCのプログラム

// プログラム中の "COM1" や "COM2" を適当に書き換えてください。

#include <windows.h>
#include <stdio.h>

#define ID_B1      1000
#define ID_B2      1010
#define ID_EDIT1   2000
#define ID_MYTIMER 3000

//ボタンを2個宣言
HWND hButton1, hButton2;

//エディットを1個宣言
HWND hEdit1;

//文字列の保管用
static char txt[57];

// COMポートのハンドル
HANDLE hCom;

// ウィンドウプロシージャの宣言(ウィンドウの動作を規定)
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

int init232(){
  hCom = NULL;
 
  // COM2 ポートを開く 
  hCom = CreateFile((LPCSTR)"COM2", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

  if(hCom == INVALID_HANDLE_VALUE){
    return 0;
  }

  // バッファサイズの設定
  if(!SetupComm(hCom,1024,1024)){ // 入力バッファサイズ、出力バッファサイズの指定
    return 0;
  }

  // バッファのクリア
  PurgeComm(hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

  // ボーレート 9600bps、8ビット、パリティなし
  DCB comdcb;

  ZeroMemory(&comdcb, sizeof(DCB));
  comdcb.DCBlength = sizeof(DCB);           // DCBサイズ
  comdcb.wReserved = 0;                     // 予約
  comdcb.BaudRate = 9600;                   // ボーレート(bit Per Second)
  comdcb.fParity = FALSE;                   // パリティ無効
  comdcb.Parity = 0;                        // 0-4:パリティなし、奇数、偶数、マーク、スペース
  comdcb.ByteSize = 8;                      // 4-8:4,5,6,7,8のどれか
  comdcb.StopBits = 0;                      // 0-2:1、1.5、2のどれか
  comdcb.fOutxCtsFlow = FALSE;              // Clear to Send フロー制御なし
  comdcb.fOutxDsrFlow = FALSE;              // Data Set Ready フロー制御なし
  comdcb.fDtrControl = DTR_CONTROL_ENABLE;  // Data Terminal Ready 有効
  comdcb.fRtsControl = RTS_CONTROL_ENABLE;  // Request to Send 有効
  comdcb.fDsrSensitivity = FALSE;           // Data Set Ready 制御なし
  comdcb.fOutX = FALSE;                     // XONなし
  comdcb.fInX = FALSE;                      // XOFFなし
  comdcb.fTXContinueOnXoff = TRUE;          // XON送信後も送信を継続
  comdcb.XonLim = 512;                      // XONが送られるまでに格納できる最小バイト数
  comdcb.XoffLim = 512;                     // XOFFが送られるまでに格納できる最小バイト数
  comdcb.XonChar = 0x11;                    // XONキャラクタ
  comdcb.XoffChar = 0x13;                   // XOFFキャラクタ
  comdcb.fBinary = TRUE;                    // バイナリモード
  comdcb.fNull = FALSE;                     // NULLバイトは破棄しない
  comdcb.fAbortOnError = TRUE;              // エラー時は読み書き操作を終了
  comdcb.fErrorChar = FALSE;                // パリティエラー時のキャラクタ置換なし
  comdcb.ErrorChar = 0x00;                  // 置換キャラクタ
  comdcb.EofChar = 0x03;                    // データ終了キャラクタ
  comdcb.EvtChar = 0x02;                    // イベントキャラクタ

  if(!SetCommState(hCom, &comdcb)){
    return 0;
  }

  COMMTIMEOUTS comtout;

  comtout.ReadIntervalTimeout = 500;
  comtout.ReadTotalTimeoutMultiplier = 0;
  comtout.ReadTotalTimeoutConstant = 500;
  comtout.WriteTotalTimeoutMultiplier = 0;
  comtout.WriteTotalTimeoutConstant = 500;

  if(!SetCommTimeouts(hCom, &comtout)){
    return 0;
  }

  return 1;
}

void deinit232(){
  if(hCom != NULL){
    CloseHandle(hCom);
    hCom = NULL;
  }
}

// 1バイト送信
int send232(unsigned char uc){
  DWORD dw;

  if(hCom != NULL){
    return WriteFile(hCom, &uc, 1, &dw, NULL);
  }

  return 0;
}

// 受信
int receive232(){
  DWORD dw;
  DWORD dwCount;
  DWORD dwErrors;
  COMSTAT ComStat;
  char buf[256];
  unsigned char r_buf[1024];

  if(hCom != NULL){
    ClearCommError(hCom, &dwErrors, &ComStat);
    dwCount = ComStat.cbInQue;
    if(ReadFile(hCom, r_buf, dwCount, &dw, NULL)){
      if(dw != 0){
        sprintf(buf, "%c:%d", r_buf[0], dwCount);
        SetWindowText(hEdit1, (LPTSTR)buf);
      }
      return dw; // 受信したデータ数を返す
    }
  }

  return 0;
}

//メイン関数
int APIENTRY WinMain(
  HINSTANCE hInstance,  //インスタンスのハンドル
  HINSTANCE hPreInstance, //以前のインスタンスハンドル
  LPSTR cmdLine,  //コマンド行の文字列
  int cmdShow)    //ウィンドウの表示状態
{
  //ウィンドウクラスの宣言
  WNDCLASS wd;

  //ウィンドウクラス名
  char *wdName = "window00";

  //ウィンドウ登録のためのループ
  if (!hPreInstance) {
    //ウィンドウスタイル
    wd.style = CS_HREDRAW | CS_VREDRAW;  //幅、高さの変化に対し再描画する
    //ウィンドウプロシージャ
    wd.lpfnWndProc = (WNDPROC)WndProc;
    //クラスの予備のメモリ領域
    wd.cbClsExtra = 0;
    //ウィンドウの予備のメモリ領域
    wd.cbWndExtra = 0;
    //プログラムのインスタンスハンドル
    wd.hInstance = hInstance;
    //アイコンのハンドル
    wd.hIcon = NULL;
    //カーソルのハンドル
    wd.hCursor = LoadCursor(NULL, IDC_ARROW);
    //バックグラウンドのブラシのハンドル
    wd.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    //メニュー名
    wd.lpszMenuName = NULL;
    //クラス名
    wd.lpszClassName = wdName;

    //ウィンドウの登録(成功 非0、失敗 0)
    if (!RegisterClass(&wd))
      return FALSE;
  }

  //ウィンドウの生成(ハンドルを form00 とする)
  HWND form00 = CreateWindowEx(
    NULL,      //拡張スタイル(なし)
    wdName,      //ウィンドウクラス名
    "window",    //ウィンドウの名前
    WS_OVERLAPPEDWINDOW,  //ウィンドウスタイル(オーバーラップ型)
    CW_USEDEFAULT,    //ウィンドウのX座標(ディフォルト)
    CW_USEDEFAULT,    //ウィンドウのY座標(ディフォルト)
    200,      //ウィンドウの幅(80に設定)
    100,      //ウィンドウの高さ(50に設定)
    NULL,      //親ウィンドウ(この場合自身が親)
    NULL,      //メニュー(なし)
    hInstance,    //プログラムのハンドル
    NULL);      //予備パラメータ

  // RS232C の準備
  init232();

  //ウィンドウの表示
  ShowWindow(form00, cmdShow);

  //ウィンドウの更新
  UpdateWindow(form00);

  //イベントのループ
  MSG msg;
  while (GetMessage(&msg, NULL, NULL, NULL))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  // RS232C の片付け
  deinit232();

  return msg.wParam;
}

// ウィンドウプロシージャ(ウィンドウの動作を規定)
LRESULT CALLBACK WndProc(
  HWND hwnd,
  UINT message,
  WPARAM wParam,
  LPARAM lParam)
  {
  //インスタンスハンドルを得る
  HINSTANCE hInst;
  hInst = (HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE);

  switch (message) {
    //ウィンドウを表示するとき処理
    case WM_CREATE:
      //タイマーのセット
      SetTimer(hwnd, ID_MYTIMER, 3000, NULL);  // 3000 ミリ秒ごと

      //ボタン1の表示
      hButton1 = CreateWindow(
        "BUTTON",
        "send 0x01",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        10,
        10,
        80,
        20,
        hwnd,
        (HMENU)ID_B1,
        hInst
        ,NULL);
      //ボタン2の表示
      hButton2 = CreateWindow(
        "BUTTON",
        "send 0x02",
        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
        100,
        10,
        80,
        20,
        hwnd,
        (HMENU)ID_B2,
        hInst,
        NULL);
      //エディットの表示
      hEdit1 = CreateWindow(
        "EDIT",
        "",
        WS_CHILD | WS_VISIBLE,
        10,
        40,
        170,
        20,
        hwnd,
        (HMENU)ID_EDIT1,
        hInst,
        NULL);
      break;
    // タイマー処理
    case WM_TIMER:
        receive232();
        break;
    //ボタンを押したときの処理
    case WM_COMMAND:
      switch(LOWORD(wParam)){
        //ボタン1を押したとき
        case ID_B1:
          send232(0x01);
          break;
        //ボタン2を押したとき
        case ID_B2:
          send232(0x02);
          break;
        default:
          return(DefWindowProc(hwnd, message, wParam, lParam));
      }
      break;
    //ウィンドウを破壊する時の処理
    case WM_DESTROY:
      //メイン関数のループを終了
      PostQuitMessage(0);
      break;
    //デフォルトの処理に対してデフォルトの処理を返す
    default:
      return DefWindowProc(hwnd, message, wParam, lParam);
  }
  return NULL;
}

inserted by FC2 system