I2C通信プログラムを作成する前に、PICマイコンの「PPS機能」の使い方を説明します。
PPS機能とは?
突然「PPS機能」なんて用語がでてきて、 I2C通信はどこいった?、という感じですよね。
今回製作したブレッドボードの回路を制御するには、I2Cプログラム作成の前にPPS機能の理解が必要になりますのでお付き合いください。
PICマイコンにはいろいろな機能モジュールが内蔵されていますよ。
例えば応用編シリーズでは、PWMやADコンバータなどのPICマイコンに内蔵されている機能を使ってタイマーを製作しました。
今回は、PIC16F18857に搭載されている「I2C通信モジュール」を使用してプログラムを作成していきます。
この「I2C通信モジュール」とは、複雑なI2C通信をハードウェアで実現してくれるものです。
ところで、このI2C通信モジュールは、PICマイコンのピンに接続されていて、クロック信号(SCL)とデータ信号(SDA)を制御します。
I2C通信モジュールのクロック信号とデータ信号は、デフォルトで特定のピンが割り当てられています。
例えば、PIC16F18857には2個のI2C通信モジュールが内蔵されていますが、デフォルトでは以下のようにピンが割り当てられています。
モジュール | 信号 | デフォルトピン |
---|---|---|
I2C通信モジュール1 | クロック信号 | RC3 |
データ信号 | RC4 | |
I2C通信モジュール2 | クロック信号 | RB1 |
データ信号 | RB2 |
実は、このピンの割り当ては変更することができるようになっています。
このピンの割り当てを変更する機能のことを「PPS機能」と呼んでいます。
「PPS」は「Peripheral Pin Select」の略で、日本語では「機能のピン選択」という意味です。意味合いとしては「内蔵機能モジュールのピン割り当て機能」という感じです。
実践編ではI2C通信モジュール1を使用することにしますが、このモジュールのデフォルトの割り当てピンは、SCLがRC3、SDAがRC4となっています。
作成した回路図は、SCLがRC3、SDAがRC2に接続していますので、割り当てピンを変更する必要があります。
そこで、I2C通信プログラムの作成に入る前にPPSの使い方を確認します。
PPSの使い方概要
I2C通信モジュールを含めて、PICマイコン内部の機能モジュールは、次のように入力部と出力部を持っています。

機能モジュールのピンの割り当ては、以下のように1つの機能モジュールに対して入力部と出力部のピンの割り当てを行います。


上野図では任意のピンに接続できるように記載していますが、実際には機能モジュールによっては割り当てできないピンもありますので注意してください。詳しい内容はこの記事の後半で説明します。
次に、I2C通信モジュールを詳しく見ていきます。
PICマイコンのI2C機能モジュールは、一つの機能モジュールではなく、I2C通信の「SCL(クロック信号)のモジュール」と「SCL(データ信号)のモジュール」の2つの機能モジュールから構成されています。
まとめると、次のようにSDAモジュールとSDAモジュールのそれぞれに対して、入力ピンと出力ピンを割り当てます。(なんだかややこしいですね…)

PPS機能のプログラム
これから、PPS機能を使って上の図のようにI2C通信モジュール1のピン割り当てを行います。
最初にPPSの設定の流れを確認しておきます。
PPSのピン割り当て機能はデフォルトではロックされています。
そのため、以下のようにPPS機能のロックを解除してからPPSの設定を行い、設定後再度ロックするようになっています。
- PPS機能のロック解除
- 機能モジュール入力部のピンの割り当て
- 機能モジュール出力部のピンの割り当て
- PPS機能のロック
それでは、この番号順に対応するプログラムを順番に説明します。
❶ PPS機能のロック解除
PPSのロックを解除するには、PPSLOCK
レジスタのPPSLOCKED
ビットを0に設定します。
この変更は、プログラムではPPSLOCKbits.PPSLOCKED = 0;
と書けば良さそうですが、このよう変更することはできないようになっています。
この変更するには、おまじないが必要になります。
そのおまじないとは、PPSLOCK
レジスタに一度0x55
を代入したあと、すぐに0xAA
を代入します。(2進数では0b01010101
を代入した後に0b10101010
を代入することになります)
ちょっと不思議なプログラムを書くことになりますが、「アリババと40人の盗賊」の「開け、ゴマ」という呪文のような感じでしょうかね。
同じレジスタに0x55
、0xAA
という順番に代入するとPPSLOCKED
ビットを変更することができるようになります。
PPSロック解除のプログラムは以下のようになります。
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 0;
❷ 機能モジュール入力のピンの割り当て
PPS機能のロックが解除できたら、次は機能モジュール入力部のピンを割り当て設定です。
ここでは、次の部分を設定することになります。(何を設定しているのか迷子にならないように注意していただければと思います)

