21 October 2012

i2cのプルアップ抵抗値はどれくらいがよいのか

i2cバスのプルアップ抵抗値をいくらにすればよいのか、試行錯誤してみた

■ 検証環境
・PIC12F1822
・Starwberry Linux キャラクタLCD SB1602B

■ 回路図と写真
20121021-i2c-lcd-circuit.png

20121021-circuit-test.jpg

クロック側のRp(プルアップ抵抗)を可変抵抗にして、値を変えながら実験した。


■ 実験した抵抗値
0.35kΩ
0.85kΩ
1.5kΩ
2.5kΩ
5.0kΩ
7.5kΩ
10kΩ

■ オシロスコープで観察した波形

20121021-signal.jpg


■ フィリップスのi2cバス仕様書

この仕様書の38ページの表

20121021-graph.jpg

Vdd=3.3VでRs=0Ωの場合(今回の試験回路)、Rp(min)=約1.0kΩ
このLCDモジュールのキャパシタンスがわからないので、図39よりキャパシタンスが十分低いとすれば、Rp(max)=10~20kΩ


■ Strawberry LinuxのQ&Aより

SCL,SDAのプルアップ抵抗が小さいと動作しません。少なくとも20kΩ以上にしてください。ACKのレベルが0Vにならず中間電位になるのはプルアップ抵抗が小さすぎるからです。I2C液晶のドライブ能力が低いので十分に電流をながせず0V付近まで引っ張れません。47kΩ程度で十分です。あるいはマイコン内蔵のプルアップ機能(100μA程度)だけで動きます。

http://strawberry-linux.com/support/27001/619011


■ 結論

プルアップ抵抗を大きくすると、オシロスコープでの観察より(チャージに時間がかかり)信号のタイミングがずれてくる(長くなる)傾向が出る。ただし、この実験で行った10kΩでは通信に特に問題は見られなかった(ちゃんと表示できる)。

Strawberry Linux社の説明文では20kΩ以上で、推奨値は47kΩとなっているが、これはフィリップスのi2cバス仕様書の推奨値から外れているように思える。ただ、表示に問題が無いのであれば、消費電力を落とすためにも大きな抵抗を用いることはよいように思える。


■ プログラム例

main.c

#include <stdio.h>
#include <stdlib.h>

#include <xc.h>
#include "lcd-i2c-lib.h"

/* PIC Configuration 1 */
__CONFIG(FOSC_INTOSC & // INTOSC oscillator: I/O function on CLKIN pin
WDTE_OFF & // WDT(Watchdog Timer) disabled
PWRTE_ON & // PWRT(Power-up Timer) disabled
MCLRE_OFF & // MCLR pin function is digital input
CP_OFF & // Program memory code protection is disabled
CPD_OFF & // Data memory code protection is disabled
BOREN_OFF & // BOR(Brown-out Reset) disabled
CLKOUTEN_OFF & // CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin
IESO_OFF & // Internal/External Switchover mode is disabled
FCMEN_OFF); // Fail-Safe Clock Monitor is disabled

/* PIC Configuration 2 */
__CONFIG(WRT_OFF & // Flash Memory Self-Write Protection : OFF
VCAPEN_OFF & // VDDCORE pin functionality is disabled
PLLEN_OFF & // 4x PLL disabled
STVREN_ON & // Stack Overflow or Underflow will not cause a Reset
BORV_HI & // Brown-out Reset Voltage Selection : High Voltage
DEBUG_OFF & // In-Circuit Debugger disabled, ICSPCLK and ICSPDAT are general purpose I/O pins
LVP_OFF); // Low-voltage programming : disable


#ifndef _XTAL_FREQ
/* 例:4MHzの場合、4000000 をセットする */
#define _XTAL_FREQ 4000000
#endif

