JavaScript が無効です。有効にしてください。
ななよんのホームページ
トップ > 電子工作 > PIC マイコン > PIC16F77 使い方

PIC16F77 使い方

最終更新日さいしゅうこうしんび:2023-08-10

 PIC16F77 は、秋月電子通商において、40 ピンのミッドレンジの中では最も安いマイコンです。2023-03-18 現在 200 円で、40 ピンのハイエンドシリーズでは PIC18F45K20 がもっと安いですが、今回は MCC を使用せずレジスタを叩いてペリフェラルを使う練習として、この 16F77 を選んでみました。16F77 は F1 ファミリではないので MCC は使えません。

 この PIC は、発振回路を外付けする必要があります(内蔵発振がない)。今回はコンデンサ内蔵の 20 MHz のセラミック発振子を使いました。また、/MCLR と VPP のピンが、入力専用ピンとして使えないので、10 kΩ 程度でプルアップする必要があります。

 安価ながら、ADC が 8 ch、PWM が 2 ch、SSP(SPI・I2C スレーブ)と USART が使えます。タイマも 3 つあり、ベースラインではないので割り込みも可能です。

ピン配置図

L チカ

 まず、マイコンの入門や動作確認として定番の「L チカ」をしてみます。16F77 はポートを A、B、C、D、E の 5 つ持っているため、PORT や TRIS のレジスタも5つあります。入力専用ピンはないので、任意のピンが LED ピンとして使えます。なお、PORTA や PORTE を使う場合、デフォルトでアナログになっているので、ADCON1 レジスタを設定する必要があります。今回は PORTA と PORTE をデジタルに設定しましたが、RB0 ピンに LED を接続しました。

 コンフィぎゅーレーションビットの設定方法については、こちらを参照してください。

#include <xc.h>

// 20 MHz の発振子を用いる
#define _XTAL_FREQ 20000000

//////// コンフィギュレーションビットの設定 ////////
#pragma config FOSC = HS        // HS オシレータを使用する
#pragma config WDTE = OFF       // WDT 無効
#pragma config PWRTE = OFF      // PWRTE 無効
#pragma config CP = OFF         // コードプロテクション オフ
#pragma config BOREN = ON       // BOR 有効

void main(void) {
    // デジタルに設定
    ADCON1 = 0b00000111;
    // B ポートを出力に設定
    TRISB = 0;
    // 出力の初期化
    PORTB = 0;
    
    while(1) {
        RB0 = !RB0;
        __delay_ms(500);
    }
}

タイマ割り込みで L チカ

 割り込みとは、何らかのきっかけで一旦メインの処理を中断し、優先度の高い処理を実行することです。割り込み処理が終わったら元の処理に戻ります。文字通り処理が横入りする感じです。PIC のタイマを使うと、一定間隔で割り込みをかけることができます。

 16F77 にはタイマ 0、タイマ 1 とタイマ 2 の 3 つのタイマが入っていて、タイマ 1 は 16 ビットです。3 つのタイマと delay でそれぞれ LED を点滅させてみます。RB0 から RB3 まで、4 つ LED を接続してください。

 割り込みを有効化するには、INTCON レジスタの GIE ビットと PEIE ビットを 1 にします。タイマ 0 の割り込みを使うには、同じく INTCON レジスタの TMR0IF ビットを 1 にします。タイマ 1 とタイマ 2 は、それぞれ PIE1 レジスタの TMR1IE と TMR2IE ビットを 1 にします。なお、タイマ割り込みが入ると、TMR0IF、TMR1IF または TMR2IF ビットがセットされるので、割り込み関数内でクリアしてあげる必要があります。

 タイマ 0 は、今回は FOSC/4 を 1:256 のプリスケーラにかけてタイマのクロックにしています。20 MHz で動作させているので、約 76.3 Hz でタイマ 0 がオーバフローし割り込みがかかります。これだと速いですね。76.3/2 ≒ 38 なので、38 回数えれば約 0.5 秒が得られます。カウンタ変数を用意して、割り込みが入るたびにそれをインクリメントさせて、その変数値が 38 になったときにLEDピンを操作すれば約 0.5 秒の周期を得ることができます。

 タイマ 1 は 16 ビットで、ちょっと高機能なタイマです。今回はプリスケーラを 1:8 に設定しました。16 ビットなので、約 9.5 Hz でオーバフローし割り込みが入ることになります。3 まで数えることとします。

 タイマ 2 は 8 ビットタイマで、PR2 と TMR2 の値が一致したときに出力されます。この一致出力にはポストスケーラ(分周器)が接続されているため、プリスケーラで設定できる範囲よりさらに遅い周期に設定できます。今回は、プリスケーラを 1:16、ポストスケーラを 16 に設定し比較する値を 255 に設定したため、約 76.3 Hz で割り込みが入ります。これも速いので、17 まで数えることにします。

 割り込み関数は、void のあとに __interrupt() をつけます。

