
PIC18F27J53のすすめ
PIC18F27J53のいいところ
秋月で売っていること
電子工作をするうえでこれはとても大事なこと。「秋月で売っているなら使ってみるか」ってなりますよね。
もちろん、秋月なので値段が安い!!
1個270円です(2016年1月現在)。
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

USBブートローダーに対応
USB通信を使って、自分自身のプログラムの書き換えをすることができます。ブートローダです。
初回に1回だけPICkit3でブートローダプログラムを書きこんでしまえば、2回目からはPICkit3を使わずにUSBで書き込むことができます。これなら、スマートに開発をすることができます。
周辺モジュールのピンを後から割り当て
周辺モジュールが多いので、ピンの重複が気になるところです。しかし、PIC18F27J53は「Remappable Pin」といものがあります。
Remappable PinはPWMやI2Cなどのピンをプログラム内で指定することができます。これなら好きなピンに機能を割り当てられるので、重複を回避できます。
さらに基板に合わせてピンを決められるので結構便利です。
タッチセンサ
PIC18F27J53にはCTMUというタッチセンサ機能を持ったモジュールがあります。押しボタンの代わりに使えるのでなかなか便利です。
リアルタイムクロックモジュール
リアルタイムモジュールがあれば、32.768kHzの水晶をつなぐだけで時計とカレンダーをカウントしてくれます。レジスタの数を見るだけで時間や日にちをGETできるのでとても簡単!実用的!
しかも、スリープ中でも時計は止まらないので、電池でも十分動かすことができます。
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の回路図
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; // 割り込みフラグをクリア
/* 何らかの処理 */
}
}
追記: コンパイラのバージョンによっては上記の関数名を void __interrupt ISR(void)
または void __interrupt() ISR(void)
にする必要があるようです。
USB関連
USB関連は結構複雑で長くなりそうなので、改めて後日書こうと思います。
→追記:PICでUSB通信 をご覧ください。
Remappable Pinの使い方
同じく長くなりそうなので、後日記事を書きます。
RTCC(リアルタイムクロック)モジュールの使い方
同じく長くなりそうなので、後日記事を書きます。
タッチセンサの使い方
同じく長くなりそうなので、後日記事を書きます。
PWM、I2C、SPIの使い方
同じく長くなりそうなので、(元気があれば)後日記事を書きます。
プログラムの例
このままコピペで動くはずです。
/*
* PIC18F27J53用サンプルプログラム
* Date: 2016.01.08
* Author: @kerikun11
*/
// 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にリプを飛ばしていただけると嬉しいです。@kerikun11 ←遠慮なくリプしてください。