概要



SC2004 / I2C通信

ATmega328とI2C

ATmega328において、I2Cのシリアルクロック線(SCL)とシリアルデータ線(SDA)で一般的によく使用されるピンは、以下の通りである。

  • SCL
    PC5
  • SDA
    PC4


ATmega328にはI2Cハードウェアモジュール(TWI)が1つ搭載されているため、SCLとSDAに対応するピンは1組しか存在しない。
これらのピンはデフォルトで以下の機能にも割り当てられている。

  • PC5
    リセット時の状態を示すピン
  • PC4
    USART0の受信ピン(RXD0)


したがって、I2C通信を使用する場合は、これらの代替機能を有効にする必要がある。
また、ATmega328にはI2C専用の物理ピンヘッダが無いため、一般的なデジタルI/Oピンを使用してI2C通信を行う必要がある。

理論的には、ポートB、C、Dのピンを使用してI2C通信を行うことができるが、ポートCの割り当てられたピンを使うのが最も確実で効率的である。
ポートB、Dを使用する場合は、開発者自身がI2C通信向けのソースコードを設計する必要がある。

  • ポートA
    ATmega328では、ポートA全体がツェナープラグ保護されているため、I2C通信が使用できない。
  • ポートB
    PB2〜PB7はツェナープラグ保護されていないため、I2C通信が使用できる。
  • ポートC
    PC4、PC5がI2Cハードウェアモジュール(TWI)に割り当てられているため、I2C通信に最適である。
  • ポートD
    ツェナープラグ保護されていないため、I2C通信が使用できるが、ハードウェア割り当てが無い。


また、Arduino IDEでは、Wire.hライブラリを使用することで、I2C通信を簡単に扱うことができる。
Arduinoの公式ボードでは、A4がSDAピン、A5がSCLピンとして使用されている。

ツェナープラグ保護 (Zener Clamping)とは

AVRマイクロコントローラ上の入出力ピンを過電圧から保護する機能のことである。

各入出力ピンの内部にツェナーダイオードが接続されている。
ツェナーダイオードは、一定の電圧を超えると導通し、過剰な電圧を基板のグランド側に流すことで、ICを保護する役割を果たす。

ATmega328ではポートA全体、一部のポートBピン(PB0、PB1)がこのツェナープラグ保護で保護されている。
保護電圧は、5[V]が典型的な値である。

ツェナープラグ保護の長所は、ICを過電圧から確実に守れることである。
しかし、その分ピンの使用方法に制約が生じる。

保護ピンはダイオードの特性上、ハイインピーダンス入力としては使用できない。
また、外部からの電流供給が制限されるため、High駆動能力が低下する。

このため、I2C通信等でプルアップ抵抗を使用する場合は、ツェナープラグ非保護のピンを選ぶ必要がある。
ATmega328では、一部のポートB、ポートC全体が最適な選択肢となる。

ATMega328の設定

ATMega328をI2Cマスターモードで設定する必要がある。

  • TWCR(TWI Control Register)レジスタを設定して、I2Cの開始条件、停止条件、データ送信等を制御する。
  • TWSR(TWI Status Register)レジスタを監視して、I2Cバスの状態を確認する。
  • TWBR(TWI Bit Rate Register)レジスタを設定して、I2Cのビットレートを決定する。


SC2004の設定

SC2004は、I2Cスレーブデバイスである。

SC2004のデータシートを参照して、初期化シーケンスやコマンドを確認する。
一般的に、LCDに表示のためには、ファンクションセット、エントリーモード、ディスプレイのON / OFF等の設定が必要となる。

I2Cを介したデータ送信

ATMega328からSC2004にデータを送信するには、I2Cのスタート条件、SC2004のスレーブアドレス、データ、ストップ条件の順に送信する。
送信データには、SC2004のコマンドやテキストデータが含まれる。

文字の表示

  1. まず、SC2004の初期化シーケンスを送信する。
  2. 次に、DDRAMアドレスを設定するコマンドを送信する。
  3. 最後に、文字列データを1文字ずつ送信する。


その他

ATMega328のデータシートとSC2004のデータシートを参照しながら、適切なレジスタ設定とデータ送信を行う必要がある。
また、I2Cのタイミングやエラー処理にも注意が必要である。

サンプルコード