#include <xc.h>

// 20 MHz の発振子を用いる
#define _XTAL_FREQ 20000000

//////// コンフィギュレーションビットの設定 ////////
#pragma config FOSC = HS        // HS オシレータを使用する
#pragma config WDTE = OFF       // WDT 無効
#pragma config PWRTE = OFF      // PWRTE 無効
#pragma config CP = OFF         // コードプロテクション オフ
#pragma config BOREN = ON       // BOR 有効

// カウント用変数
unsigned char tmr0cnt = 0;
unsigned char tmr1cnt = 0;
unsigned char tmr2cnt = 0;

void __interrupt() tmrint(void) {
    // タイマ 0 割り込み
    if(TMR0IF) {
        tmr0cnt++;  // 数える
        if(tmr0cnt == 38) {
            RB0 = !RB0;
            tmr0cnt = 0;
        }
        TMR0IF = 0; // クリア
    }
    // タイマ 1 割り込み
    if(TMR1IF) {
        tmr1cnt++;  // 数える
        if(tmr1cnt == 3) {
            RB1 = !RB1;
            tmr1cnt = 0;
        }
        TMR1IF = 0; // クリア
    }
    // タイマ 2 割り込み
    if(TMR2IF) {
        tmr2cnt++;  // 数える
        if(tmr2cnt == 17) {
            RB2 = !RB2;
            tmr2cnt = 0;
        }
        TMR2IF = 0; // クリア
    }
}

void main(void) {
    // デジタルに設定
    ADCON1 = 0b00000111;
    
    // 割り込みの設定
    INTCON = 0b11100000;    // 割り込みを有効化、タイマ 0 の割り込みを有効化
    PIE1 = 0b00000011;      // タイマ 1 とタイマ 2 の割り込みを有効化
    
    OPTION_REG = 0b10000111;    // タイマ 0 プリスケーラ 1:256
    T1CON = 0b00110001;         // タイマ 1 プリスケーラ 1:8
    T2CON = 0b01111111;         // タイマ 2 プリスケーラ 1:16 ポストスケーラ 16
    PR2 = 0b11111111;           // タイマ 2 の比較値
    
    TRISB = 0;  // B ポートを出力に設定
    PORTB = 0;  // 出力の初期化
    
    while(1) {
        RB3 = !RB3;
        __delay_ms(700);
    }
}

広告

