ATmega328 - LCD
概要
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のコマンドやテキストデータが含まれる。
文字の表示
- まず、SC2004の初期化シーケンスを送信する。
- 次に、DDRAMアドレスを設定するコマンドを送信する。
- 最後に、文字列データを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文字ずつデータを送信
}
}