●PIC で RS232c通信を行う

PICマイコン自体にシリアル通信を行う専用回路が入っています。
それを使ってパソコンと通信をしてみたいと思います。

UART や USART の回路が入ったPICのデータシートを見ると、確かに、TXとRXの文字が見えます。
Txが送信で、Rxが受信を意味しています。


このRX TXはUSARTもしくはUARTの入出力信号で電源が5Vで動作している場合には、0Vと5Vでビットを表現しており、0Vが0、5Vが1だそうです。
一方、RS232cでは±15Vの電圧がかかり、-3V以下が1、+3V以上が0を表すそうです。

そのため、直接接続する事は出来ないため、変換する部品が必要になってきます。
RS232c TTL変換コンバーターなどがあればそれを接続すると通信が出来ます。

変換コンバータとの接続はこのようにします。
5Vの電源も出力として出ているため回路が簡単になって便利です。
RS232c TTL変換コンバーターを私が購入したのはこちらの商品です。
商品が届くのが待てない時はこちらの方がいいです。

実際に接続した所。

これで、PICでRS232c通信ができるはずです。
後はプログラムを書くだけです。


▼変換コンバータを自作する方法

手元にRS232cとマイコンを接続するために使うMAX3232があったため、それで作成します。
ただ、RS232cのポートがパソコンに無いと接続できません。
最近のパソコンにはないため、USB-RS232変換アダプタが必要になってきます。
まず、MAX3232のデータシートを検索して使い方を調べます。


データシートから見た回路図を実際の回路図に近い形で書き直しました。
使わないTTL側の入力は、正しいかどうか不明ですがフリーは良くないですのでGNDに接続しました。



RS232c端子のピンの位置、2番がRX、3番がTX、4番がDTR、5番がGNDになります。
もう少し詳しい説明はこちら、RS232c

手元にあった適当な部品を集めて作成します。
セラミックコンデンサはすべて0.1μFです。


実際に作成した所


作成したRS232c TTL変換コンバーターとPICを接続します。
5Vを作成した基盤に供給しつつ、PICにも供給しないといけません。
基盤のRXをPICのRB2/TXに接続し、PICの送信側とパソコンの受信側を接続します。
基盤のTXをPICのRB1/RXに接続し、パソコンの送信側とPICの受信側を接続します。


実際に接続してみた所。

これで、PICでRS232c通信ができるハードができました。
後はプログラムを書くだけです。


▼ PIC12F1822 でのシリアル通信

しかししかし、持っている18ピンのPICのデータシートが英語であまりよくわからないと言う事で、日本語データシートがある PIC12F1822 で作成したいと思います。
PIC12F1822 を調べると、RA0がTXでRA1がRXに割り当てられる事が書いてあり、回路図を書き直します。


この回路図に対して、ボーレート9600bpsで送受信するプログラムです。
送られた文字をそのまま返す動きをします。
#include <stdio.h>
#include <stdlib.h>
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = HI        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), high trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include <xc.h>
#define _XTAL_FREQ 16000000

void send(unsigned char c) {
    while (!TXIF);
    TXREG = c;
}

int receive(unsigned char*c) {
    if (RCIF) {
        *c = RCREG;
        return 0;
    }
    return -1;
}

void main() {
    OSCCON = 0b01111010; //クロック周波数16MHz
    ANSELA = 0b00000000; //ポートAをデジタル入力にする
    PORTA = 0b00000000;
    TRISA = 0b00000010;  //RA1は入力にしないと受信できない

    TXCKSEL = 0; // TX:RA0
    RXDTSEL = 0; // RX:RA1

    TXSTA = 0b00100100;
    BRG16 = 1;
    RCSTA = 0b10010000;

    //ボーレート= FOSC /(4([SPBRGHx:SPBRGx] + 1))
    // SPBRGHx:SPBRGx:=((FOSC / ボーレート)/ 4)-1

    const uint16_t DBR = ((uint32_t) ((uint32_t) _XTAL_FREQ / 9600) / 4) - 1;

    SPBRGH = (uint8_t) (DBR >> 8);
    SPBRGL = (uint8_t) (DBR);

    while (1) {
        unsigned char c;
        if (!receive(&c)) {
            send(c);
        }
    }
}

