Sim's blog

電子工作はじめてみました

温度センサーLM73を使ってみる

2008-09-23 18:54:02 | AVR
エレキジャックNo.8に、ナショナルセミコンダクタの温度センサーLM73が載った基板が付録についてきました。マイコンとはI2Cで通信します。

LM73基板です。

足とパスコン(0.1uF)をつけています。パスコンはつけなくても、基板の外(2pinと3pinの間)につけてもいいです。穴が開いているのでチップコンデンサでなくてもいいのですが、チップ部品のはんだづけの練習もかねてつけてみました。ちょっとななめってます。

pin配置です。エレキジャックにpin配置が載っていなかったので、データシートから写しました。
1 ADDR I2Cのslave addressを決めるpin。openで0b1001100になる
2 GND
3 Vdd 2.7~5.5V
4 SMBCLK I2CのSCL (要pull up)
5 #ALERT 範囲外出力
6 SMBDAT I2CのSDA (要pull up)

つなぐ必要があるのはGND(2pin)、Vdd(3pin)、SMBCLK(4pin)、SMBDAT(6pin)の4本です。内部レジスタに設定した範囲を超えるとALERT信号を出すことができますが、使わなければつながなくていいです。SMBCLKとSMBDATのpull up抵抗はエレキジャックのミッション2第1章では3.3kΩ、第2章では1kΩです。ちなみに第1章ではLM73をPICに、第2章ではHCS08につないでいます。

このところAVRづいているので、AVRにつないでみます。

I2C通信の参考にしたサイトです。
趣味の電子工作の部屋 by すん I2Cコントロール実験
AVR試用記 I2C通信
エレキジャック 1、2、3線シリアル・インターフェース

I2Cでマスターからスレーブにデータを送るには以下の手順になります。
(1) スタートビットを送る
(2) スレーブのアドレスとWを送る(SLA+Wといいます)。
(3) ACK/NAKをもらう
(4) データを送る
(5) ACK/NAKをもらう。必要なだけ(4)と(5)を繰り返す
(6) ストップビットを送る

I2Cでマスターからスレーブのデータを受け取るには以下の手順になります。
(1) スタートビットを送る
(2) スレーブのアドレスとRを送る(SLA+Rといいます)。
(3) ACK/NAKをもらう
(4) データを受信する
(5) ACKを返して、さらにデータをもらうために(4)に戻るか
NAKを返して、もうデータがいらないことを伝える
(6) ストップビットを送る

上の2つを組み合わせたパターンもあります。そのときは、受信時のスタートビットはリピーテッド・スタートと名前が変わります。また、上の手順ではエラーチェックをしていませんが、やたら細々とエラーチェックが必要になります。

ATMega88はTWIというI2Cのハードがついているので、レジスタをいじることでI2C通信ができます。全部ソフトで書くのよりは随分と楽になります。

実験している様子です。

上に見えているのがライター代わりの78K0付録基板(トラ技8月号)です。左側がRS232C通信用の秋月製FT232RL USBシリアル変換モジュールです。中央がAVR ATMega88で、右側が今回のLM73付録基板です。秋月で売っている小型温度計モジュール(通販番号M-01063)で温度を測っています。温度計の影になっていますが電源は3.3VのACアダプタです(実測3.5V)。
0.5秒毎に、LM73で測った温度データはI2CでATMega88に読み込まれてUSBシリアル変換モジュール経由でPCに送られます。

ソースです。<と>は全角になっています。
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>

typedef unsigned char byte;

// USART関連は省略

// 16進1桁出力
void puthex1(byte d)
{
    d += '0';
    if(d > '9') d += 'a' - '9' - 1;
    putch(d);
}

// 16進2桁出力
void puthex2(byte d)
{
    puthex1(d >> 4);
    puthex1(d & 0x0f);
}

// 10進2桁出力 (x < 100のみ可)
void putdec2(byte x)
{
    putch('0' + x / 10);
    putch('0' + x % 10);
}

// エラー
void i2c_error(void)
{
    putch('['); puthex2(TWSR); putch(']'); // TWSRの内容
    putch(0x0d); putch(0x0a); // 改行
    PORTB = 0x01; // LED点灯
    while(1) ;
}

