PIC18F27J53のすすめ

PIC18F27J53のいいところ

秋月で売っていること

電子工作をするうえでこれはとても大事なこと。「秋月で売っているなら使ってみるか」ってなりますよね。

もちろん、秋月なので値段が安い!!
1個270円です(2016年1月現在)。

PIC18F27J53

PIC18F27J53

豊富なメモリ

我が宿敵のATMEGA328Pのメモリと比べてみましょう。

機能 ATMEGA328P PIC18F27J53
プログラムメモリ 32kB 128kB
SRAM 2kB 3.8kB

メモリがありすぎて、困っちゃうなぁ。

豊富な周辺モジュール

主な機能を表にまとめました。

機能 個数 備考
8bitタイマ 4個
16bitタイマ 4個
PWM 10 内3つは超高機能PWM
I2C/SPI 2個 マスタ&スレーブ
12bit-ADC 10ch モジュールは1個
CTMU ADCのピン タッチセンサ
RTCC 1 リアルタイムクロック&カレンダー
USB 1個 スレーブ側

タイマとPWMがたくさんあるのでロボットは動かし放題ですね!

USBでパソコンと通信

デバックのためにマイコンとパソコンで通信をするときは、UARTで通信するのが一般的ですが、PIC18F27J53ならば、それをUSBでできます。

USBで接続するとCOMポートが現れるので、UARTとまったく同じように使うことができるのです。

これで、FT232のUSBシリアル変換モジュールは必要なくなります!!

AE-FT232

AE-FT232

USBブートローダーに対応

USB通信を使って、自分自身のプログラムの書き換えをすることができます。ブートローダです。

初回に1回だけPICkit3でブートローダプログラムを書きこんでしまえば、2回目からはPICkit3を使わずにUSBで書き込むことができます。これなら、スマートに開発をすることができます。

周辺モジュールのピンを後から割り当て

周辺モジュールが多いので、ピンの重複が気になるところです。しかし、PIC18F27J53は「Remappable Pin」といものがあります。

Remappable PinはPWMやI2Cなどのピンをプログラム内で指定することができます。これなら好きなピンに機能を割り当てられるので、重複を回避できます。

さらに基板に合わせてピンを決められるので結構便利です。

タッチセンサ

PIC18F27J53にはCTMUというタッチセンサ機能を持ったモジュールがあります。押しボタンの代わりに使えるのでなかなか便利です。

リアルタイムクロックモジュール

リアルタイムモジュールがあれば、32.768kHzの水晶をつなぐだけで時計とカレンダーをカウントしてくれます。レジスタの数を見るだけで時間や日にちをGETできるのでとても簡単!実用的!

しかも、スリープ中でも時計は止まらないので、電池でも十分動かすことができます。

PIC18F27J53の使い方(ハードウェア)

ピンアサイン

PIC18F27J53のピンアサイン(データシートより)

PIC18F27J53のピンアサイン

PIC18F27J53のピンアサイン

RPxというピンが「Remappable Pin」です。

電源について

電源電圧は3.3Vです。注意してください。

誤って5Vを接続したことがありますが、壊れませんでした(PICは強い!)。しかし正常には動作しませんでした。

上のピンアサインの図の灰色のピンは、5.5Vトレラントなので、5.5Vの入力もそのまま接続できます。

PIC18F27J53のCoreは2.5Vで動作しています。入力電圧は3.3Vなので、内部にレギュレータが入っています。それの安定化のために「V_DDVORE/V_CAP」端子に10uFのコンデンサをつなぐ必要があります。

PIC18F27J53の一般的な回路図

USBから電源をとるときは、3端子レギュレータを通して3.3Vにするのを忘れずに。

PIC18F27J53の基本的な回路図

PIC18F27J53の回路図

PIC18F27J53の回路図

SPIやPWMなどは「Remappable」のピンに割り当てればOKです。

回路図を見てわかるように、実際に使えるGPIO(General Purpose Input Output)は、12本(内10本ADC対応&Remappable)です。

UARTやI2Cを使わないならば、GPIOはもう少し増えます。

この回路図をもとに回路を作っていきましょう。

PIC18F27J53の使い方(ソフトウェア)

ソフトウェアもほかのPICと少し違うので少し注意が必要です。

Configration Bits

いつも変わらないので、コピペしましょう。