このプログラムを書き込み、先ほどの基盤にPICをセットしてパソコンのRS232cに接続します。
デバイスマネージャを開いて接続されているポート名を確認します。
Windows10の場合には、スタートメニューの検索ボックスに dev と入力するとデバイスマネージャを見つける事ができます。


デバイスマネージャを開き ポート(COM と LTP) の項目をクリックして接続されているポート名を確認します。

私の場合にはUSB接続のCH340 USB-RS232変換アダプタを使いましたので、 COM4 でした(これはパソコンにより変わります)
USB接続の機器でしたら抜き差しするとデバイスマネージャが更新されて見つけやすいです。

ポートに対して何らかのシリアル通信ソフトで接続して文字を送ってみると通信が確認できます。
もし、適当なソフトが無かったら私が20年ぐらい前に適当に作ったインストール不要の RS232c テスト用アプリケーション でも使ってみてください。
ソフトのダウンロードはこちらです。

接続されている ポート名を入力したら → 接続ボタンを押して → 送信文字列の場所になにか半角文字を入力して → 送信ボタン を押す。
そうすると、下の受信文字列に PIC からの文字が表示されるはずです。


▼ PIC16F1827 でのシリアル通信

当初の18ピン用の回路図を使って、18ピンPICである PIC16F1827 用にプログラムを書き換えてみます。
このマイコンは互換性を維持するため内部はほぼ同じ構造をしておりプログラムの違いはほとんどありません。
送られた文字をそのまま返す動きをします。

#include <stdio.h>
#include <stdlib.h>
// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (INTOSC oscillator: I/O function on CLKIN pin)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = ON       // Power-up Timer Enable (PWRT enabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown-out Reset Enable (Brown-out Reset enabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)
// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = OFF      // PLL Enable (4x PLL disabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = HI        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), high trip point selected.)
#pragma config LVP = OFF        // Low-Voltage Programming Enable (High-voltage on MCLR/VPP must be used for programming)

#include <xc.h>
#define _XTAL_FREQ 16000000

void send(unsigned char c) {
    while (!TXIF);
    TXREG = c;
}

int receive(unsigned char*c) {
    if (RCIF) {
        *c = RCREG;
        return 0;
    }
    return -1;
}

void main() {
    OSCCON = 0b01111010; //クロック周波数16MHz
    ANSELA = 0b00000000; //ポートAをデジタル入力にする
    ANSELB = 0b00000000; // ポートBをデジタル入力にする
    PORTA = 0b00000000;
    PORTB = 0b00000000;
    TRISA = 0b00000000;
    TRISB = 0b00000010;  //RB1は入力にしないと受信できない

    TXCKSEL = 0; // TX:RB2
    RXDTSEL = 0; // RX:RB1

    TXSTA = 0b00100100;
    BRG16 = 1;
    RCSTA = 0b10010000;

    //ボーレート= FOSC /(4([SPBRGHx:SPBRGx] + 1))
    // SPBRGHx:SPBRGx:=((FOSC / ボーレート)/ 4)-1

    const uint16_t DBR = ((uint32_t) ((uint32_t) _XTAL_FREQ / 9600) / 4) - 1;

    SPBRGH = (uint8_t) (DBR >> 8);
    SPBRGL = (uint8_t) (DBR);

    while (1) {
        unsigned char c;
        if (!receive(&c)) {
            send(c);
        }
    }
}
このプログラムでも、同様に同じ動きをします。


▼ PIC16F648A でのシリアル通信

当初の18ピン用の回路図を使って、18ピンPICである PIC16F648A 用にプログラムを書き換えてみます。
この古いデバイスである PIC16F648A では、内部クロックモードでは4MHzしか出ず、
送信タイマーの精度が8ビットの為、誤差が多く文字化けが起きてあまりいい通信ができませんでした。
そこで、オシロスコープで波形を確認しながら赤文字部分で調整を行いました。
送られた文字をそのまま返す動きをします。