ADC を使ってみる

 ADC とはアナログ-デジタル変換器のことで、センサ値等のアナログ電圧をデジタル値に変換できます。16F77 には 8 ビットの ADC が 8 ch ついています。ADCON0 と ADCON1 で ADC の設定を行います。ADCON1 レジスタでピンの割り当てができるのですが、制約があります。特に RA3 はアナログか VREF かにしか設定できません(デジタルにしようとすると、8 ch すべてデジタルになってしまう)。詳しくはデータシートを参照してください。今回は、ADCON1 を 100 に設定し、RA0、RA1 と RA3 をアナログ入力として使うことにしました。ADC の値に応じて点滅周期を変えてみます。点滅にはタイマ 0 の割り込みを使います。

 ADC の値を取得するには、ADCON0 レジスタの CHS でチャネルを選択し、GO/DONE ビットをセットします。変換が完了すると GO がクリアされ、ADRES レジスタに値が格納されます。

 注意すべき点は、ADCS による ADC のクロック選択です。20 MHz で動作させる場合、FOSC/32 に設定する必要があります。したがって、今回は ADCS を 10 に設定しました。または、内蔵 RC のタイミングを使用する(11)こともできます。少し時間がかかりますが、そのほうが確実なようです。また、変換開始前に 20 us 以上待つ必要があるようです。

#include <xc.h>

// 20 MHz の発振子を用いる
#define _XTAL_FREQ 20000000

//////// コンフィギュレーションビットの設定 ////////
#pragma config FOSC = HS        // HS オシレータを使用する
#pragma config WDTE = OFF       // WDT 無効
#pragma config PWRTE = OFF      // PWRTE 無効
#pragma config CP = OFF         // コードプロテクション オフ
#pragma config BOREN = ON       // BOR 有効

// タイマカウント変数
unsigned char tmrcnt0 = 0;
unsigned char tmrcnt1 = 0;
unsigned char tmrcnt2 = 0;
// ADC 値格納用変数
unsigned char adc0, adc1, adc2;

void __interrupt() tmr0int(void) {
    // タイマ 0 割り込み
    if(TMR0IF) {
        tmrcnt0++;   // カウント
        tmrcnt1++;   // カウント
        tmrcnt2++;   // カウント
        // ADC 0 の値と比較
        if(tmrcnt0 >= adc0) {
            RB0 = !RB0;
            tmrcnt0 = 0;
        }
        // ADC 1 の値と比較
        if(tmrcnt1 >= adc1) {
            RB1 = !RB1;
            tmrcnt1 = 0;
        }
        // ADC 2 の値と比較
        if(tmrcnt2 >= adc2) {
            RB2 = !RB2;
            tmrcnt2 = 0;
        }
        TMR0IF = 0; // クリア
    }
}

void main(void) {
    ADCON0 = 0b10000001;    // ADC を動作させる
    ADCON1 = 0b00000100;    // RA0, RA1, RA3 をアナログ入力に
    
    INTCON = 0b11100000;    // 割り込みを有効化、タイマ 0 の割り込みを有効化
    OPTION_REG = 0b10000111;    // タイマ 0 プリスケーラ 1:256
    
    TRISB = 0;  // B ポートを出力に設定
    PORTB = 0;  // 出力の初期化
    
    while(1) {
        // RA0 アナログ値取得
        ADCON0 = 0b10000001;    // RA0 を選択
		__delay_ms(20);			// 20 us 待ち
		GO = 1;		// AD 変換開始
        while(GO);  // ADC 変換待ち
        adc0 = ADRES;
        
        // RA1 アナログ値取得
        ADCON0 = 0b10001001;    // RA1 を選択
		__delay_ms(20);			// 20 us 待ち
		GO = 1;		// AD 変換開始
        while(GO);  // ADC 変換待ち
        adc1 = ADRES;
        
        // RA3 アナログ値取得
        ADCON0 = 0b10011001;    // RA3 を選択
		__delay_ms(20);			// 20 us 待ち
		GO = 1;		// AD 変換開始
        while(GO);  // ADC 変換待ち
        adc2 = ADRES;
    }
}

CCP(PWM を試してみます)