// CONFIG1L
#pragma config WDTEN = ON, PLLDIV = 2, CFGPLLEN = ON, STVREN = OFF, XINST = OFF
// CONFIG1H
#pragma config CPUDIV = OSC1, CP0 = OFF
// CONFIG2L
#pragma config OSC = INTOSCPLL, SOSCSEL = HIGH, CLKOEC = OFF, FCMEN = OFF, IESO = OFF
// CONFIG2H
#pragma config WDTPS = 8
// CONFIG3L
#pragma config DSWDTOSC = T1OSCREF, RTCOSC = T1OSCREF, DSBOREN = OFF, DSWDTEN = OFF, DSWDTPS = G2
// CONFIG3H
#pragma config IOL1WAY = OFF, ADCSEL = BIT12, MSSP7B_EN = MSK7
// CONFIG4L
#pragma config WPFP = PAGE_127, WPCFG = OFF
// CONFIG4H
#pragma config WPDIS = OFF, WPEND = PAGE_WPFP, LS48MHZ = SYS48X8

動作クロックの設定

クロックは内蔵8MHzをPLLで6倍してできる、48MHzを使うのが一般的です。これはUSBとして使える周波数です。

// OSC Settings
void OSC_init(void){
	OSCCONbits.IRCF = 7;	// 内蔵発振周波数は8MHz
	OSCTUNEbits.PLLEN = 1;	// PLL(x6)をEnable
	OSCCONbits.SCS = 0;	// クロックソースは内蔵発振or外部クロック
}

ここに記述した内容と、Configration Bitsの内容によりクロックソースと周波数が決まります。

I/Oポート関連

PIC18F27J53には8bitの入出力ポートが3つあります。PORTA,PORTB,PORTCです。

I/Oの設定はTRISA,TRISB,TRISCレジスタで行います。出力が0で、入力が1です。

アナログ/デジタルの切り替えはANCONレジスタで行います。アナログが0でデジタルが1で、他のPICとは逆なので注意しましょう。

内蔵プルアップはポートBのみについているので、デジタル入力はポートBでするのがいいでしょう。

// I/O Port Settings
void IO_init(void){
	// 0が出力、1が入力
	TRISA =  0bxxx1xxxx; // RA7, RA6, RA5, Vcap, RA3, RA2, RA1,  RA0
	TRISB =  0bxx11xxxx; // RB7, RB6, SDA, SCL,  RB3, RB2, RB1,  RB0
	TRISC =  0b10111x10; // RXD, TXD, D+,  D-,   Vusb,RC2, T1OSI,T1OSO
	// 0がアナログ、1がデジタル
	ANCON0 = 0b111xxxxx; // x,x,x,AN4(RA5),AN3(RA3),AN2(RA2),AN1(RA1),AN0(RA0)
	ANCON1 = 0b000xxxxx; // VBG,x,x,AN12(RB0),AN11(RC2),AN10(RB1),AN9(RB3),AN8(RB2)
	INTCON2bits.RBPU = 0; // PORTB Pull-up Pnable
}

※「x」となっているところは自分で適当に0/1を入れる。

タイマ関連

Timer 0 (8bit)

割り込み周波数 = Fosc / (4 * 256 * 2^prescaler) = 46875/(2^prescaler) [Hz]

// prescaler is 0~8
void Timer0_init(unsigned char prescaler) {
    T0CONbits.TMR0ON = 1;
    T0CONbits.T08BIT = 1; // 8-bit timer
    T0CONbits.T0CS = 0;   // use internal-OSC
    if (prescaler == 0) {
	T0CONbits.PSA = 1; // Not use prescaler
    } else {
	T0CONbits.PSA = 0; // use prescaler
	T0CONbits.T0PS = prescaler - 1;
    }
    INTCONbits.T0IE = 1;    // Timer 0 Enable
    INTCONbits.TMR0IF = 0;  // interrupt Flag clear
    INTCON2bits.TMR0IP = 0; // low priority
    INTCONbits.PEIE = 1;
}

Timer 1 (16bit)

割り込み周波数 = CLOCK / (4 * 65536 * 2^prescaler) [Hz]