今回製作したブレッドボードの接続では、割り当て方は次のようになります。(上の図と比較してみてください)
モジュール | 割り当てピン |
---|---|
I2C通信モジュール1のSDAの入力部 | RC2 |
I2C通信モジュール1のSCLの入力部 | RC3 |
この設定は、プログラムでは次のフォーマットで書きます。
[機能モジュール名] = [ピン名];
この「機能モジュール名」と「ピン名」はデータシートに記載されています。
今回はPIC16F18857のデータシートを解読していきますが、他の型番のPICマイコンでも同じ読み方になります。
最初に「機能モジュール名」部分の書き方、次に「ピン名」部分の書き方を説明します。
機能モジュール名の書き方
次の表は、「機能モジュールの入力部」の設定を行うための「機能モジュール名」の表です。
この表は機能モジュール名の他に、デフォルトで割り当てられているピン、設定可能なピンが書かれています。

I2C通信モジュール1の機能モジュール名は、赤枠で囲んだ部分に記載されています。
I2C通信モジュール1のSDAとSCLの入力部の名前は「SSP1DATPPS」、SCLは「SSP1CLKPPS」です。(かなりややこしくなってきましたね…)
ここまでの内容をまとめると、I2C通信モジュールのSDAとSCLの入力部のピンの割り当ては次のようになります。
SSP1DATPPS = [RC2のピン名];
SSP1CLKPPS = [RC3のピン名];
ピン名の書き方
次に「ピン名」の書き方です。
ピン名は文字列ではなく数値で指定します。
次の表は、機能モジュールの入力部の割り当てピンを設定するための各ピンの設定値の表です。

表中、赤枠で囲んだ部分がRC2ピンとRC3ピンの値です。
RC2ピンは0x12
、RC3ピンは0x13
を設定することになります。
まとめると、機能モジュールの入力部の設定は次のようになります。
// I2C通信モジュール1の入力部設定
SSP1DATPPS = 0x12; // SDA入力部をRC2に設定する
SSP1CLKPPS = 0x13; // SCL入力部をRC3に設定する
❸ 機能モジュール出力部のピンの割り当て
次にI2C通信モジュール1のSDA、SCLの出力部の設定を行います。
ここでは、次の部分を設定することになります。

先ほどの入力部と同じく、ピンの割り当ては次のようになります。
モジュール | 割り当てピン |
---|---|
I2C通信モジュール1のSDAの出力部 | RC2 |
I2C通信モジュール1のSCLの出力部 | RC3 |
この設定は、プログラムでは次のフォーマットで書きます。(先ほどの入力部とは逆の設定のイメージです)
[ピン名] = [機能モジュール名];
それでは、「ピン名」部分と「機能モジュール名」部分の書き方を確認していきましょう!
ピン名の書き方
ピン名はフォーマットが決まっていて、以下のように記述します。
ピン名PPS
ちょっとわかりづらい説明ですね。少し具体的に確認しましょう。
例えば、RC2であればピン名はRC2PPS
、RC3であればRC3PPS
と記述します。
ここまでの説明で、出力部の設定は次のようになります。
RC2PPS = [SDAのモジュール名];
RC3PPS = [SCLのモジュール名];
機能モジュール名の書き方
次に機能モジュール名の設定です。
モジュール名は数値で指定することになります。
以下の表は、機能モジュールの出力部の設定を行うための表です。
この表は機能名とその設定値が書かれています。またデフォルトで割り当てられているピンも記載されています。

