PIC32でI2Cの送信
Send an I2C signal with PIC32
通信方式としてSPIとセットにして解説されるI2C。Arduinoでは必要なハードウェア設定は代わりにやってくれるので、すぐにプログラムが組めますが、PICマイコンではその箇所もきっちりとプログラムしないといけないので、まあまあやっかいです。
個人的な意見ですが、サンプルの制作に当たっては、Arduinoのプログラムコードを参考にする場合に気をつけなければいけない点として、これらが挙げられます。
- スレーブアドレスは1ビット左に詰めて、最初に1ビットに送信か受信かを示す値をセットする。例えば、参考書に記載されているスレーブアドレスが0x20だとしたら、PICのレジスタに登録するときは「(0x20 << 1) | (send_mode ? 0 : 1)」のように計算しなければならない可能性がある。
- I2Cに指定するピンは入力モード(TRISBbits.TRISBn = 1)かつプルアップ抵抗を有効(CNPUBbits.CNPUn = 1にするか、SCLとSDAにそれぞれ電源と抵抗(3.3Vなら1kΩが目安)を接続)しておかないと信号が正しく送受信されない。
ではマイクロチップ社のI2C方式I/Oエキスパンダー「MCP23017」のLチカを通して大まかなプログラムの流れを組んでみましょう。
MCP22017の左下にあるGNDの3本線はスレーブアドレスの定義用です。すべてが0だとアドレスは「0x20(0b10000)」ですが、3本すべてに電圧をかけている状態だと「0x27(0b10111)」になります。
その右隣にある電源から引っ張ってきた線はオフになるとリセットされます。プログラムを書き込んだ直後で正しく動かないようであれば、この線を挿し直してみましょう。
PIC32MX220F032Bでは、I2Cの信号線の位置は固定です。この例ではI2C2に当たるRB2とRB3を接続しています。
400kHz以上の高速度でデータをやりとりする場合は「I2C2CONbits.DISSLW = 0」のままでかまいません。この周波数を指示するレジスタが「I2CxBRG」ですが、計算式はメーカーによると「(I2Cクロック) = (周辺クロック)/(2*(I2CxBRG + 2))」とのことなので、以下の数値が目安になります。
周辺クロック | I2C周波数 | I2CxBRGの値 |
80MHz | 400kHz | 0x062 |
80MHz | 100kHz | 0x18E |
40MHz | 400kHz | 0x030 |
40MHz | 100kHz | 0x0C6 |
20MHz | 400kHz | 0x017 |
20MHz | 100kHz | 0x062 |
main.c
#include <stdio.h>
#include <stdlib.h>
#include <p32xxxx.h>
typedef unsigned char byte;
#pragma config FNOSC = PRIPLL // 発信源 = 主外部発振器
#pragma config POSCMOD = XT // 主発振の方法 = 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_2 //周辺モジュールクロック倍数 : x1/2
// レジスタに登録するアドレスは7bitの数値を1bit左にずらしたものになる
#define MCP23017_ADDR (0x20 << 1)
void IdleI2C(void)
{
// スタート、待機、受信、ACK、送信レジスタがクリアされるまで待機する
while(I2C2CONbits.SEN || I2C2CONbits.PEN || I2C2CONbits.RSEN || I2C2CONbits.RCEN || I2C2CONbits.ACKEN || I2C2STATbits.TRSTAT);
}
unsigned int WriteI2C(unsigned char data)
{
I2C2TRN = data;
if(I2C2STATbits.IWCOL) {
// 通信の衝突が発生したなら0を返す
return 0;
}else{
while(I2C2STATbits.TBF); // 送信バッファが空になるのを待機
IdleI2C();
return 1;
}
}
void send(byte reg, byte val)
{
// 開始フラグ
I2C2CONbits.SEN = 1;
IdleI2C();
WriteI2C(MCP23017_ADDR);
WriteI2C(reg);
WriteI2C(val);
// 停止状態にして送信を完了
I2C2CONbits.PEN = 1;
IdleI2C();
/*
// データ受信のプログラム例(※汎用例です)
I2C2CONbits.SEN = 1;
IdleI2C();
WriteI2C(SLAVE_ADDR);
WriteI2C(reg);
WriteI2C(val);
// 信号を途切れさせず、連続してデータを送りたいときの例
I2C2CONbits.RSEN = 1; // リピートスタート
IdleI2C();
WriteI2C(SLAVE_ADDR | 0x1); // 0x1はスレーブから受信を促すために必要
WriteI2C(val);
// データ読み出し
I2C2CONbits.RCEN = 1; // 受信モードに移行
while(I2C2CONbits.RCEN) ; // 受信が完了すると0になるのでそれまで待機
I2C2STATbits.I2COV = 0; // バッファあふれのエラーフラグをリセット
res = I2C2RCV; // 受信したデータを格納
I2C2CONbits.PEN = 1;
IdleI2C1();
*/
}
int main()
{
int i, toggle = 0xFF;
ANSELB = 0;
// B2,B3を入力モードにし、プルアップ抵抗を有効にする
TRISBbits.TRISB2 = 1;
TRISBbits.TRISB3 = 1;
CNPUBbits.CNPUB2 = 1;
CNPUBbits.CNPUB3 = 1;
// 100kHzモード
I2C2CONbits.DISSLW = 1;
I2C2BRG = 0x0C5;
// I2C2を有効にする
I2C2CONbits.ON = 1;
send(0x00, 0x00); // PortAをすべて出力にする命令
while(1){
send(0x12, toggle); // PortAのGPIOをすべてON/OFFにする命令
toggle = ~toggle;
for(i=0;i<1000000;i++);
}
return 0;
}
2018/10/10