#define FOSC_4 0 // Fosc/4
#define T1OSC  2 // Timer 1 OSC
// prescaler is 0~3
// clock_select is FOSC_4(Fosc/4) or T1OSC(32.768kHz)
void Timer1_init(unsigned char  prescaler, unsigned char clock_select) { 
    T1CONbits.TMR1CS = clock_select;
    if (clock_select == T1OSC) T1CONbits.T1OSCEN = 1; // Drive Crystal
    else T1CONbits.T1OSCEN = 0;
    T1CONbits.T1CKPS = prescaler;
    T1CONbits.nT1SYNC = 1; // No Sync
    T1CONbits.RD16 = 0; //Timer1=16bit timer
    T1CONbits.TMR1ON = 1; //enable
    IPR1bits.TMR1IP = 0; // low priority
    PIR1bits.TMR1IF = 0;
    PIE1bits.TMR1IE = 1;
    INTCONbits.PEIE = 1;
}

Timer 3 (16bit)

割り込み周波数 = CLOCK / (4 * 65536 * 2^prescaler) [Hz]

// prescaler is 0~3
void Timer3_init(unsigned char prescaler) {
    T3CONbits.TMR3CS = 0; //Clock = Fosc/4
    T3CONbits.T3OSCEN = 0; // Not Drive Crystal
    T3CONbits.T3CKPS = prescaler;
    T3CONbits.RD163 = 0; //Timer3=16bit timer
    T3CONbits.TMR3ON = 1; //enable
    IPR2bits.TMR3IP = 0; // low priority
    PIR2bits.TMR3IF = 0;
    PIE2bits.TMR3IE = 1;
    INTCONbits.PEIE = 1;
}

UARTの設定&動作

ここではとりあえずUART通信できるプログラムを示します。本来ならばリングバッファを使って実装します。長くなりそうなので、そのやり方はまた改めて説明します。

最初に1回UART_init();を呼び出せば、後はrx_send(data);で送信する子ができます。

ボーレートは115200bpsに設定されています。

// UART初期化関数
void UART_init(void) {
    TXSTA1bits.TX9 = 0; // 0:8-bit
    TXSTA1bits.TXEN = 1; //1:enable
    TXSTA1bits.SYNC = 0; // 0:Asynchronous mode
    TXSTA1bits.BRGH = 0; // 1:High Speed
    RCSTA1bits.SPEN = 1; // 1:Serial Port enable
    RCSTA1bits.RX9 = 0; // 0:8-bit
    RCSTA1bits.CREN = 1; // 1:continuous receive enable
    BAUDCON1bits.BRG16 = 1; // 1:use 16-bit SPBRG
    SPBRG1 = _XTAL_FREQ / 16 / 115200 - 1;
    SPBRGH1 = (_XTAL_FREQ / 16 / 115200 - 1) >> 8;
    IPR1bits.RCIP = 0; // Low Priority
    PIE1bits.RCIE = 1; // Interrupt Enable
    INTCONbits.PEIE = 1; // Peripheral Interrupt Enable
}
// 送信関数
void tx_send(char send_data){
    while(!PIR1bits.TXIF); // 前のデータの送信完了まで待つ
    TXREG1 = send_data;
}

割り込み関数

割り込み関数はPIC16FやPIC18Fと同様です。

今回説明した内容の機能をを使う場合、割り込み関数は以下のようになります。

// 割り込み関数
void interrupt ISR(void) {
    // UART受信割り込み処理
    if (PIE1bits.RCIE && PIR1bits.RCIF) {
	char recv_char = RCREG1; // RCREG1を読み取ることで割り込みフラグはクリアされる
	/* 受信データrecv_charを使った何らかの処理 */
    }
    // タイマ0割り込み処理
    if (INTCONbits.T0IF && INTCONbits.T0IE) {
	INTCONbits.T0IF = 0; // 割り込みフラグをクリア
	/* 何らかの処理 */
    }
    // タイマ1割り込み処理
    if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) {
	PIR1bits.TMR1IF = 0; // 割り込みフラグをクリア
	/* 何らかの処理 */
    }
    // タイマ3割り込み処理
    if (PIR2bits.TMR3IF && PIE2bits.TMR3IE) {
	PIR2bits.TMR3IF = 0; // 割り込みフラグをクリア
	/* 何らかの処理 */
    }
}

USB関連

USB関連は結構複雑で長くなりそうなので、改めて後日書こうと思います。

→追記:PICでUSB通信をご覧ください。

Remappable Pinの使い方

同じく長くなりそうなので、後日記事を書きます。

RTCC(リアルタイムクロック)モジュールの使い方

同じく長くなりそうなので、後日記事を書きます。