// 通信速度の初期化
void i2c_init(void)
{
    TWSR = 0b00000000;  // 1分周
    TWBR = 32;          // 100k = 8MHz / (16 + 2 * TWBR * 1)
}

// master 1byte送信
void i2c_write(byte d)
{
    TWDR = d; // 送信データ
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // データの送出完了待機
    if((TWSR & TW_STATUS_MASK) != TW_MT_DATA_ACK) i2c_error();
}

// master 1byte受信(ackを返す)
byte i2c_read_ack(void)
{
    TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
    while( !(TWCR & _BV(TWINT)) ) ; // 受信完了待ち
    if((TWSR & TW_STATUS_MASK) != TW_MR_DATA_ACK) i2c_error();
    return TWDR; // データを返す
}

// master 1byte受信(noackを返す)
byte i2c_read_nak(void)
{
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // 受信完了待ち
    if((TWSR & TW_STATUS_MASK) != TW_MR_DATA_NACK) i2c_error();
    return TWDR; // データを返す
}

// master送信開始、IDはslave address << 1
void i2c_start(byte id)
{
    byte s;

    // 開始条件を送る
    TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // 開始条件の送出完了待機
    s = TWSR & TW_STATUS_MASK;
    if(s != TW_START && s != TW_REP_START) i2c_error();

    // アドレスを送る
    TWDR = id;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while( !(TWCR & _BV(TWINT)) ) ; // アドレスの送出完了待機
    s = TWSR & TW_STATUS_MASK;
    if(s != TW_MT_SLA_ACK && s != TW_MR_SLA_ACK) i2c_error();
}

// master送信終了
void i2c_stop(void)
{
    TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
    while( !(TWCR & _BV(TWSTO)) ) ;
    // statusはTW_NO_INFOになる
}


#define LM73 (0x4c << 1)    // LM73のslave address

int main()
{
    byte th, tl;

    DDRB  = 0x01;
    PORTB = 0x00;

    usart_init();
    i2c_init();

    // LM73設定
    i2c_start(LM73 | TW_WRITE);
    i2c_write(0x04); // register 4
    i2c_write(0x60); // 14bit resolution
    i2c_stop();

    // ポインタを0にしておく(readするだけで温度が読めるようになる)
    i2c_start(LM73 | TW_WRITE);
    i2c_write(0x00); // register 0
    i2c_stop();

    while(1){
        // 温度受信
        i2c_start(LM73 | TW_READ);
        th = i2c_read_ack(); // 上位読み込み
        tl = i2c_read_nak(); // 下位読み込み(最後はNAKを返す)
        i2c_stop();

        // 結果出力 (マイナスは考えない)
        puthex2(th);
        puthex2(tl);
        putch(' ');
        putdec2(th << 1 | tl >> 7); // 結果出力 整数部
        putch('.');
        putdec2(((tl & 0x7f) * 200) >> 8); // 結果出力 小数部
        putch(0x0d);    // 改行
        putch(0x0a);

        _delay_ms(500); // 0.5秒待つ
    }
}

USART関連(usart_init、getch、putch)は前回と同じなので省略しました。
i2c関係は以下です。
i2c_init(初期化)、
i2c_start(スタートビットを送る)、
i2c_stop(ストップビットを送る)、
i2c_write(1バイト、マスター→スレーブ通信)、
i2c_read_ack(マスター←スレーブ通信とack返答)、
i2c_read_nak(マスター←スレーブ通信とack返答)
エラーを見つけたらi2c_errorに飛んで無限ループになります。
i2c_startは厳密には4種類考えられます(R/Wとstart, repeat start)が、たいへんなので1つにまとめています。

TeraTermで受信している様子です。

左側が受け取った生データで右側が換算した値です。27℃くらいです。
受信データは、16ビット中最上位が符号ビット、8ビットが整数部、7ビットが小数部になっています。11ビット精度のときは、符号1 + 整数部8 + 小数部2です。14bitだと小数部が5ビットに伸びます。

I2Cは、今回初めて使いました。なかなか複雑でデバッグがたいへんでした。

AVRのUSARTを使ってみる

