2015年7月18日土曜日

PSoC5LPでI2Cを使おう(マスター編)

前回、I2Cってどんなものなのかって言うお話をしました。
今回は実践的な話で、「I2Cを使ってみましょう」と言う話です。


先ずはいつもの様に、空のPSoC5LPプロジェクトを作ります。
I2Cを通じてセンサーの結果をキャラクターLCDに出力したいので
キャラクターLCDコンポーネントを入れます。
つぎにI2Cのコンポーネントを入れるわけですが、
I2Cのコンポーネントにはいくつか種類があります。
まずはCommunicationsのツリーを開いてI2Cのツリーを開きます。
I2Cに関するコンポーネントが表示されました。



前回話した通り、I2Cにはマスターとスレーブの関係性があります。
PSoCはマスターにもスレーブにもなることが出来ます。
また、PSoC5LP,PSoC3,PSoC4にはI2C専用の通信ペリフェラル
UDBを消費してI2Cペリフェラルを生成する2通りがあります。
(PSoC4では通信ペリフェラルが複合型)
今回はI2C専用の通信ペリフェラルを使ってPSoC5LP側がマスター側で通信します。




なのでI2C Master (Fixed Function)を選んで入れます。
ピンも付いた状態で画面に入るので今回はpinを入れる必要は無いです。
必要なコンポーネントは今回、これだけです。



さて、I2Cのコンポーネントがこれで入りましたのでI2Cの設定をしたいと思います。
I2Cのコンポーネントをダブルクリックで設定を出しましょう。
そうすると通信方式と通信スピードについての設定が出てきます。



通信方式は複数選ぶことが出来ますが、単一マスターなので
ModeのプルダウンメニューからMaster選びます。




 PSoCの場合通信スピードは1Mbit/sまでの通信に対応しています。
今回、通信スピードは400kbit/sにしたいので
Date rateのプルダウンメニューから400を選びます。


ここまで出来るとこんな感じになるので「OK」をクリックして閉じましょう。



ピンアサインは今まで通り、任意のピンを設定です。



今回は、数値文字変換のsprintf()を使うので、ヒープサイズも再設定しておきましょう。
リンカーの設定もして置きましょう。*トラブルシューティングを参照



ここまで出来たら、1回目のビルドをしましょう。

ビルドが終わったので今度はPSoC5LPとセンサーモジュールとの接続をしましょう。
I2Cのピン同士はオープンドレインと呼ばれる状態にあります。
7セグLEDの時にPSoC5LPのソース電流とシンク電流の話をしました。
オープンドレインはプルアップ抵抗を使うシンク電流の接続方法と似た状態です
しかしシンク電流時とは違い、電源電圧を超えてON,OFFする場合
I2Cの様な通信の用途で使われています。

I2Cで使われるプルアップ抵抗には適当な抵抗を用いる事が出来ません。
何故かと言うと配線に使われる銅線は小さいながらも
コンデンサーの様な働きもあります
なので、適当な抵抗をつないでしまうとパッシブフィルターの様な状態となり
正しいON,OFFの信号がデバイス間に伝わりません。
本来ならば、オシロスコープを使いながら選定するのが良いのですが
今回はモジュールの基板に実装されているプルアップ抵抗を用いりたいと思います。




配線が終わったら、プログラミングをしましょう。
今回は長いです…。


#include <project.h>
#include <stdio.h>
#define HTDU21D_ADDRESS 0x40

#define TRIGGER_TEMP_MEASURE_HOLD  0xE3
#define TRIGGER_HUMD_MEASURE_HOLD  0xE5
#define TRIGGER_TEMP_MEASURE_NOHOLD  0xF3
#define TRIGGER_HUMD_MEASURE_NOHOLD  0xF5
#define WRITE_USER_REG  0xE6
#define READ_USER_REG  0xE7
#define SOFT_RESET  0xFE


//
//I2Cの通信で使う高度API
//タイムアウト処理が無いので注意。
//

uint8 I2C_Write(uint8 SlaveAddress, uint8 *DataAddress, uint8 Byte_Count, uint8 I2C_Mode){
    uint8 temp;
    temp = I2C_1_MasterWriteBuf(SlaveAddress,DataAddress,Byte_Count,I2C_Mode);
    while (temp != I2C_1_MSTR_NO_ERROR);
    while(I2C_1_MasterStatus() & I2C_1_MSTAT_XFER_INP);
    temp = I2C_1_MasterClearStatus();
    return (temp);
}

