아트메가/ATMEGA32U4 Breakout

ATMEGA32U4 breakout-보드 사용하기 5편(I2C)

원원 2024. 4. 7. 00:36

안녕하세요. 오늘은 기존의 프로젝트에 I2C기능을 추가하겠습니다. 

기본적인 I2C기능은 아래의링크 참고바랍니다
1) ATMEGA128 TWI(I2C)통신 알아보기 1편
2) ATMEGA128 TWI(I2C)통신 알아보기 2편
3) ATMEGA128 TWI(I2C)통신 알아보기 3편

위에 적은  I2C글에서는 write / read함수 하나에 start, address write, data write등등 기능들을 함수 하나에 넣어놨는데 이번에는 이런 기능들을 분리해서 함수로 구현하고 write, read함수에서 사용하는 형식으로 하겠습니다

void i2cInit()
{
    PORTD |= 0x03; // SDA,SCL pull-up setting PD0: SCL, PD1: SDA
    TWBR = 0x48; /* SCL 100kHz */
    TWSR = 0x00;  	
    TWCR = (1<<TWINT)|(1<<TWEN);
}

void i2c_stop()
{
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
}

uint8_t i2c_start()
{
    _delay_ms(1);
    /* Start */
    TWCR = (1<<TWSTA)|(1<<TWINT)|(1<<TWEN); //start

    /* Start check */
    while(1)
    {
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x08))
            return 1;
    }
}

uint8_t i2c_restart()
{
    /* REPEAT START */
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
    /* ReStart check */
    while(1)
    {
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x10))
          return 1;
        else if ((TWCR & 0x80) && ((TWSR & 0xf8) == 0x08))
        {
            return 0;
        }
    }
}


uint8_t i2c_write_addr(uint8_t addr) // opt 0: search, 1: normal
{
    /* Address send */
    TWDR = addr<<1 | 0; // slave address, write
    TWCR = (1<<TWINT)|(1<<TWEN);

    /* ACK Check */
    while(1)
    {
        if((TWCR & 0x80) && ((TWSR & 0xf8) ==0x18))
        {
            return 1;
        }
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x20))
        {
            return 0;
        }
    }
}

uint8_t i2c_read_addr(uint8_t addr)
{
    /* Address send */
    TWDR = addr<<1 | 1; // slave address, write
    TWCR = (1<<TWINT)|(1<<TWEN);

    /* ACK Check */
    while(1)
    {
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x40))
        {
            return 1;
        }
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x48))
        {
            return 0;
        }
    }
}
uint8_t i2c_write_data(uint8_t data)
{
    /*  Data send */
    TWDR = data;
    TWCR = (1<<TWINT)|(1<<TWEN);

    /* Data send check */
    while(1)
    {
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x28))
        {
            return 1;
        }        
        if((TWCR & 0x80) && ((TWSR & 0xf8) == 0x30))
        {
            return 0;
        }
    }
}

uint8_t i2c_write_read(uint8_t *data1)
{
    /* send NACK*/
    TWCR = (1<<TWINT)|(1<<TWEN); //NACK
    /* Data receive Check */
    while(1)
    {
        if((TWCR & 0x80) && (((TWSR & 0xf8) == 0x50) || ((TWSR & 0xf8) == 0x58)))
        {
            *data1 = TWDR;
            return 1;
        }     
    }
}

기능별로 함수를 만들었습니다. 기능별로 만들어놓으면 장점이 write,read함수에 들어가는 코드의 양이 줄어듭니다. 또한 원하는 write,read 프로토콜에 맞게 구현하기가 편해집니다.


write함수를 구현해보겠습니다. (address , data 순서로 보내는 프로토콜)
write를 할때 순서입니다.
1. I2C start
2. I2C address+write send
3. I2C data send
4. I2C stop

1번은 i2c_start();에서 합니다
2번은 i2c_write_addr(addr);에서 합니다
3번은 i2c_write_data(data1);에서 합니다
4번은 i2c_stop();에서 합니다
여기서 address, data, data 순서로 보내고싶다면 3번이후에 i2c_write_data(data2)를 추가해주면 됩니다.

uint8_t i2c_write1(uint8_t addr, uint8_t data1)
{
    if(i2c_start() == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_write_addr(addr) == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_write_data(data1) == 0)
    {
        i2c_stop();
        return 0;
    }
    i2c_stop();
    return 1;
}


read함수를 구현해보겠습니다. (address , data 순서로 보내고 restart해서 1바이트 읽는 프로토콜)
read를 할때 순서입니다.
1. I2C start
2. I2C address+write send
3. I2C data send
4. I2C restart
5. I2C address +read send
6. I2C data read
7. I2C stop

1번은 i2c_start();에서 합니다
2번은 i2c_write_addr(addr);에서 합니다
3번은 i2c_write_data(data1);에서 합니다
4번은 i2c_restart();에서 합니다
5번은 i2c_read_addr(addr);에서 합니다
6번은 i2c_write_read(save_data);에서 합니다
7번은 i2c_stop();에서 합니다

uint8_t i2c_read1(uint8_t addr, uint8_t data1, uint8_t *save_data1)
{
    if(i2c_start() == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_write_addr(addr) == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_write_data(data1) == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_restart() == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_read_addr(addr) == 0)
    {
        i2c_stop();
        return 0;
    }
    if(i2c_write_read(save_data1) == 0)
    {
        i2c_stop();
        return 0;
    }
    i2c_stop();
    return 1;
}

이번에 해당 기능을 추가하면서 느낀 게 TWSR(TWI Status Register) 가 매우 중요합니다. I2C 통신의 상태를 나타내줍니다. 통신을 하다가 문제가 생기면 TWSR를 확인해 봐야 합니다. 위의 코드에서는 모든 상황에 대해서 TWSR가 처리가 되어있지는 않고 필요한 것만 해놨습니다

*전송관련 TWSR

*수신관련 TWSR

TWSR을 이용하면 연결된 I2C 제품을 찾는것도 가능합니다
i2c_write_addr함수에서 주소를 보내고 TWSR체크를 합니다. 0x18이면 ACK가 온거고 0x20이면 NAK가 온것입니다
그래서 0x18가오면 해당주소에 해당하는 뭔가가 연결됐다고 보면 됩니다


이제 위에서 적었던 내용을 테스트해보겠습니다. cli 기능을 활용하여 테스트합니다.
(cli 기능은 소스코드 참고바랍니다)
1) i2c search 테스트 : i2c 모듈을 두개 연결해놨습니다. 연결한 제품에 따라서 NAK/ACK가 오는걸 확인할수있습니다

 


2) i2c write테스트 : w는 write를 의미합니다. address ,data1, data2 / address,data1 두가지 write를 구현해놨습니다


3) i2c read테스트 : r는 read를 의미합니다. 결괏값으로 0xf2가 왔고 실제 파형도 0xf2가 측정됐습니다.


소스코드(Commits: ADD I2C)
https://github.com/yhunterr/ATMEGA32u4/commits/main/