12 August 2012

PIC12F675/690でソフトウエア方式RS232C出力

UART機能を持たないPIC12F675,PIC12F690などで、ソフトウエアUARTを用いてRS232C経由でコンピュータにデータを送る方法。

■ 検証環境
・PIC12F675
・通信方式は2400bps, ストップビット=1bit, パリティ無し
・Microchip MPLAB X IDE ver 1.30
・Microchip MPLAB XC8 ver 1.01

20120812-test-circuit.jpg
ブレッドボード上に作成したテスト回路

20120812-circuit.png
回路図 (MAX232よりPC側の回路は省略)


■ タイミング微調整前後の受信状態の例

2400bpsでの1ビットの信号持続時間は 1/2400 = 0.0004166 → 416マイクロ秒 となる。 プログラム中でこの時間を出すために、__delay_usマクロやタイマーを使うが、その場合の時間設定は416usecではなく、微妙な調整が必要。(コンパイラが吐き出すアセンブラコードの最適化度によっても、あるいはOSCCALがずれてしまっている場合など、個別にあわせこんでいく必要がある)

20120812-pc-receive.png
タイミング調整後(上)と、調整前(下)の受信状況

■ 共通部分

#include <stdio.h> #include <stdlib.h> #include<xc.h> #include "functions.h" __CONFIG(CP_OFF & CPD_OFF & BOREN_OFF & MCLRE_OFF & WDTE_OFF & PWRTE_ON & FOSC_INTRCIO); /* Clock Frequency (use by __delay_ms function) */ #define _XTAL_FREQ 4000000 /* printf関数の出力先のスタブ関数を定義する */ void putch(unsigned char ch){ rs232c_putchar(ch); return; } int main(int argc, char** argv) { TRISIO = 0b00001000; /* IOポートGP0...GP5を出力モードに設定(GP3は常に1,入力専用) */ CMCON = 0x07; /* コンパレータを使わない設定 */ INTCONbits.PEIE = 0; /* 割り込み機能をOFF */ GPIO = 0b00000010; /* RA0...RA5をV-low, TXに接続したGP1はプルアップ */ ANSEL = 0b00000000; /* A/Dポートを無効化(A/Dは初期化後は有効であるため、必ず無効化する) */ for(;;){ rs232c_puts("Test String from PIC12F675\n"); for(unsigned char i=0x20; i<=0xd0; i+=0x10){ if(i==0x80||i==0x90) continue; printf("%02X : ", i); /* 出力はputch()スタブ関数 */ for(unsigned int j=0; j<=0xf; j++){ rs232c_putchar(i+j); } rs232c_puts("\n"); } rs232c_puts("\n"); __delay_ms(500); } }

■ __delay_us (遅延マクロ)を使ったソフトウエアUART

/* RS-232C送信:1文字(1バイト)送信する */ /* 2400bps, stopbit=1bit, parity=off */ /* __delay でタイミングを取る方式 */ void rs232c_send_char_delay(unsigned char ch){ /* start bit */ GPIObits.GP1 = 0; /* TX */ __delay_us(370); /* 2400bpsのタイミングは416マイクロ秒。この関数内でのwaitは360から380の間で確認済み */ for(unsigned int i=0; i<8; i++){ if(ch>>i & 0x01){ GPIObits.GP1 = 1; /* TX */ } else{ GPIObits.GP1 = 0; /* TX */ } __delay_us(370); } /* stop bit */ GPIObits.GP1 = 1; /* TX */ __delay_us(370); __delay_us(10); }


■ プロセッサタイマーを使ったソフトウエアUART

/* RS-232C送信:1文字(1バイト)送信する */ /* 2400bps, stopbit=1bit, parity=off */ /* Timer1でタイミングを取る方式 */ void rs232c_send_char_timer(unsigned char ch){ T1CON = 0b00010001; /* 00-01-000-1 : Timer1設定を1:2プリスケール, Timer ON */ /* (1/4MHz)*4*2PreScale = 2 micro-sec */ /* 2400bpsのタイミングは416マイクロ秒。416/2 = 208タイマーカウント */ /* 65536 - 208 = 65328 = 0xff30 この値が理論値だが、この関数内では190から205カウント(0xff42から0xff33)が許容値 */ TMR1H = 0xff; /* 0xff3a >> 8 */ TMR1L = 0x3a; /* 0xff3a & 0x00ff */ PIE1bits.TMR1IE = 0; /* Timer1割り込みを無効化 */ PIR1bits.TMR1IF = 0; /* Timer1カウンターオーバーフローフラグをクリア */ /* start bit */ GPIObits.GP1 = 0; /* TX */ while(!PIR1bits.TMR1IF) ; /* Timer1のカウントが完了するのを待つ(416マイクロ秒) */ for(unsigned int i=0; i<8; i++){ TMR1H = 0xff; /* 0xff3a >> 8 */ TMR1L = 0x3a; /* 0xff3a & 0x00ff */ PIR1bits.TMR1IF = 0; /* Timer1再スタート */ if(ch>>i & 0x01){ GPIObits.GP1 = 1; /* TX */ } else{ GPIObits.GP1 = 0; /* TX */ } while(!PIR1bits.TMR1IF) ; /* Timer1のカウントが完了するのを待つ(416マイクロ秒) */ } TMR1H = 0xff; /* 0xff3a >> 8 */ TMR1L = 0x3a; /* 0xff3a & 0x00ff */ PIR1bits.TMR1IF = 0; /* Timer1再スタート */ /* stop bit */ GPIObits.GP1 = 1; /* TX */ while(!PIR1bits.TMR1IF) ; /* Timer1のカウントが完了するのを待つ(416ミリ秒) */ T1CONbits.TMR1ON = 0; /* Timer1停止 */ __delay_us(10); }


■ 参考にしたウエブページ
実験2: ソフトウェア・シリアル通信
12F675覚書
12F629、12F675でシリアル通信