CCP は、キャプチャ/コンペア/PWM のことで、キャプチャ機能やコンペア機能もありますが、今回は PWM モードを試してみることとします。PWM はパルス幅変調のことで、パルス幅を変えることで LED 等の明るさを変えることができます。鉄道模型の制御にも使えますよ。16F77 には CCP は 2 組入っているため、2 つの LED の明るさを変えてみます。

 CCP の設定には CCP1CON、CCP2CON レジスタを使います。CCPxCON の CCPxX、CCPxY(小文字の x には CCP の番号が入ります)はデューティ比の下位 2 ビット、すなわち右から 2 つのビットになります。Y が LSB(右端のビット)です。16F77 の PWM は 10 ビットですが、上位の 8 ビットは CCPRxL レジスタで設定します。CCPRxH は PWM モードでは使用しません。CCPxCON レジスタの CCPxM は、CCP のモードを選択します。PWM は 11xx(x は 0 でも 1 でもいい)です。

PWM にはタイマ 2 を使用します。タイマ割り込みの項で説明したように、PR2 レジスタと TMR2 が一致したときにタイマ出力が出るので、そのタイミングが PWM で使われます。PWM の周期およびデューティ比の計算式はデータシートに記載されています。PWM は、PR2 の値によって分解能が変わります。16F77 のデータシート TABLE 8-4 に 20 MHz の場合の例が載っています。タイマのプリスケーラ値を 4、PR2 を 0xFF に設定すると 4.88 kHz で 10 ビットの分解能が得られることがわかります。

 なお、CCP の出力ピンは RC1、RC2 でそれぞれ CCP2、CCP1 になります。番号が逆なので注意してください。

 今回は、PWM 出力値の設定を関数化し、値を渡すことでデューティ比を設定できるようにしました。10 ビットなので 0~1023 が設定可能範囲です。このプログラムを書き込むと、2 つの LED が交互に明るくなったり暗くなったりするはずです。

#include <xc.h>

// 20 MHz の発振子を用いる
#define _XTAL_FREQ 20000000

//////// コンフィギュレーションビットの設定 ////////
#pragma config FOSC = HS        // HS オシレータを使用する
#pragma config WDTE = OFF       // WDT 無効
#pragma config PWRTE = OFF      // PWRTE 無効
#pragma config CP = OFF         // コードプロテクション オフ
#pragma config BOREN = ON       // BOR 有効

void setPWM1Duty(unsigned short value) {
    CCP1CONbits.CCP1Y = value & 1;
    CCP1CONbits.CCP1X = value & 2;
    CCPR1L = (unsigned char)(value >>  2);
}

void setPWM2Duty(unsigned short value) {
    CCP2CONbits.CCP2Y = value & 1;
    CCP2CONbits.CCP2X = value & 2;
    CCPR2L = (unsigned char)(value >> 2);
}

void main(void) {
    ADCON1 = 0b00000111;    // デジタルに設定
    T2CON = 0b00011100; // プリスケーラ 1:4
    PR2 = 0xFF; // タイマ 2 比較値
    
    CCP1CON = 0b00001111;   // CCP1 を PWM モードで使用
    CCP2CON = 0b00001111;   // CCP2 を PWM モードで使用
    
    TRISC = 0;  // C ポートを出力に設定
    PORTC = 0;  // 出力の初期化
    
    while(1) {
        // 1024 回繰り返す
        for(unsigned short i=0; i<1024; i++) {
            setPWM1Duty(i);
            setPWM2Duty(1023-i);
            __delay_ms(5);
        }
        
        // 1024 回繰り返す
        for(unsigned short i=0; i<1024; i++) {
            setPWM1Duty(1023-i);
            setPWM2Duty(i);
            __delay_ms(5);
        }
    }
}

広告