2008-09-23 16:12:13 | AVR
AVRでUSARTを使ってみました。
MAX232のようなレベル変換のICでなくて、秋月で売っているFT232RL USBシリアル変換を使っています(通販番号K-01977)。
FT232RLは3.3VのLDOを内蔵していて、外部に3.3Vを給電することができます。データシートを見ると(4.2)最大50mAを供給できるそうです。入出力の電圧はVCCIOというpinにいれる電圧で決まります。秋月モジュールではJ1を1-2ショートで5V、2-3ショートで3.3Vを選べます。VCCIOはCN2の4pinに出ているので、ジャンパを取り去って、外部から電圧を決めてやることもできます。

秋月のモジュールにはリセット用抵抗端子PU1, PU2というのがあります。PU2の電圧を10kΩ2個で分圧してPU1に出ています(PU2-10kΩ-PU1-10kΩ-GND)。PU1がPU2の半分の電圧になる回路になっています。ついてきた紙には、この回路の使い方が書いてありませんでした。FT232RLのデータシートを見ると、RESET#の説明に
Active low reset pin. This can be used by an external device to reset the FT232R. If not required can be left unconnected, or pulled up to VCC.
と書かれています。resetはオープンにするかプルアップでいいので、秋月のモジュールにあるリセット用回路は使う必要がないみたいです。何のためについている回路なんでしょうね?

秋月のモジュールは次の4本を接続するだけで使えるようになります。

1pin TXD PC→マイコンへのデータ伝送
5pin RXD マイコン→PCへのデータ伝送
7pin GND (24pin GND、内部でつながっている)
4pin VIO I/Oの電圧を外部から給電する(J1のジャンパは取り外す)

秋月モジュールから給電するときは、VIOのかわりに5VならVCC(15pin, 21pin)、3.3Vなら3V3(19pin)をつなぎます。

ATMega88側はRXD(2pin)を秋月モジュールのTXDに、TXD(3pin)を秋月モジュールのRXDにつなぎます。あべこべにつなぎます。お約束通り、最初はTXD同士、RXD同士つないで動かない理由が分かりませんでした。

AVRの初期設定は次の3つです。
(1) UBRR0でボーレートの設定(必要ならUCSR0AのU2X0も設定)
(2) UCSR0Cでパリティ、データビット長、ストップビットの設定
(3) UCSR0Bで送受信の許可

ソースです。
#include <avr/io.h>

typedef unsigned char byte;

byte getch(void)
{
    while(!(UCSR0A & _BV(RXC0))) ;
    return UDR0;
}

void putch(byte c)
{
    while(!(UCSR0A & _BV(UDRE0))) ;
    UDR0 = c;   
}

void usart_init(void)
{
#if 0
    UBRR0 = 51; // 9600bps @ 8MHz
#else
    UBRR0 = 25; // 38400bps @ 8MHz
    UCSR0A = _BV(U2X0);
#endif

    // 19.10.4 USART制御/状態レジスタC
    //         ++------ UMSEL01:UMSEL00 00 非同期動作
    //         ||++---- UPM01:UPM00     00 パリなし
    //         ||||+--- USBS0            0 ストップビット 1ビット
    //         |||||++- UCSZ01:UCSZ00   11 データビット 8ビット 
    //         |||||||+ UCPOL0           0 クロック極性立ち上がり
    UCSR0C = 0b00000110;

    // 19.10.3 USART制御/状態レジスタB
    //         +------- RXCIE0 0
    //         |+------ TXCIE0 0
    //         ||+----- UDRIE0 0
    //         |||+---- RXEN0  1 受信許可
    //         ||||+--- TXEN0  1 送信許可
    //         |||||+-- UCSZ02 0 データビット長
    //         ||||||+- RXB80  0 受信データビット8
    //         |||||||+ TXB80  0 送信データビット8
    UCSR0B = 0b00011000;
}

int main()
{
    // LED設定
    DDRB = 0x01;
    PORTB = 0x01;

    usart_init();

    while(1){
        putch(getch());
        PORTB ^= 0x01;
    }
}

送られてきたデータをエコーバックして、1文字毎にLEDを反転します。
送受信の関数名は最初getcとputcだったのですが、内部モジュールと名前がぶつかっているという警告が出たのでgetchとputchに名前を変えました。

とりあえず、外とお話しできるようになるとデバッグが楽になります。