タッチセンサの使い方

同じく長くなりそうなので、後日記事を書きます。

PWM、I2C、SPIの使い方

同じく長くなりそうなので、(元気があれば)後日記事を書きます。

プログラムの例

このままコピペで動くはずです。

/*
 * PIC18F27J53用サンプルプログラム 
 * Date:	2016.01.08
 * Author:	@Ryokeri
 */
// CONFIG1L
#pragma config WDTEN = OFF, PLLDIV = 2, CFGPLLEN = ON, STVREN = OFF, XINST = OFF
// CONFIG1H
#pragma config CPUDIV = OSC1, CP0 = OFF
// CONFIG2L
#pragma config OSC = INTOSCPLL, SOSCSEL = HIGH, CLKOEC = OFF, FCMEN = OFF, IESO = OFF
// CONFIG2H
#pragma config WDTPS = 1024
// CONFIG3L
#pragma config DSWDTOSC = T1OSCREF, RTCOSC = T1OSCREF, DSBOREN = OFF, DSWDTEN = OFF, DSWDTPS = G2
// CONFIG3H
#pragma config IOL1WAY = OFF, ADCSEL = BIT12, MSSP7B_EN = MSK7
// CONFIG4L
#pragma config WPFP = PAGE_127, WPCFG = OFF
// CONFIG4H
#pragma config WPDIS = OFF, WPEND = PAGE_WPFP, LS48MHZ = SYS48X8

#include <xc.h>

// __delay_ms()関数を使うために周波数を定義
#define _XTAL_FREQ 48000000