UART の実験

 UART は、1 本の信号線で送信をし、その信号線を 2 本使うことで双方向の通信ができるシリアル通信方式です。16F77 は USART 機能を搭載しており、同期式と非同期式のシリアル通信ができますが、今回使用するのは非同期の UART になります。USB-シリアル変換器を使用し、16F77-PC 間で文字の送受信を行ってみます。

 UART は、通信速度(ボーレート)を決める必要があります。2400、9600、115200 がよく使いますが数字が大きいほど速いので、クロックも上げる必要があります。今回は 9600 bps で試してみます。

 TXSTA レジスタで送信の設定を行います。8 ビット、パリティなしで設定するので、0b00100000 にします。受信設定は RCSTA です。今回は 0b10010000 にします。

 ボーレートは、SPBRG レジスタで設定します。16F77 データシートの TABLE 10-3 を参照し、今回は 20 MHz なので 9600 bps の場合、SPBRG は 32 に設定します。2400 bps のほうがエラー率は低いので、通信速度を求めない場合は 2400 に設定するのがいいでしょう。その場合、SPBRG は 129 にします。

 送信をするには、まず PIR1 レジスタの TXIF ビットで送信バッファが空である(1)ことを確認し、TXREG レジスタに送信値を書き込みます。
受信すると同じく PIR1 の RCIF が 1 になるので、それを確認し RCREG を読みます。これで送受信ができます。今回は送受信を関数にしてみました。受信したデータをそのまま返します。さらに、ASCIIの「1」受信で LED が ON、「0」受信で LED が OFF になるようにもしています。ピンは、RC7 が RX、RC6 が TX になります。LED は RB0 に接続しました。

 動作確認は、TeraTerm 等のソフトで行います。Arduino IDE のシリアルモニタでもできると思います。

#include <xc.h>

// 20 MHz の発振子を用いる
#define _XTAL_FREQ 20000000

//////// コンフィギュレーションビットの設定 ////////
#pragma config FOSC = HS        // HS オシレータを使用する
#pragma config WDTE = OFF       // WDT 無効
#pragma config PWRTE = OFF      // PWRTE 無効
#pragma config CP = OFF         // コードプロテクション オフ
#pragma config BOREN = ON       // BOR 有効

char rxdata;

// 送信関数
void transmit(char data) {
    if(PIR1bits.TXIF) {
        TXREG = data;
    }
}

// 受信関数
char receive(void) {
    return RCREG;
}

void main(void) {
    TXSTA = 0b00100000;     // 8 ビット、パリティなし
    RCSTA = 0b10010000;     // 受信設定
    SPBRG = 32;             // 9600 bps
    
    ADCON1 = 0b00000111;    // デジタルに設定
    
    TRISB = 0;  // B ポートを出力に設定
    TRISC = 0b10000000; // C ポートは RX のみ入力設定
    PORTB = 0;  // ポート B 出力の初期化
    PORTC = 0;  // ポート C 出力の初期化
    
    while(1) {
        while(!PIR1bits.RCIF);  // 受信待ち
        rxdata = receive();     // 受信
        
        transmit(rxdata);       // オウム返し
        
        // 受信値を判定
        switch(rxdata) {
            case '1':
                // 「1」受信で LED ON
                RB0 = 1;
                break;
            case '0':
                // 「0」受信で LED OFF
                RB0 = 0;
                break;
        }
    }
}

 今回試したほかにも、SSP(I2C スレーブ・SPI)や PSP(パラレルスレーブポート)というペリフェラルがあり、INT ピン(RB0)での割り込みも可能です。また、PORTB は内蔵プルアップの設定が可能です。安価でピン数が多いので、アイデア次第でいろいろな使い方ができるかと思います。秋月電子通商では D マークがついていますがまだ 1,000 個以上在庫があるようなので、試してみてはいかがでしょうか。


戻る
広告

右メニュー

【表示がおかしい・崩れる場合】PC では Shift と F5 を同時押し。スマホではキャッシュを削除してみてください。



とうサイトでは Cookieくっきー使用しようします。くわしくは上記じょうきの「プライバシーポリシー・免責事項等めんせきじこうとう」もご確認かくにんください。



上へ戻る