湿度・温度センサ(DHT22 (AM2302))をI2Cで読む


湿度・温度センサ(DHT22 (AM2302))は湿度・温度センサより 湿度・温度データ(5バイト)を取り、表示データに加工してRS-232Cで出力してます。 この湿度・温度データ(5バイト)を「I2Cのスレーブ」で出力してみました。。

CPU(ATmega328P)のシステムクロックは内臓の8MHzを使用しました。(工場校正:8MHz±10%)

「I2Cのスレーブ」はCPU(ATmega328P)の内臓のTWIを使用しました。(従って、SCL、SDAのポートは固定)

CPUが「I2Cのスレーブ」を行う場合、CPUの応答が(EEPROM、RTCなどに比べて)遅いのでクロックストレッチ動作が 採用されてます。(証拠はSCLのプルアップ)
クロックストレッチとは、スレーブ側が送信の時、処理する間、強制的にSCLをLowとすることでマスターの クロックを待たせることです。(従って、SCLも入出力となる)
マスターを汎用ポートで行う場合、クロックストレッチの処理を乗っけて下さい。

ディバイスアドレスは0x57です。(左に1ビットシフトしていません)
シーケンシャル読み出し(読み出しデータ:00000010 10001100 00000001 01011111 11101110)
+-+-------+-+=+--------+=+-+-------+-+=+========+-+========+-+   +========+-+-+
|S|1010111|0|0|00000000|0|S|1010111|1|0|00000010|0|10001100|0|...|11101110|1|P|
+-+-------+-+=+--------+=+-+-------+-+=+========+-+========+-+   +========+-+-+
   DA      W A ADDRESS  A   DA      R A DATA1    A DATA2    A     DATA5    N
S:START CONDITION
P:STOP CONDITION
R:READ
W:WRITE
A:ACK
N:NAK
DA:DEVICE ADDRESS
ADDRESS:DATA ADDRESS BYTE
DATA1〜5:DATA BYTE
-:WRITE
=:READ


DHT22_I2C_kairo.gif

回路図(スレーブ)


DHT22_I2C_master_kairo.gif

回路図(マスター)


DHT22_result.jpg

結果の図


How to Compile (コンパイルのしかた)

スレーブプログラム
/*
 *  DHT22_I2C.c
 *  システムクロック:8MHz
 *  湿度センサ「DHT22」から湿度、温度を得て、
 *  得られた湿度、温度をI2C(スレーブアドレス0x57、
  * データ5バイト)で出力する。
 *  ATmega328P
 *
 *
 *  Fuses(システムクロック:8MHz)
 *
 *              Int. RC Osc
 *  EXTENDED    0xFD
 *  HIGH        0xD9
 *  LOW         0xE2
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8.0E6                 /* 8 MHz */
#include <util/delay.h>

#define SLAVE_CPU_ADDRESS 0xae
#define I2C_BufferSize 5

///void delay_us(unsigned short t){while(t--) _delay_us(1);}
void delay_ms(unsigned short t){while(t--) _delay_ms(1);}
static void initI2C_SLAVE_CPU(void);

unsigned char I2C_Buffer[I2C_BufferSize];