// 自作delay関数
void delay_ms(unsigned int t) {
    while (t--)__delay_ms(1);
}
// OSC Settings
void OSC_init(void) {
    OSCCONbits.IRCF = 7; // 内蔵発振周波数は8MHz
    OSCTUNEbits.PLLEN = 1; // PLL(x6)をEnable
    OSCCONbits.SCS = 0; // クロックソースは内蔵発振or外部クロック
}
// I/O Port Settings
void IO_init(void) {
    // 0が出力、1が入力
    TRISA = 0b00010000; // RA7, RA6, RA5, Vcap, RA3, RA2, RA1,  RA0
    TRISB = 0b00111111; // RB7, RB6, SDA, SCL,  RB3, RB2, RB1,  RB0
    TRISC = 0b10111010; // RXD, TXD, D+,  D-,   Vusb,RC2, T1OSI,T1OSO
    // 0がアナログ、1がデジタル
    ANCON0 = 0b11111111; // x,x,x,AN4(RA5),AN3(RA3),AN2(RA2),AN1(RA1),AN0(RA0)
    ANCON1 = 0b00011111; // VBG,x,x,AN12(RB0),AN11(RC2),AN10(RB1),AN9(RB3),AN8(RB2)
    INTCON2bits.RBPU = 0; // PORTB Pull-up Pnable
}
// prescaler is 0~8
void Timer0_init(unsigned char prescaler) {
    T0CONbits.TMR0ON = 1;
    T0CONbits.T08BIT = 1; // 8-bit timer
    T0CONbits.T0CS = 0; // use internal-OSC
    if (prescaler == 0) {
	T0CONbits.PSA = 1; // Not use prescaler
    } else {
	T0CONbits.PSA = 0; // use prescaler
	T0CONbits.T0PS = prescaler - 1;
    }
    INTCONbits.T0IE = 1; // Timer 0 Enable
    INTCONbits.TMR0IF = 0; // interrupt Flag clear
    INTCON2bits.TMR0IP = 0; // low priority
    INTCONbits.PEIE = 1;
}
#define FOSC_4 0 // Fosc/4
#define T1OSC  2 // Timer 1 OSC
// prescaler is 0~3
// clock_select is FOSC_4(Fosc/4) or T1OSC(32.768kHz)
void Timer1_init(unsigned char prescaler, unsigned char clock_select) {
    T1CONbits.TMR1CS = clock_select;
    if (clock_select == T1OSC) T1CONbits.T1OSCEN = 1; // Drive Crystal
    else T1CONbits.T1OSCEN = 0;
    T1CONbits.T1CKPS = prescaler;
    T1CONbits.nT1SYNC = 1; // No Sync
    T1CONbits.RD16 = 0; //Timer1=16bit timer
    T1CONbits.TMR1ON = 1; //enable
    IPR1bits.TMR1IP = 0; // low priority
    PIR1bits.TMR1IF = 0;
    PIE1bits.TMR1IE = 1;
    INTCONbits.PEIE = 1;
}
// prescaler is 0~3
void Timer3_init(unsigned char prescaler) { // prescaler is 0~3
    T3CONbits.TMR3CS = 0; //Clock = Fosc/4
    T3CONbits.T3OSCEN = 0; // Not Drive Crystal
    T3CONbits.T3CKPS = prescaler;
    T3CONbits.RD163 = 0; //Timer3=16bit timer
    T3CONbits.TMR3ON = 1; //enable
    IPR2bits.TMR3IP = 0; // low priority
    PIR2bits.TMR3IF = 0;
    PIE2bits.TMR3IE = 1;
    INTCONbits.PEIE = 1;
}
// UART初期化関数
void UART_init(void) {
    TXSTA1bits.TX9 = 0; // 0:8-bit
    TXSTA1bits.TXEN = 1; //1:enable
    TXSTA1bits.SYNC = 0; // 0:Asynchronous mode
    TXSTA1bits.BRGH = 0; // 1:High Speed
    RCSTA1bits.SPEN = 1; // 1:Serial Port enable
    RCSTA1bits.RX9 = 0; // 0:8-bit
    RCSTA1bits.CREN = 1; // 1:continuous receive enable
    BAUDCON1bits.BRG16 = 1; // 1:use 16-bit SPBRG
    SPBRG1 = _XTAL_FREQ / 16 / 115200 - 1;
    SPBRGH1 = (_XTAL_FREQ / 16 / 115200 - 1) >> 8;
    IPR1bits.RCIP = 0;
    PIE1bits.RCIE = 1;
    INTCONbits.PEIE = 1;
}
// 送信関数
void tx_send(char send_data) {
    while (!PIR1bits.TXIF); // 送信完了まで待つ
    TXREG1 = send_data;
}
// 割り込み関数
void interrupt ISR(void) {
    // UART受信割り込み処理
    if (PIE1bits.RCIE && PIR1bits.RCIF) {
	char recv_char = RCREG1; // RCREG1を読み取ることで割り込みフラグはクリアされる
	tx_send(recv_char);
	/* 何らかの処理 */
    }
    // タイマ0割り込み処理
    if (INTCONbits.T0IF && INTCONbits.T0IE) {
	INTCONbits.T0IF = 0; // 割り込みフラグをクリア
	/* 何らかの処理 */
    }
    // タイマ1割り込み処理
    if (PIR1bits.TMR1IF && PIE1bits.TMR1IE) {
	PIR1bits.TMR1IF = 0; // 割り込みフラグをクリア
	TMR1H = 0xC0; // 0.5秒ごとの割り込みにするため
	/* 何らかの処理 */
    }
    // タイマ3割り込み処理
    if (PIR2bits.TMR3IF && PIE2bits.TMR3IE) {
	PIR2bits.TMR3IF = 0; // 割り込みフラグをクリア
	/* 何らかの処理 */
    }
}
// main関数
int main(void) {
    // 初期設定
    OSC_init();
    IO_init();
    Timer0_init(6);
    Timer1_init(0, T1OSC);
    Timer3_init(2);
    UART_init();

    // 割り込み許可
    INTCONbits.GIE = 1;

    while (1) {
	// UART送信
	tx_send('H');
	tx_send('e');
	tx_send('l');
	tx_send('l');
	tx_send('o');
	tx_send('\n');
	delay_ms(1000);
    }
    return 0;
}

さいごに

PICを使おう!

僕がググった感じだとPIC18F27J53を使っている人はかなり少ないと思います。この記事を読んで使ってくれる人がいたら嬉しいです。このマイコンは魅力的ですよ~

PICはとてもおもしろいですね!バンバン使って電子工作を楽しみましょう!

やっぱり、AVRなんかよりPICの方がいいですね~

PIC用ライブラリ

僕の作ったPIC用ライブラリのリンクを貼っておきます。参考にしてください。
PICライブラリ

主に PIC18F27J53 と PIC16F1827、PIC12F1822 用に作られています。

何か気づいたら

もし、この記事の内容に間違いや疑問をを見つけたら遠慮なく教えてください。Twitterにリプを飛ばしていただけると嬉しいです。@Ryokeri14←遠慮なくリプしてください。