int main(int argc, char** argv) {
// 基本機能の設定
OSCCON = 0b01101010; // 内部オシレーター 4MHz
TRISA = 0b00001110; // IOポートRA1,RA2を入力モード(RA3は入力専用)
ANSELA = 0; // A/D変換を全ポートRA0,1,2,4で無効
PORTA = 0;

SSP1STAT = 0b10000000; // I2C 100kHz
SSP1CON1 = 0b00101000; // I2C Master Mode / RA1/RA2をSCL/SDAとして利用
SSP1ADD = 9; // Baud rate, 4MHz/((SSP1ADD + 1)*4) = 100K
// SSP1ADD = 19; // Baud rate, 8MHz/((SSP1ADD + 1)*4) = 100K

// 0.5秒待つ
__delay_ms(500);

i2c_lcd_init();
i2c_lcd_puts("Test");

return (EXIT_SUCCESS);
}

i2c LCD用ライブラリ関数

lcd_i2c_lib.c

#include <stdio.h>
#include <stdlib.h>

#include <xc.h>
#include "lcd-i2c-lib.h"

#ifndef _XTAL_FREQ
/* 例:4MHzの場合、4000000 をセットする */
#define _XTAL_FREQ 4000000
#endif

#define LCD_I2C_ADDR 0x7c
#define CONTRAST 0x30 // for 3.3V

// I2C書き込みサイクルの開始(Start Conditionの発行)
void i2c_start(void)
{
SSP1CON2bits.SEN = 1; // Start Condition Enabled bit
i2c_wait();
}

// I2C書き込みサイクルの終了(Stop Conditionの発行)
void i2c_stop(void)
{
SSP1CON2bits.PEN = 1; // Stop Condition Enable bit
i2c_wait();
}

// I2Cバスにデータを送信(1バイト分)
void i2c_send_byte(unsigned char data)
{
SSP1BUF = data;
i2c_wait();
}

void i2c_send_command(unsigned char i2c_addr, unsigned char data)
{
i2c_start();
i2c_send_byte(i2c_addr);
i2c_send_byte(0);
i2c_send_byte(data);
i2c_stop();

/* ClearかHomeか */
if((data == 0x01)||(data == 0x02))
__delay_ms(2); // 2msec待ち
else
__delay_us(30); // 30μsec待ち
}

void i2c_send_data(unsigned char i2c_addr, unsigned char data)
{
i2c_start();
i2c_send_byte(i2c_addr);
i2c_send_byte(0x40);
i2c_send_byte(data);
i2c_stop();

__delay_us(30);
}

void i2c_wait(void)
{
while ( ( SSP1CON2 & 0x1F ) || ( SSPSTAT & 0x04 ) );

}
void i2c_lcd_init(void)
{
__delay_ms(40);
i2c_send_command(LCD_I2C_ADDR, 0x38); // 8bit 2line Normal mode
i2c_send_command(LCD_I2C_ADDR, 0x39); // 8bit 2line Extend mode
i2c_send_command(LCD_I2C_ADDR, 0x14); // OSC 183Hz BIAS 1/5
/* コントラスト設定 */
i2c_send_command(LCD_I2C_ADDR, 0x70 | (CONTRAST & 0x0F));
i2c_send_command(LCD_I2C_ADDR, 0x5C | ((CONTRAST >> 4) & 0x03));
// lcd_cmd(0x6A); // Follower for 5.0V
i2c_send_command(LCD_I2C_ADDR, 0x6B); // Follwer for 3.3V
__delay_ms(300);
i2c_send_command(LCD_I2C_ADDR, 0x38); // Set Normal mode
i2c_send_command(LCD_I2C_ADDR, 0x0C); // Display On
i2c_send_command(LCD_I2C_ADDR, 0x01); // Clear Display


}

void i2c_lcd_puts(const char *s)
{
while(*s) //文字取り出し
i2c_send_data(LCD_I2C_ADDR, *s++); //文字表示
}

lcd_i2c_lib.h

#ifndef LCD_I2C_LIB_H
#define LCD_I2C_LIB_H

#ifdef __cplusplus
extern "C" {
#endif

void i2c_start(void);
void i2c_stop(void);
void i2c_send_byte(unsigned char data);
void i2c_send_command(unsigned char i2c_addr, unsigned char data);
void i2c_send_data(unsigned char i2c_addr, unsigned char data);
void i2c_wait(void);
void i2c_lcd_init(void);
void i2c_lcd_puts(const char *s);

#ifdef __cplusplus
}
#endif

#endif /* LCD_I2C_LIB_H */