int main(void)
{
    int i;
    static unsigned char pri_data, current_data;
    unsigned char change_data;
    unsigned short last_tcnt1;
    static unsigned char data[5];

    /* ATmega328系 */
    DDRB = 0b00000000;      /* XTAL2 XTAL1 SCK   MISO  MOSI  ----- ----- ----- */
    DDRC = 0b00000000;      /* xxxxx RESET ----- ----- ----- ----- ----- DATA  */
    DDRD = 0b00000000;      /* ----- ----- ----- ----- ----- ----- TXD   RXD   */
    PORTB = 0b00111111;
    PORTC = 0b00111110;
    PORTD = 0b11111111;

    initI2C_SLAVE_CPU();
    TCCR1A = 0b00000000;        /* ノーマル */
    TCCR1B = 0b00000001;        /* 8MHz / 1 = 8MHz (125n) */

    sei();

    /* DHT22が準備するのを待つ */
    delay_ms(2000);

    while(1){
        /* 立下りエッジの検出回数リセット */
        i = 0;

        /* スタート信号を送信 */
        PORTC &= ~_BV(0);
        DDRC |= _BV(0);
        delay_ms(2);
        PORTC |= _BV(0);
        DDRC &= ~_BV(0);

        /* DHT22の出力波形より温湿度データを得る */
        while(1){
            /* 立下りエッジの検出 (42回) */
            pri_data = current_data;
            current_data = PINC & _BV(0);
            change_data = pri_data ^ current_data;
            if(change_data != 0 && current_data == 0){

                /* 立下りエッジ間の時間を得る */
                last_tcnt1 = TCNT1;
                TCNT1 = 0;

                /* 立下りエッジ間の時間の長短によりビット値を得る  */
                /* 100us / 0.125us = 800 */
                switch(i){
                case 0:
                case 1:
                    i++;
                    break;
                case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9:
                    data[0] <<= 1;
                    if(last_tcnt1 > 800)
                        data[0] += 1;
                    i++;
                    break;
                case 10:case 11:case 12:case 13:case 14:case 15:case 16:case 17:
                    data[1] <<= 1;
                    if(last_tcnt1 > 800)
                        data[1] += 1;
                    i++;
                    break;
                case 18:case 19:case 20:case 21:case 22:case 23:case 24:case 25:
                    data[2] <<= 1;
                    if(last_tcnt1 > 800)
                        data[2] += 1;
                    i++;
                    break;
                case 26:case 27:case 28:case 29:case 30:case 31:case 32:case 33:
                    data[3] <<= 1;
                    if(last_tcnt1 > 800)
                        data[3] += 1;
                    i++;
                    break;
                case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:
                    data[4] <<= 1;
                    if(last_tcnt1 > 800)
                        data[4] += 1;
                    i++;
                    break;
                }
            }
            if(i == 42)
             break;
        }
        cli();
        for(i=0; i<5; i++)
            I2C_Buffer[i] = data[i];
        sei();

        /* サンプリング周期 */
        delay_ms(2000);
    }
}

/*
 *  スレーブCPUの初期化
 */
void initI2C_SLAVE_CPU(void)
{
    unsigned char device_address;

    device_address = SLAVE_CPU_ADDRESS;
    device_address &= 0xfe;
/// device_address |= 1;                            /* ジェネラル・コール・アドレス許可 */
    TWAR = device_address;
    TWCR = (_BV(TWEA) | _BV(TWEN) | _BV(TWIE));     /* スレーブACK応答許可、割り込み許可 */
}

ISR(TWI_vect)
{
    static int I2C_DataIndex;
    static int i;

    if((TWCR & _BV(TWINT)) == 0)
        goto FIN;

    switch(TWSR & 0xf8){
    case 0x60:                      /* 自宛SLA+W受信, ACK応答 */
        i = 0;
        break;
    case 0x80:                      /* 自宛データバイト受信, ACk応答 */
        if(i == 0)
            I2C_DataIndex = TWDR;   /* 自宛データバイト受信の1バイト目は書き込み開始アドレス */
        else{
            I2C_Buffer[I2C_DataIndex++] = TWDR;
            if(I2C_DataIndex >= I2C_BufferSize)
                I2C_DataIndex = 0;
        }

        i++;
        break;
    case 0xa8:                      /* 自宛SLA+R受信, ACK応答 */
    case 0xb8:                      /* データバイト送信, ACK受信 */
        TWDR = I2C_Buffer[I2C_DataIndex++];
        if(I2C_DataIndex >= I2C_BufferSize)
            I2C_DataIndex = 0;
        break;
    case 0xa0:                      /* 自指定中の停止条件または再送開始条件検出 */
    case 0xc0:                      /* データバイト送信, NACK受信 */
        break;
    }

    TWCR = (_BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE));
FIN:;
}