uint8 I2C_Read(uint8 SlaveAddress, uint8 *DataAddress, uint8 Byte_Count, uint8 I2C_Mode){
    uint8 temp;
    temp = I2C_1_MasterWriteBuf(SlaveAddress,DataAddress,1,I2C_Mode);
    while (temp != I2C_1_MSTR_NO_ERROR);
    while(I2C_1_MasterStatus() & I2C_1_MSTAT_XFER_INP);
    temp = I2C_1_MasterClearStatus();
   
    CyDelay(60);//このデバイスではいるが、通常は要らない。
   
    temp = I2C_1_MasterReadBuf(SlaveAddress,DataAddress,Byte_Count, I2C_Mode);
    while (temp != I2C_1_MSTR_NO_ERROR);
    while(I2C_1_MasterStatus() & I2C_1_MSTAT_XFER_INP);
    temp = I2C_1_MasterClearStatus();
    return (temp);
}
//
//高度APIはここまで
//


//
//gitにあるArduinoのデザインリファレンスから取ってきた
//デバイスからのデータ演算用
//

//Given the raw temperature data, calculate the actual temperature
float calc_temp(int SigTemp)
{
  float tempSigTemp = SigTemp / (float)65536; //2^16 = 65536
  float realTemperature = -46.85 + (175.72 * tempSigTemp); //From page 14

  return(realTemperature); 
}

//Given the raw humidity data, calculate the actual relative humidity
double calc_humidity(int SigRH)
{
  float tempSigRH = SigRH / (float)65536; //2^16 = 65536
  float rh = -6 + (125 * tempSigRH); //From page 14

  return(rh); 
}



//
//main
//
int main()
{
    CyGlobalIntEnable; /* Enable global interrupts.(割り込みを使う事を許可する) */

    LCD_Char_1_Start();
    LCD_Char_1_Position(0,0);
    LCD_Char_1_PrintString("HTU21D");
    /* Place your initialization/startup code here (e.g. MyInst_Start()) */
    I2C_1_Start();
    uint8 wr_buff[2]={TRIGGER_HUMD_MEASURE_NOHOLD,0x00};//取り出すレジスターと空の配列
    int R=0;
    uint16_t HBIT,LBIT,RESULT;//配列受け渡し用の変数
    char out[10];//文字変換後の配列
    R=I2C_Read(HTDU21D_ADDRESS,wr_buff,2,I2C_1_MODE_COMPLETE_XFER);//アドレス、レジスターが入った配列、配列の個数、I2Cのモード
    LCD_Char_1_PrintInt8(R);//エラーの出力(01ならok)
    LCD_Char_1_PrintString("/");
    //LCD_Char_1_Position(1,0);
    LCD_Char_1_PrintInt8(wr_buff[0]);//配列の0番目を出力
    LCD_Char_1_PrintInt8(wr_buff[1]);//1番目を出力
    //LCD_Char_1_PrintString("/");
    HBIT = wr_buff[0];
    LBIT = wr_buff[1];
    RESULT = HBIT<<8|LBIT;//上位ビットを左に8個分シフト(ずらす)して下位ビットとORを行う。
    RESULT &= 0xFFFC;//ステータスビットを0にして計算出来るようにする。
    LCD_Char_1_Position(1,0);
    //LCD_Char_1_PrintInt16(RESULT);
    double FRESULT;//小数点が出るのでダブル型の変数
    FRESULT = calc_humidity(RESULT);//湿度の計算をしてダブル型の変数に渡す
    sprintf(out,"%.2f",FRESULT);//数値文字変換をする。
    LCD_Char_1_PrintString(out);
    LCD_Char_1_PrintString("% ");
    wr_buff[0] = TRIGGER_TEMP_MEASURE_NOHOLD;
    wr_buff[1] = 0x00;
    I2C_Read(HTDU21D_ADDRESS,wr_buff,2,I2C_1_MODE_COMPLETE_XFER);
    HBIT = wr_buff[0];
    LBIT = wr_buff[1];
    RESULT = HBIT<<8|LBIT;
    RESULT &= 0xFFFC;
    FRESULT = calc_temp(RESULT);
    sprintf(out,"%.2f",FRESULT);
    LCD_Char_1_PrintString(out);
    //LCD_Char_1_PutChar(LCD_Char_1_CUSTOM_0);//LCDのカスタムモードで出す場合
    LCD_Char_1_PrintString("C");
    for(;;)
    {
        /* Place your application code here. */
    }
}


