ホームに戻る
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;
}