How to Compile (コンパイルのしかた)

マスタープログラム
/*
 *  DHT22_I2C_master.c
 *  DHT22_I2CのレジスタをI2Cで読み、
 *  得られた湿度、温度をRS-232C(115200 8-N-1)で出力する。
 *  ATmega328P
 *
 *
 *  Fuses(システムクロック:20MHz)
 *
 *              水晶
 *  EXTENDED    0xFD
 *  HIGH        0xD9
 *  LOW         0xF7
 */

#include <stdio.h>
#include <stdlib.h>                 /* abs() */
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 20.0E6                /* 20 MHz */
#include <util/delay.h>

#define SCL_OUT DDRC |= _BV(5)
#define SDA_OUT DDRC |= _BV(4)
#define SCL_IN DDRC &= ~_BV(5)
#define SDA_IN DDRC &= ~_BV(4)
#define SCL_H wait_scl_h()
#define SCL_H2 PORTC |= _BV(5)
#define SDA_H PORTC |= _BV(4)
#define SCL_L PORTC &= ~_BV(5)
#define SDA_L PORTC &= ~_BV(4)
#define SCL (PINC & _BV(5))
#define SDA (PINC & _BV(4))
/* 変更無し */
#define ACK 0
/* ハードに合わせて変更する (SDAのビット) */
#define NAK _BV(4)

#define RH_WRITE 0xae
#define RH_READ 0xaf

void delay_us(unsigned short t){while(t--) _delay_us(1);}
void delay_ms(unsigned short t){while(t--) _delay_ms(1);}
void initUSART0(void);
void Send0(char *str);
int RH_read_i2c(unsigned char addr, unsigned char *data, unsigned char size);

int main(void)
{
    static unsigned char data[5];
    static char buffer[32];
    unsigned char sum;
    short RH, T_;

    /* ATmega328P */
    DDRB = 0b00000000;      /* XTAL2 XTAL1 SCK   MISO  MOSI  ----- ----- ----- */
    DDRC = 0b00000000;      /* xxxxx RESET ----- ----- ----- ----- ----- ----- */
    DDRD = 0b00000000;      /* ----- ----- ----- ----- ----- ----- TXD   RXD   */
    PORTB = 0b00111111;
    PORTC = 0b00111111;
    PORTD = 0b11111100;

    initUSART0();
    sei();

    Send0("START\r");

    /* DHT22が準備するのを待つ */
    delay_ms(2000);
    /* DHT22がデータを取り終えるのを待つ */
    delay_ms(2000);

    while(1){
        RH_read_i2c(0, data, 5);

        /* チェックサムの確認 */
        sum = (unsigned char)(data[0] + data[1] + data[2] + data[3]);
        if(sum == data[4]){

            /* 計算 */
            RH = data[0] * 256 + data[1];
            T_ = (data[2] & 0x7f) * 256 + data[3];
            if((data[2] & 0x80) != 0)
                T_ = -T_;

            /* 表示 */
            sprintf(buffer, "%d.%d %%    %d.%d ℃\r", RH / 10, RH % 10, T_ / 10, abs(T_ % 10));
            Send0(buffer);
        }

        /* サンプリング周期 */
        delay_ms(2000);
    }
}

void initUSART0(void)
{
    UBRR0 = 21;             /* U2X0 = 1, 115200ボー/20MHzクロック */
    UCSR0A = 0b00000010;    /* U2X0 = 1 */
    UCSR0B = 0b00001000;    /* 送信イネーブル */
    UCSR0C = 0b00000110;    /* 8ビット, パリティなし, ストップ・ビット1 */
}

/*
 *  1データ送信
 */
void Tx0(char data)
{
    while((UCSR0A & _BV(5)) == 0)
        ;
    UDR0 = (unsigned char)data;
}

/*
 *  文字列を送信
 */