I2C通信モジュール1のSDAとSCLは赤枠で囲んである部分で、それぞれ0x15
と0x14
です。
プログラムでは次のように設定することになります。
RC3PPS = 0x14; // RC3をSCLに設定
RC2PPS = 0x15; // RC2をSDAに設定
これで入力部と出力部の設定が完了しました!
❹ PPS機能のロック
PPSの設定が終わったら、PPSを変更できないようにロックします。
ロックするにはPPSLOCKED
ビットに1をセットすればOKですが、ロック解除の時と同様に呪文が必要になります。
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 1;
I2C通信機能モジュールのPPS設定まとめ
今までの設定をまとめると以下のようになります。
// PPS設定ロックの解除
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 0;
// I2C通信モジュール1のSCL、SDA入力部のピン割り当て
SSP1DATPPS = 0x12; // SDA入力部をRC2に設定する
SSP1CLKPPS = 0x13; // SCL入力部をRC3に設定する
// I2C通信モジュール1のSCL、SDA出力部のピン割り当て
RC3PPS = 0x14; // RC3をSCLに設定
RC2PPS = 0x15; // RC2をSDAに設定
// PPS設定ロック
PPSLOCK = 0x55;
PPSLOCK = 0xAA;
PPSLOCKbits.PPSLOCKED = 1;
これでブレッドボード回路に合ったI2C通信モジュールのピン割り当てができました!
次回はI2C通信モジュールを使用するプログラムを作成していきます。
更新履歴
日付 | 内容 |
---|---|
2018.6.10 | 新規投稿 |
2025.8.10 | 説明内容補足 |
情報大変有り難う御ございました。
確認してみます。
(i2cで相手から来るデーターが32bytも有るので苦慮しておりました)
インクリメントの方もテストしてみます。
大変有り難うございました。
お役に立つ情報か分かりませんが、また何か不明点ありましたらご質問いただけると幸いです。
ご連絡大変ありがとうございました。
ご指摘に場所のコメント分修正しておきます。
ポート設定ですが、下記のようにセットされています
movlw B’11111111′
movwf TRISB
clrf PORTB
movlw B’00011000′
movwf TRISC
clrf PORTC
movlw B’00000000′
movwf ANSELB
movwf ANSELC
あともう一つ伺いたいのですが、ユーザーメモリー番地をプログラム上でインクリメントさせて行く方法は有るのでしょうか?
例として
_dat_bak1 EQU 30h
_dat_bak2 EQU 31h
_dat_bak3 EQU 32h
_dat_bak4 EQU 33h
_dat_bak*を30hからデーターの値を入力開始し、アドレス値にデーター入れたら、次の値31h→32hとインクリメントさせる方法。
何せ初心者な物でご伝授の程よろしくお願い申し上げます。
お返事どうもありがとうございます。
【PPS処理】
TRISとANSELレジスタ設定で解決されない場合、他の要因の可能性もあります。
もし不具合が発生するようでしたら、次のことも試してみてください。
最初に背景です。うろ覚えで申し訳ないのですが、PPSロック解除後、ピン割り当てを変更してから再ロックしますが、すぐに再ロックするとうまくピン割り当てができないような情報を見たことがあります。
そのための対策ですが、PPSを再ロックかける前に、10us程度時間待ちをすると解決する場合もあるようです。
具体的には次のように10us程度のNOPを入れます。
NOPは1CPUサイクル必要ですので、クロック周波数の場合、NOPひとつで4周期分となります。
例えば、4MHz(4,000,000Hz)動作の場合、NOPひとつで1usになります。(計算間違いなければですが…)
ということで次のようにPPSロックの前に10個程度NOPを入れてみて試していただければと思います。
NOP
NOP
NOP
:
10個程度入れてみる
;///////// PPS設定再ロック
BANKSEL PPSLOCK
MOVLW 0x55
MOVWF PPSLOCK
MOVLW 0xAA
MOVWF PPSLOCK
bsf PPSLOCK,PPSLOCKED
【メモリアドレスのインクリメント】
メモリー番地のインクリメントとのとこですが、次のようなイメージでしょうかね。
・RAMのある連続した番地に対して、プログラムを使って順番にデータを書き込む
・次に書き込むアドレスをプログラム側で自動的にインクリメントする(_dat_bak1 に書き込んだら次は _dat_bak2 へ、というように、ラベル名ではなくアドレス値を直接操作したい)
このような場合、間接アドレッシングでのアクセスになると思います。
例えば、アドレス0x30. 0x31, 0x32, 0x33に対して、それぞれの番地に0x11, 0x22, 0x33, 0x44を書き込みたい場合、次のようにINDFを使用すると良いと思います。
; 先頭アドレスをFSRに設定
movlw 0x30 ; 先頭アドレス
movwf FSR ; FSRにセット
; 最初のデータを代入
movlw 0x11 ; WREGに0x11を代入
movwf INDF ; WREGの内容をFSRアドレスに代入
; FSRレジスタの値を+1
incf FSR, F
; 2番目のデータを代入
movlw 0x22 ; WREGに0x22を代入
movwf INDF ; WREGの内容をFSRアドレスに代入
; FSRレジスタの値を+1
incf FSR, F
; 3番目のデータを代入
movlw 0x33 ; WREGに0x33を代入
movwf INDF ; WREGの内容をFSRアドレスに代入
; FSRレジスタの値を+1
incf FSR, F
; 4番目のデータを代入
movlw 0x44 ; WREGに0x44を代入
movwf INDF ; WREGの内容をFSRアドレスに代入
PICマイコンによってINDF0、INDF1がありますが、考え方としてはこのようになると思います。
また、上の例では4つのデータに対してそれぞれ命令を書いていますが、実際にはループした方が良いですね…
初めてご連絡致します。
PIC126F18857を使って液晶コントロールしておりますが、i2cのポートプロテクトロックで原因不明の状況が発生して困っておりますので、ご伝授下さい。
状況
PPSLOCK解除した後、ポートの各設定を行い終了後に、改めてPPSLOCKするとi2c出力しません。
但し、PPSLOCKを解除したままプログラムを走らせると、特に問題なく動きます。
(原因不明です)
?PPS設定ロックを解除したままでプログラムを作成しても問題ないのでしょうか?
以下が設定内容です。
;///////// PPS設定ロックの解除
BANKSEL PPSLOCK
MOVLW 0x55
MOVWF PPSLOCK
MOVLW 0xAA
MOVWF PPSLOCK
bcf PPSLOCK,PPSLOCKED
;ロック解除
BANKSEL SSP1DATPPS ;BK=62
movlw 0x14 ;
movwf SSP1DATPPS ;SDA入力部をRC2に設定する
movlw 0x13 ;
movwf SSP1CLKPPS ;SCL入力部をRC3に設定する
;///////// I2c通信(液晶用)モジュール1のSCL、SDA出力部のピン割り当て
BANKSEL RC3PPS
movlw 0x14 ;
movwf RC3PPS ;RC3をSCLに設定
movlw 0x15
movwf RC4PPS ;RC4をSDAに設定
;///////// ここまで
;///////// I2c通信(温度用)モジュール2のSCL、SDA入力部のピン割り当て
BANKSEL SSP2DATPPS ;BK=62
movlw 0x0A ;
movwf SSP2DATPPS ;SDA2入力部をRB2に設定する(SDA2にRB2ピン設定)
movlw 0x09 ;
movwf SSP2CLKPPS ;SCL2入力部をRB1に設定する(SCL2にRB1ピンを設定)
;///////// I2c通信(温度用)モジュール2のSCL、SDA出力部のピン割り当て
BANKSEL RB1PPS
movlw 0x16 ;
movwf RB1PPS ;RB1をSCL2に設定(RB2ピンにSCL2設定)
movlw 0x17
movwf RB2PPS ;RB2をSDA2に設定(RB2ピンにSDA2設定)
;///////// ここまで
MOVLB 0x00 ;BK=0
;///////// PPS設定再ロック
BANKSEL PPSLOCK
MOVLW 0x55
MOVWF PPSLOCK
MOVLW 0xAA
MOVWF PPSLOCK
bsf PPSLOCK,PPSLOCKED
;ロック設定
以上よろしくお願い申し上げます。
ご質問どうもありがとうございます。
PPSによるピンの割り当て変更の手順は、確認した限り問題ないようです。
ただ、コメントとプログラムが一致していない2点がありました。
1) 液晶モジュールのDAT1の割り当て
プログラムでは0x14(RC4)を設定していますが、コメントではRC2に設定とありますので、コメントはRC4ですね。
2) 温度モジュールのSCL2の割り当て
プログラムでは0x16(CSL2)をRB1に設定していますが、コメント括弧内にRB2ピントありますので、括弧内のRB2はRB1ですね。
とはいってもコメントは動作に影響ありませんので、いただいたプログラムは問題ないので原因の特定はちょっと難しい状況ですね…
そこで、最初にRB1, RB2, RC3, RC4ピンの設定についてご確認いただけますでしょうか。
いずれのピンも、デジタルの入力に設定にする必要があります。
入出力に関してはデフォルトで入力になっているので問題ないと思いますが、
念のため次のコードで設定しておくのが確実と思います。
; ピンの入力設定
BANKSEL TRISB
bsf TRISB, TRISB1 ; RB1を入力に設定
bsf TRISB, TRISB2 ; RB2を入力に設定
BANKSEL TRISC
bsf TRISC, TRISC3 ; RC3を入力に設定
bsf TRISC, TRISC4 ; RC4を入力に設定
; ピンのデジタル設定
BANKSEL ANSELB
bcf ANSELB, ANSB1 ; RB1をデジタルモードに設定
bcf ANSELB, ANSB2 ; RB2をデジタルモードに設定
BANKSEL ANSELC
bcf ANSELC, ANSC3 ; RC3をデジタルモードに設定
bcf ANSELC, ANSC4 ; RC4をデジタルモードに設定
このあとにPPSのロック解除、ピン割り当て変更、PPSロックしていただければと思います。
お世話になります
RB1,RB2ピンでPPS機能を使う場合どの様な設定が必要になるのでしょうか?
RB1にSCL(クロック)、RB2にSDA(データ)を当てて
ピン属性を
ANSELC = 0b00000000;
ANSELB = 0b00000000;
TRISC = 0b01001100;
//RB1,RB2をIN
TRISB = 0b00000110;
にして
SS1を全部SS2にしてみたけど駄目でした
ご質問は「SSP2のSCLをRB1に、SDAをRB2ピンに割り当てるにはどうしたらよいか」という内容でしょうか。
ご質問内容では、RBポートのデジタル設定と入出力設定のみとなっています。
PPS機能を使用するには
1) PPS機能でSSP2をRB1とRB2に割り当てる
2) RBポートのデジタル設定と入出力設定を行う
3) PPS2機能を使用してI2C通信を制御する
という流れになります。ご質問の内容では(2)のコードのみですが、(1)と(3)はどのようなコードにされましたでしょうか。
返信ありがとうございます
1)すみません、ピンの変更はしないのでPPSは関係なかったです
2)はこれでいいんですよね?
ANSELB = 0b00000000;
TRISB = 0b00000110;
3)SS1をSS2に置き換えるだけですよね?
SSP2STAT = 0x80;
SSP2CON1 = 0x28;
SSP2CON3 = 0x00;
SSP2ADD = 0x09;
void i2cProtocolStart() {
SSP2IF = 0;
SSP2CON2bits.SEN = 1;
while (SSP2IF == 0) {}
SSP2IF = 0;
return;
}
void i2cProtocolStop() {
SSP2IF = 0;
SSP2CON2bits.PEN = 1;
while (SSP2IF == 0) {}
SSP2IF = 0;
return;
}
void i2cProtocolSendData(uint8_t data) {
SSP2IF = 0;
SSP2BUF = data;
while (SSP2IF == 0) {}
SSP2IF = 0;
return;
}
デフォルト設定でのRB1,RB2のl2c(SSP)機能が反映してないようです
スタートコンディションのWhileで止まってしまいます
RB1,RB2でLED付けたりして接触不良も無さそうです
追記
問題解決しました
PPSで設定したら機能が反映しました
SSP2CLKPPS = 0x09;
SSP2DATPPS = 0x0A;
RB1PPS = 0x16;
RB2PPS = 0x17;
これってピンの変更だけじゃなくて
モジュール機能使う場合にも必要になるんですね
ご迷惑かけました
お返事遅くなり、自己解決してしまったとのことで申し訳ございませんでした。
すみません、PPS機能は基本的にデフォルトピンから変更するときに必要と思っていましたが、確かに仕様書のMSSPモジュールの説明を読むと、注意書きにそのように取れる記述がありました。(PPSでの設定が必須であれば、デフォルトピンがなぜあるのか不思議ではありますが)
ちょっと腑に落ちませんが、PPS設定をする、ということで理解しておきたいと思います。どうもありがとうございました。