※注意
I2C接続はオープンドレイン出力を使用するため、通信を行わない時はSCLとSDAをプルアップ抵抗で高レベルに保つ必要がある。

 #include <avr/io.h>
 #include <util/delay.h>
 
 // I2C設定の定義
 #define SCL_CLK 100000L   // I2Cのクロックレート
 #define SC2004_ADDR 0x27  // I2C接続するSC2004のスレーブアドレス
                           // LCDのICがPCF8574Tの場合  : 0x27
                           // LCDのICがPCF8574ATの場合 : 0x3F
 
 // I2C接続するピンの定義
 #define SCL_PIN 5  // SCLピン (PORTC.5)
 #define SDA_PIN 1  // SDAピン (PORTC.1)
 
 // SC2004 LCDコマンド群
 #define LCD_CLEAR        0x01
 #define LCD_HOME         0x02
 #define LCD_ENTRY_MODE   0x06
 #define LCD_DISPLAY_ON   0x0C
 #define LCD_DISPLAY_OFF  0x08
 #define LCD_FUNCTION_SET 0x38
 #define LCD_CGRAM_ADDR   0x40
 #define LCD_DDRAM_ADDR   0x80
 
 // 関数のプロタイプ宣言
 void I2C_Init();                        // I2Cクロックレートを設定
 void I2C_Start();                       // I2C通信の開始
 void I2C_Stop();                        // I2C通信の停止
 void I2C_Write(unsigned char);          // I2C通信のデータ送信
 void LCD_Write_Command(unsigned char);  // SC2004にコマンドを送信
 void LCD_Write_Data(unsigned char);     // SC2004にデータを送信
 void LCD_Init();                        // SC2004にデータを送信
 void LCD_Clear();                       // LCDの表示をクリア
 void LCD_Home();                        // カーソル位置をホームに移動
 void LCD_Print(char*);                  // LCDに文字列を表示
 
 int main()
 {
    // I/Oポートの初期化
    DDRC  |= (1 << SCL_PIN) | (1 << SDA_PIN); // SCLおよびSDAとして使用するピンを出力に設定
    PORTC |= (1 << SCL_PIN) | (1 << SDA_PIN); // SCLおよびSDAとして使用するピンをプルアップ
 
    // 初期化
    I2C_Init();
    LCD_Init();
    LCD_Clear();
    LCD_Home();
 
    // LCDに文字を表示
    LCD_Print("Hello");
 
    while (1);
 }
 
 void I2C_Init()
 {
    TWBR = ((F_CPU / SCL_CLK) - 16) / 2;  // I2Cクロックレートの設定
    TWSR = 0;                             // プリスケーラのビット
 }
 
 void I2C_Start()
 {
    TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);  // Transmit START condition
    while (!(TWCR & (1 << TWINT)));                    // Wait for START to be transmitted
 }
 
 void I2C_Stop()
 {
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);  // Transmit STOP condition
 }
 
 void I2C_Write(unsigned char data)
 {
    TWDR = data; // Load data into TWDR
    TWCR = (1 << TWINT) | (1 << TWEN);  // I2C通信のデータ送信を開始
    while (!(TWCR & (1 << TWINT)));     // データ送信が完了するまで待機
 }
 
 void LCD_Write_Command(unsigned char cmd)
 {
    I2C_Start();
    I2C_Write(SC2004_ADDR);  // I2C接続のLCDスレーブアドレスを送信
    I2C_Write(0x00);         // Co = 0, Writing command
    I2C_Write(cmd);
    I2C_Stop();
 
   _delay_ms(2);             // コマンドが実行されるまで待機
 }
 
 void LCD_Write_Data(unsigned char data)
 {
    I2C_Start();
    I2C_Write(SC2004_ADDR);  // I2C接続のLCDスレーブアドレスを送信
    I2C_Write(0x40);         // Co = 1, Writing data
    I2C_Write(data);
    I2C_Stop();
 
   _delay_ms(2);             // コマンドが実行されるまで待機
 }
 
 void LCD_Init()
 {
    _delay_ms(50);                        // LCDが起動するまで待機
    LCD_Write_Command(LCD_FUNCTION_SET);  // Function set
    LCD_Write_Command(LCD_DISPLAY_ON);    // LCDを有効にする
    LCD_Write_Command(LCD_CLEAR);         // LCDの表示をクリア
    LCD_Write_Command(LCD_ENTRY_MODE);    // Entry mode
 }
 
 void LCD_Clear()
 {
    LCD_Write_Command(LCD_CLEAR);  // LCDの表示をクリア
    _delay_ms(2);                  // コマンドが実行されるまで待機
 }
 
 void LCD_Home()
 {
    LCD_Write_Command(LCD_HOME);   // カーソル位置をホームに移動
 }
 
 void LCD_Print(char *str)
 {
    while (*str) {
       LCD_Write_Data(*str++);     // LCDに1文字ずつデータを送信
    }
 }