#include <stdio.h>
#include <stdlib.h>
// CONFIG
#pragma config FOSC = INTOSCCLK
#pragma config WDTE = OFF      
#pragma config PWRTE = ON      
#pragma config MCLRE = OFF     
#pragma config BOREN = ON      
#pragma config LVP = OFF       
#pragma config CPD = OFF       
#pragma config CP = OFF        

#include <xc.h>
#define _XTAL_FREQ 4000000 //クロック周波数

void send(unsigned char c) {
    while (!TXIF);
    TXREG = c;
}

int receive(unsigned char*c) {
    if (RCIF) {
        *c = RCREG;
        return 0;
    }
    return -1;
}

void main() {
    OSCF=1;//クロックを4MHzに設定
    PORTA = 0b00000000;
    PORTB = 0b00000000;
    TRISA = 0b00000000;
    TRISB = 0b00000010; //RB1は入力にしないと受信できない
    
    TXSTA = 0b00100100; //   BRGH=1 高速モード
    RCSTA = 0b10010000;
    
    //BRGH=1でのボーレート= FOSC /(16(ボーレート + 1))
    // SPBRG:=((FOSC /希望ボーレート)/ 16)-1

    const uint16_t DBR = ((uint32_t) ((uint32_t) _XTAL_FREQ / 9600) / 16) - 1;
    SPBRG = (uint8_t) (DBR + 2);

    while (1) {
        unsigned char c;
        if (!receive(&c)) {
            send(c);
        }
    }
}
まずは、9600bpiの時の1ビットの時間を計算します。
1000000 / 9600 = 104.167us
次に、PICのTXにオシロスコープでを接続してタイミングを測ります。


105.6us / 104.167us = 1.013%
誤差は1%前後のようですので問題なく通信ができました。

プログラム中の調整部分 + 2 を + 1 に変更すると、102.2usになり、1.8%ほどの誤差が生じます。
これならまだ通信ができますが、+ 0 にすると誤差が大きくなり、文字化けで使い物になりませんでした。

やはり、新しく改良されたPICの方がいいですね。
古いハードだとなにかと大変です。


■ 文字の出力に printf を使う

シリアル通信に一文字だけの出力では心もとないので、printfで出力できるようにします。
printfが使えると文字列のフォーマットが指定できるので、応用範囲がものすごく広がります。
使えるようにするには、 #include <stdio.h> ヘッダーと1文字を出力する void putch(char c) 関数を追加する必要があります。
上のPIC16F648Aのプログラムを改造してprintf出力にしました。
#include <stdio.h>
#include <stdlib.h>
// CONFIG
#pragma config FOSC = INTOSCCLK
#pragma config WDTE = OFF      
#pragma config PWRTE = ON      
#pragma config MCLRE = OFF     
#pragma config BOREN = ON      
#pragma config LVP = OFF       
#pragma config CPD = OFF       
#pragma config CP = OFF        

#include <xc.h>
#define _XTAL_FREQ 4000000 //クロック周波数


void send(unsigned char c) {
    while (!TXIF);
    TXREG = c;
}
void putch(char c){
    send(c);
}
int receive(unsigned char*c) {
    if (RCIF) {
        *c = RCREG;
        return 0;
    }
    return -1;
}

void main() {
    OSCF=1;//クロックを4MHzに設定
    PORTA = 0b00000000;
    PORTB = 0b00000000;
    TRISA = 0b00000000;
    TRISB = 0b00000010; //RB1は入力にしないと受信できない
    
    TXSTA = 0b00100100; //   BRGH=1 高速モード
    RCSTA = 0b10010000;
    
    //BRGH=1でのボーレート= FOSC /(16(ボーレート + 1))
    // SPBRG:=((FOSC /希望ボーレート)/ 16)-1

    const uint16_t DBR = ((uint32_t) ((uint32_t) _XTAL_FREQ / 9600) / 16) - 1;
    SPBRG = (uint8_t) (DBR+2);

    while (1) {
        static int i;
        __delay_ms(500);
        printf("%d\r\n",i++);
        //send('a');
    }
}
このプログラムを走らせると、シリアル通信で数字をカウントアップします。
なんだか、色々応用できそうな気がしてきました。


▲トップページ > マイコンなど