I2C_Write();I2C_Read();の関数は私が作成したAPIです。
戻り値(R=I2C_Reas();とした場合のRの値)には
エラーコード等が入るようになっています。
I2C_Write()を使う場合は*DataAddressには配列を使うと良いと思います。
I2Cデバイスの多くがスレーブ側のアドレスを
インクリメントしてくれる機能がある様です。
配列の先頭(***[0])レジスターの番号を使い、次の配列(***[1]には
先頭のレジスターに入れたい、数値設定を入れます。
数値設定は8bit分を16進数表記にしてあげる必要があるので注意してください。
また、その次の配列(***[2]には先頭のレジスター番号に
インクリメント(レジスター番号+1のレジスター番号)された
レジスター番号に入れたい設定数値が入ります。
I2C_Read();を使う場合には*DataAddressにつかった配列と
同じ様な使い方をすると良いと思います。
I2C_Readで使う場合の配列では、先頭の配列に読み出したいレジスター番号入れてあげます。実行すると、先頭の配列には読み出したいレジスターに入っている数値が入っています。
先頭の配列の2番目には読み出したいレジスター番号に
インクリメントした番号のレジスターに入っている数値が入っています。
もちろん配列が多ければ、読み出したいレジスターをインクリメントし続けて読み出します。

まだ接続をしていない場合にタイムアウトする機能や、
今回の様に読み出しの際に時間差をつける機能無いのですが
今後追加して行こうと思います。

プログラムを入れ終わったら、もう一度コンパイルしましょう。
これで完成(/・ω・)/
実際に書きこんで見ましょう。



どうでしょうか?
LCDに湿度や温度が表記されたでしょうか?
For();の中で使う場合には湿度や温度には
移動平均フィルターをかけた方が良いと思います。
湿度や温度は常に変化し続けるので、安定しません。

また、main()の前に沢山関数を書きました。
これはC言語ではよくあるルールでもありますが
PSoC Creatorではmainの前に関数を書いてあげると
コード補完によって「これ使うの?」と聞いてきます。
これを使うとコードを書く苦労が少し減るかな?


いずれスレーブ編も書こうかなと思います。

**今回の高度APIはITショップ「えとせとら」さんのブログを参考に作成しました。ありがとうございます。

2015年7月13日月曜日

I2Cってなんじゃいな。

PSoCまつり明けの記事としてはちょっと高難易度かもしれません。

さて、I2Cはアイ・ツー・シー、もしくは
アイ・スケア(スクエア)・シーと読みます。

I2Cはフィリップス・セミコンダクター(今のNXP)が開発した
2線式の双方向バスです。
シリアルデータライン(SDA)シリアルクロックライン(SCL)
2本を用いて通信をし100kbit/s,400kbit/s,1Mbit/s,3.4Mbit/s,5Mbit/s
の4つの通信スピードがあります。

UART(シリアル通信)と同じようにデバイス間の通信方式ですが
UARTとは異なり複数のデバイスとも通信することが出来ます。

I2Cには、デバイス同士にマスターとスレーブと言う関係性を持っています。

マスターのデバイスはスレーブに対する設定をしたり、スレーブが作ったデータを取ったりします。
スレーブのデバイスはスレーブ同士を見分けるための固定のアドレス
レジスター(メモリーの集合郵便ポストみたいなもの)を持っています。
レジスターに設定やスレーブが用意したデータが格納され、
レジスター内のデータを返してマスター側とデータのやり取りをします。



多くの場合、I2Cの接続にはマスターが1つに対して
複数のスレーブが接続する単一マスター接続があります。
場合によって、複数のマスターと複数のスレーブを接続する
マルチマスター接続と言うのもあります。

I2Cで難しいのは8bitのセットで格納されているスレーブ側のレジスターです。
レジスターには0,1のビットが8個セットで格納されており、
格納してあるビット列で設定やデータの受け渡しをしています。
スレーブ側のレジスターに格納されているビット列を変更する事によって
スレーブ側の設定が変更されます。
また、一つのビットだけでも大きい意味を持つので、設定に関しては
スレーブ側のデーターシートを良く見る必要があります。
受け渡すデータも格納されている8bitだけでは無く、16bit,24bitと長くなる場合には
マスター側が「ビット演算子」を使ってデータを復元する必要性もあります。

ここまで分かっていればPSoCでI2Cを使う場合には問題ないかなと思います。
PSoCではCapSenseのモニターにもI2Cは使われており、何かとI2Cは登場する機会が
多いかなと思いますので、習得しておくと便利だと思います。

もしも、I2Cの細かい所を知りたい場合には
NXPの中の人が書いた解説を読むのが良いと思います。
https://developer.mbed.org/users/okano/notebook/i2c-access-examples/


次回は実際にPSoC5LPをマスター、スレーブ側を
センサーモジュールでI2Cの通信してみたいと思います。