void Send0(char *str)
{
    while(*str != '\0'){
        Tx0(*str);
        str++;
    }
}
/*
 *  クロックストレッチ以上の待ち時間を作る(100μS)
 *
 *  ATmega328P内臓のTWIを使用したI2Cスレーブ(DHT22_I2C.c)の場合
 *  クロックストレッチの待ち時間は6μSでした
 */
void wait_scl_h(void)
{
    unsigned char i;

    SCL_IN;

    for(i=0; i<10; i++){
        if(SCL != 0)
            break;
        delay_us(10);
    }

    SCL_H2;

    SCL_OUT;
}

void start_i2c(void)
{
    SCL_OUT; SDA_OUT;

    SCL_L;
    SDA_H;
    SCL_H;
    delay_us(1);            /* TSU:STA = 600ns */

    SDA_L;
    delay_us(1);            /* THD:STA = 600ns */
    SCL_L;

    SCL_OUT; SDA_IN;
}

void stop_i2c(void)
{
    SCL_OUT; SDA_OUT;

    SCL_L;
    SDA_L;

    SCL_H;
    delay_us(1);            /* TSU:STO =  600ns */
    SDA_H;
    delay_us(1);            /* TBUF = 1300ns */
    delay_us(1);

    SCL_L;

    SCL_OUT; SDA_IN;
}

/*
 * 戻り値 ACK:0, NAK:SDA
 */
unsigned char writebyte(unsigned char data)
{
    unsigned char i;

    SCL_OUT; SDA_OUT;

    for(i=0; i<8; i++){
        if(data & 0x80)
            SDA_H;
        else
            SDA_L;

        SCL_H;
        delay_us(1);        /* THIGH = 600ns */
        SCL_L;
        delay_us(1);        /* TLOW = 1300ns */
        delay_us(1);

        data <<= 1;
    }   

    SCL_OUT; SDA_IN;

    SCL_H;
    delay_us(1);            /* THIGH = 600ns */
    i = SDA;
    SCL_L;
    delay_us(1);            /* TLOW = 1300ns */
    delay_us(1);

    return i;
}

/*
 * 戻り値 読み込みデータ(8ビット)
 */
unsigned char readbyte(void)
{
    unsigned char i, data;

    data = 0;
    for(i=0; i<8; i++){
        data <<= 1;

        SCL_H;
        delay_us(1);        /* THIGH = 600ns */
        if(SDA)
            data++;
        SCL_L;
        delay_us(1);        /* TLOW = 1300ns */
        delay_us(1);
    }

    return data;
}

void ack(void)
{
    SCL_OUT; SDA_OUT;

    SDA_L;
    SCL_H;
    delay_us(1);            /* THIGH = 600ns */
    SCL_L;
    delay_us(1);            /* TLOW = 1300ns */
    delay_us(1);

    SCL_OUT; SDA_IN;
}

void nak(void)
{
    SCL_OUT; SDA_OUT;

    SDA_H;
    SCL_H;
    delay_us(1);            /* THIGH = 600ns */
    SCL_L;
    delay_us(1);            /* TLOW = 1300ns */
    delay_us(1);

    SCL_OUT; SDA_IN;
}


/*
 * 戻り値 0:成功, -1:失敗
 *
 * コントロール・バイトの書き込み
 */
int send_ctrl(unsigned char ctrl)
{
    unsigned char ret;

    start_i2c();
    ret = writebyte(ctrl);
    if(ret == NAK)
        return -1;

    return 0;
}

/*
 * 戻り値 0:成功, -1:失敗
 *
 * コントロール・バイトの書き込み以外はACKをチェックしてません
 */
int RH_read_i2c(unsigned char addr, unsigned char *data, unsigned char size)
{
    int ret;
    unsigned char i;

    ret = send_ctrl(RH_WRITE);
    if(ret == -1) return -1;
    writebyte(addr);
    ret = send_ctrl(RH_READ);
    if(ret == -1) return -1;
    for(i=0; i<size-1; i++){
        *data++ = readbyte();
        ack();
    }
    *data = readbyte();
    nak();
    stop_i2c();
    return 0;
}


真空管
戻る