본문 바로가기
MCU/AVR

AVR128DB48 I2C 사용 방법 및 예제 코드(수정)

by linuxgo 2025. 8. 19.

1. AVR128DB48 I2C 모듈 개요

Microchip의 AVR128DB48 마이크로컨트롤러는 두 개의 I2C(TWI: Two-Wire Interface) 모듈(TWI0, TWI1)을 제공하여 센서, 디스플레이, 메모리 장치 등과의 통신에 적합합니다. I2C 모듈은 마스터 및 슬레이브 모드를 지원하며, 저전력 애플리케이션과 높은 호환성을 제공합니다. 이 문서에서는 AVR128DB48의 TWI0 및 TWI1 설정 방법, Bitfield 구조를 활용한 레지스터 설정, 그리고 실용적인 예제 코드를 제공하여 초보자와 숙련된 개발자 모두 쉽게 활용할 수 있도록 돕습니다.

TWI block diagram

주요 사양

  • I2C 채널:
    • TWI0: SDA (PA2/PC2), SCL (PA3/PC3) (Master/Slave 가능, PORTMUX ALT2로 PC2/PC3 선택)
    • TWI1: SDA (PB2/PF2), SCL (PB3/PF3) (Master/Slave 가능, PORTMUX DEFAULT로 PB2/PB3, ALT2로 PF2/PF3 선택)
  • 지원 모드: 마스터, 슬레이브
  • 주요 레지스터 (TWI0 및 TWI1 공통):
    • TWIx.MCTRLA: 마스터 제어 레지스터
    • TWIx.MBAUD: 마스터 보율 설정
    • TWIx.MSTATUS: 마스터 상태
    • TWIx.MDATA: 마스터 데이터
    • TWIx.SCTRLA: 슬레이브 제어 레지스터
    • TWIx.SADDR: 슬레이브 주소
  • 인터럽트: 데이터 전송/수신, 에러, 충돌 감지
  • 전압 레벨: 1.8V~5.5V 지원주요 수정 사항 상세
TWI
채널

(VQFN48/TQFP48 기준)
멀티플렉싱  세부Notes
(데이터시트 기준)
TWI0 PA2 (SDA, MS)
PA3 (SCL, MS)
기본 핀
(PORTMUX DEFAULT)
Master/Slave 가능 (MS).
VQFN32/TQFP32: 동일.
TWI0 PC2 (SDA, MS)
PC3 (SCL, MS)
대체 핀
(PORTMUX ALT2)
SOIC28/SSOP28/SPDIP28: 동일.
TWI1 PB2 (SDA, MS)
PB3 (SCL, MS)
기본 핀
(PORTMUX DEFAULT)
Master/Slave 가능 (MS).
TWI1 PF2 (SDA, MS)
PF3 (SCL, MS)
대체 핀
(PORTMUX ALT2)
VQFN64/TQFP64에서 사용 가능.
PF 포트는 64핀 패키지에만 존재
(48핀 이하에서는 PB2/PB3만).
Slave-only (S) 핀 없음.
  • PORTMUX 설정: 코드 구현 시 PORTMUX.TWIROUTEA 레지스터로 선택 (0: DEFAULT, 2: ALT2).
  • 패키지별 차이: VQFN48/TQFP48 기준으로 설명. 64핀(VQFN64/TQFP64)에서는 PF 포트 추가, 32핀(VQFN32/TQFP32)에서는 PA2/PA3/PC2/PC3 중심.
  •  
  • 기타: 외부 풀업 저항 (4.7kΩ 권장) 필수
  • 전력 관리: 저전력 모드에서 동작 지원
  • 클럭 주파수: 최대 400kHz (Fast-mode), 100kHz (Standard-mode)

2. I2C Bitfield 설정 상세

AVR128DB48의 I2C 모듈은 <avr/io.h> 헤더 파일을 통해 Bitfield 구조로 접근합니다. TWI0와 TWI1은 동일한 레지스터 구조를 사용하며, TWI0.* 또는 TWI1.*로 구분됩니다. 주요 레지스터는 다음과 같습니다:

2.1 TWIx.MCTRLA (마스터 제어 레지스터)

  • bit.ENABLE: TWI 모듈 활성화 (1: 활성화)
  • bit.SMEN: 스마트 모드 활성화
  • bit.RIEN: 읽기 인터럽트 활성화
  • bit.WIEN: 쓰기 인터럽트 활성화

2.2 TWIx.MBAUD (마스터 보율 레지스터)

  • 보율 설정: BAUD = ((F_CPU / (2 * F_SCL)) - 5)
    • 예: 24MHz에서 100kHz 설정 시, BAUD = ((24000000 / (2 * 100000)) - 5) = 115

2.3 TWIx.MSTATUS (마스터 상태 레지스터)

  • bit.BUSSTATE: 버스 상태 (Idle, Owner, Busy)
  • bit.RIF: 읽기 인터럽트 플래그
  • bit.WIF: 쓰기 인터럽트 플래그
  • bit.CLKHOLD: 클럭 홀드 상태

2.4 TWIx.MDATA (마스터 데이터 레지스터)

  • 데이터 송수신용 레지스터
  • 쓰기: 전송할 데이터
  • 읽기: 수신된 데이터

3. I2C 설정 절차

  1. 시스템 초기화:
    •   시스템 클럭 설정 (24MHz, set_system_clock())
    •   인터럽트 비활성화 (cli())
  2. 포트 설정:
    • TWI0: PA2(SDA)/PA3(SCL) (기본, PORTMUX DEFAULT) 또는 PC2(SDA)/PC3(SCL) (대체, PORTMUX ALT2)에 외부 풀업 저항 연결
    • TWI1: PB2(SDA)/PB3(SCL) (기본, PORTMUX DEFAULT) 또는 PF2(SDA)/PF3(SCL) (대체, PORTMUX ALT2)에 외부 풀업 저항 연결
    • PORTx.DIRSET으로 출력 설정
  3. I2C 모듈 활성화:
    •   TWIx.MCTRLA로 마스터 모드 활성화
    •   TWIx.MBAUD로 보율 설정
  4. 마스터 동작:
    •   TWIx.MADDR로 슬레이브 주소 전송
    •   TWIx.MDATA로 데이터 송수신
  5. 인터럽트 설정 (선택):
    •   TWIx.MCTRLB로 인터럽트 조건 설정
  6. 상태 확인:
    •   TWIx.MSTATUS로 버스 상태 및 에러 확인

4. I2C 설정 고려사항

  • 클럭 설정: 정확한 보율 계산 및 클럭 안정화 확인
  • 풀업 저항: SDA, SCL 라인에 4.7kΩ 저항 필수
  • 버스 충돌: 다중 마스터 환경에서 TWIx.MSTATUS.BUSERR 확인
  • 저전력: 스마트 모드(SMEN)로 전력 소모 감소
  • 타임아웃: 긴 대기 시간 방지를 위해 타임아웃 설정 권장
  • 채널 선택: TWI0은 기본 채널, TWI1은 추가 장치 연결이나 핀 충돌 회피 시 사용

5. 실용적인 I2C 예제 코드 (Bitfield 구조)

아래는 AVR128DB48의 TWI0 및 TWI1을 활용한 예제 코드입니다. Atmel Studio 또는 MPLAB X IDE에서 AVR-GCC로 실행 가능합니다. 예제는 24LC256 EEPROM을 대상으로 작성되었습니다.

AVR128DB48 I2C

5.1 예제 1: TWI0 마스터로 EEPROM에 데이터 쓰기 및 읽기

// File: i2c_twi0_eeprom.c
// Description: AVR128DB48 TWI0 마스터로 24LC256 EEPROM 쓰기/읽기 + UART 전송
// Compiler: AVR-GCC
// Target: AVR128DB48

#ifndef F_CPU
#define F_CPU 24000000UL  // 시스템 클록 주파수 (24 MHz)
#endif

#include <avr/io.h>        // AVR 입출력 관련 헤더 파일
#include <util/delay.h>    // 지연 함수 관련 헤더 파일

#define I2C_BAUD(F_SCL) (((F_CPU / (2UL * F_SCL)) - 5)) // I2C 보율 계산 (100kHz, 2UL로 명시)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소
#define BAUD_RATE 9600    // UART 보드레이트 9600bps
#define BAUD_VALUE (((F_CPU * 64UL) / (16UL * BAUD_RATE)) - 1) // UART BAUD 값 (9999)

// 시스템 클럭 설정: 24MHz OSCHF, XOSC32K 참조
void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    uint32_t timeout = 24000000UL;
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm) && timeout--); // 안정화 대기
    if (timeout == 0) return; // 타임아웃 시 기본 클록 유지
    
    // 내부 OSCHF를 24MHz로 설정, XOSC32K 오토튜닝
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF 선택
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성화
}

// TWI0 초기화: PC2(SDA), PC3(SCL) 사용
void i2c_twi0_init(void) {
    PORTMUX.TWIROUTEA = PORTMUX_TWI0_ALT2_gc; // PC2/SDA, PC3/SCL 선택
    PORTC.DIRSET = PIN2_bm | PIN3_bm; // PC2, PC3 출력 설정
    PORTC.PIN2CTRL |= PORT_PULLUPEN_bm; // SDA 풀업 활성화
    PORTC.PIN3CTRL |= PORT_PULLUPEN_bm; // SCL 풀업 활성화
    
    TWI0.MCTRLA = TWI_ENABLE_bm; // TWI 마스터 모드 활성화
    TWI0.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; // 버스 상태 초기화
}

// UART0 초기화: PA0(TXD0), PA1(RXD0)
void uart_init(void) {
    PORTA.DIRSET = PIN0_bm; // PA0(TXD0) 출력
    PORTA.DIRCLR = PIN1_bm; // PA1(RXD0) 입력
    PORTA.OUTCLR = PIN0_bm; // TXD0 초기 Low
    
    USART0.BAUD = BAUD_VALUE; // 보드레이트 설정
    USART0.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | // 비동기 모드
                   USART_PMODE_DISABLED_gc |     // 패리티 없음
                   USART_SBMODE_1BIT_gc |        // 1 스톱 비트
                   USART_CHSIZE_8BIT_gc;         // 8비트 데이터
    USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // 송신기 및 수신기 활성화
}

// UART 데이터 전송
void uart_transmit(uint8_t data) {
    while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터 비어질 때까지 대기
    USART0.TXDATAL = data; // 데이터 전송
}

// I2C 시작 조건 전송, 에러 핸들링 포함
uint8_t i2c_twi0_start(uint8_t addr) {
    TWI0.MADDR = (addr << 1); // 7비트 주소 + 쓰기(0)/읽기(1) 비트
    while (!(TWI0.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))) {
        if (TWI0.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI0.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm; // 에러 플래그 클리어
            return 1; // 버스 에러 또는 중재 손실
        }
    }
    if (TWI0.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK 수신
    }
    return 0; // 성공
}

// 데이터 쓰기
uint8_t i2c_twi0_write(uint8_t data) {
    TWI0.MDATA = data;
    while (!(TWI0.MSTATUS & TWI_WIF_bm)) {
        if (TWI0.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI0.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm;
            return 1; // 에러
        }
    }
    if (TWI0.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK
    }
    return 0; // 성공
}

// 데이터 읽기 (ACK 전송)
uint8_t i2c_twi0_read_ack(void) {
    TWI0.MCTRLB &= ~TWI_ACKACT_bm; // ACK 전송 설정
    while (!(TWI0.MSTATUS & TWI_RIF_bm));
    return TWI0.MDATA;
}

// I2C 정지 조건 전송
void i2c_twi0_stop(void) {
    TWI0.MCTRLB = TWI_MCMD_STOP_gc;
}

// EEPROM에 1바이트 쓰기 (ACK 폴링 포함)
uint8_t eeprom_write_byte(uint16_t mem_addr, uint8_t data) {
    uint8_t status;
    status = i2c_twi0_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return status;
    status = i2c_twi0_write(mem_addr >> 8); // 상위 주소
    if (status) return status;
    status = i2c_twi0_write(mem_addr & 0xFF); // 하위 주소
    if (status) return status;
    status = i2c_twi0_write(data); // 데이터 쓰기
    if (status) return status;
    i2c_twi0_stop();
    
    // ACK 폴링으로 쓰기 완료 확인
    do {
        status = i2c_twi0_start(EEPROM_ADDR);
        if (status == 2) _delay_ms(1); // NACK 시 1ms 대기
    } while (status == 2); // ACK 수신까지 반복
    i2c_twi0_stop();
    return status;
}

// EEPROM에서 1바이트 읽기
uint8_t eeprom_read_byte(uint16_t mem_addr) {
    uint8_t status;
    status = i2c_twi0_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return 0xFF; // 에러 시 0xFF 반환
    status = i2c_twi0_write(mem_addr >> 8); // 상위 주소
    if (status) return 0xFF;
    status = i2c_twi0_write(mem_addr & 0xFF); // 하위 주소
    if (status) return 0xFF;
    status = i2c_twi0_start(EEPROM_ADDR | 1); // 읽기 모드 시작
    if (status) return 0xFF;
    uint8_t data = i2c_twi0_read_ack();
    i2c_twi0_stop();
    return data;
}

// GPIO 초기화: PB3(LED0) 출력 설정
void gpio_init(void) {
    PORTB.DIRSET = PIN3_bm; // PB3 출력 설정
    PORTB.OUTSET = PIN3_bm; // LED 초기값 High (켜짐)
}

int main(void) {
    set_system_clock(); // 시스템 클럭 설정
    i2c_twi0_init(); // TWI0 초기화
    uart_init(); // UART0 초기화
    gpio_init(); // GPIO 초기화

    while (1) {
        // EEPROM 0x00 주소에 0x55 쓰기
        if (eeprom_write_byte(0x00, 0x55) == 0) { // 성공 시
            _delay_ms(100);
            uint8_t data = eeprom_read_byte(0x00); // 데이터 읽기
            if (data == 0x55) {
                PORTB.OUTTGL = PIN3_bm; // LED 토글
                uart_transmit(data); // 읽은 데이터 UART로 전송
            } else {
                uart_transmit(0xFF); // 에러 시 0xFF 전송
            }
        } else {
            uart_transmit(0xFE); // 쓰기 실패 시 0xFE 전송
        }
        _delay_ms(500); // 점멸 및 전송 주기
    }

    return 0; // 도달하지 않음
}

설명:

  • 기능: TWI0을 통해 24LC256 EEPROM에 데이터를 쓰고 읽어 PB3 LED를 토글
  • 설정: TWI0 마스터 모드, 100kHz, PC2(SDA)/PC3(SCL) 풀업 저항
  • 출력: EEPROM 데이터가 0x55일 때 LED 점멸

5.2 예제 2: TWI1 마스터로 EEPROM에 데이터 쓰기

// File: i2c_twi1_eeprom.c
// Description: AVR128DB48 TWI1 마스터로 24LC256 EEPROM 쓰기/읽기 + UART 전송
// Compiler: AVR-GCC
// Target: AVR128DB48

#ifndef F_CPU
#define F_CPU 24000000UL  // 시스템 클록 주파수 (24 MHz)
#endif

#include <avr/io.h>        // AVR 입출력 관련 헤더 파일
#include <util/delay.h>    // 지연 함수 관련 헤더 파일

#define I2C_BAUD(F_SCL) (((F_CPU / (2UL * F_SCL)) - 5)) // I2C 보율 계산 (100kHz, 2UL로 명시)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소
#define BAUD_RATE 9600    // UART 보드레이트 9600bps
#define BAUD_VALUE (((F_CPU * 64UL) / (16UL * BAUD_RATE)) - 1) // UART BAUD 값 (9999)

// 시스템 클럭 설정: 24MHz OSCHF, XOSC32K 참조
void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    uint32_t timeout = 24000000UL;
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm) && timeout--); // 안정화 대기
    if (timeout == 0) return; // 타임아웃 시 기본 클록 유지
    
    // 내부 OSCHF를 24MHz로 설정, XOSC32K 오토튜닝
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF 선택
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성화
}

// TWI1 초기화: PB0(SDA), PB1(SCL) 사용
void i2c_twi1_init(void) {
    PORTMUX.TWIROUTEA = PORTMUX_TWI1_DEFAULT_gc; // PB0/SDA, PB1/SCL 선택 (기본)
    PORTB.DIRSET = PIN0_bm | PIN1_bm; // PB0, PB1 출력 설정
    PORTB.PIN0CTRL |= PORT_PULLUPEN_bm; // SDA 풀업 활성화
    PORTB.PIN1CTRL |= PORT_PULLUPEN_bm; // SCL 풀업 활성화
    
    TWI1.MCTRLA = TWI_ENABLE_bm; // TWI 마스터 모드 활성화
    TWI1.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI1.MSTATUS = TWI_BUSSTATE_IDLE_gc; // 버스 상태 초기화
}

// UART0 초기화: PA0(TXD0), PA1(RXD0)
void uart_init(void) {
    PORTA.DIRSET = PIN0_bm; // PA0(TXD0) 출력
    PORTA.DIRCLR = PIN1_bm; // PA1(RXD0) 입력
    PORTA.OUTCLR = PIN0_bm; // TXD0 초기 Low
    
    USART0.BAUD = BAUD_VALUE; // 보드레이트 설정
    USART0.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | // 비동기 모드
                   USART_PMODE_DISABLED_gc |     // 패리티 없음
                   USART_SBMODE_1BIT_gc |        // 1 스톱 비트
                   USART_CHSIZE_8BIT_gc;         // 8비트 데이터
    USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // 송신기 및 수신기 활성화
}

// UART 데이터 전송
void uart_transmit(uint8_t data) {
    while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터 비어질 때까지 대기
    USART0.TXDATAL = data; // 데이터 전송
}

// I2C 시작 조건 전송, 에러 핸들링 포함
uint8_t i2c_twi1_start(uint8_t addr) {
    TWI1.MADDR = (addr << 1); // 7비트 주소 + 쓰기(0)/읽기(1) 비트
    while (!(TWI1.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))) {
        if (TWI1.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI1.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm; // 에러 플래그 클리어
            return 1; // 버스 에러 또는 중재 손실
        }
    }
    if (TWI1.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK 수신
    }
    return 0; // 성공
}

// 데이터 쓰기
uint8_t i2c_twi1_write(uint8_t data) {
    TWI1.MDATA = data;
    while (!(TWI1.MSTATUS & TWI_WIF_bm)) {
        if (TWI1.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI1.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm;
            return 1; // 에러
        }
    }
    if (TWI1.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK
    }
    return 0; // 성공
}

// 데이터 읽기 (ACK 전송)
uint8_t i2c_twi1_read_ack(void) {
    TWI1.MCTRLB &= ~TWI_ACKACT_bm; // ACK 전송 설정
    while (!(TWI1.MSTATUS & TWI_RIF_bm));
    return TWI1.MDATA;
}

// I2C 정지 조건 전송
void i2c_twi1_stop(void) {
    TWI1.MCTRLB = TWI_MCMD_STOP_gc;
}

// EEPROM에 1바이트 쓰기 (ACK 폴링 포함)
uint8_t eeprom_write_byte(uint16_t mem_addr, uint8_t data) {
    uint8_t status;
    status = i2c_twi1_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return status;
    status = i2c_twi1_write(mem_addr >> 8); // 상위 주소
    if (status) return status;
    status = i2c_twi1_write(mem_addr & 0xFF); // 하위 주소
    if (status) return status;
    status = i2c_twi1_write(data); // 데이터 쓰기
    if (status) return status;
    i2c_twi1_stop();
    
    // ACK 폴링으로 쓰기 완료 확인
    do {
        status = i2c_twi1_start(EEPROM_ADDR);
        if (status == 2) _delay_ms(1); // NACK 시 1ms 대기
    } while (status == 2); // ACK 수신까지 반복
    i2c_twi1_stop();
    return status;
}

// EEPROM에서 1바이트 읽기
uint8_t eeprom_read_byte(uint16_t mem_addr) {
    uint8_t status;
    status = i2c_twi1_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return 0xFF; // 에러 시 0xFF 반환
    status = i2c_twi1_write(mem_addr >> 8); // 상위 주소
    if (status) return 0xFF;
    status = i2c_twi1_write(mem_addr & 0xFF); // 하위 주소
    if (status) return 0xFF;
    status = i2c_twi1_start(EEPROM_ADDR | 1); // 읽기 모드 시작
    if (status) return 0xFF;
    uint8_t data = i2c_twi1_read_ack();
    i2c_twi1_stop();
    return data;
}

// GPIO 초기화: PB3(LED0) 출력 설정
void gpio_init(void) {
    PORTB.DIRSET = PIN3_bm; // PB3 출력 설정
    PORTB.OUTSET = PIN3_bm; // LED 초기값 High (켜짐)
}

int main(void) {
    set_system_clock(); // 시스템 클럭 설정
    i2c_twi1_init(); // TWI1 초기화
    uart_init(); // UART0 초기화
    gpio_init(); // GPIO 초기화

    while (1) {
        // EEPROM 0x00 주소에 0x55 쓰기
        if (eeprom_write_byte(0x00, 0x55) == 0) { // 성공 시
            _delay_ms(100);
            uint8_t data = eeprom_read_byte(0x00); // 데이터 읽기
            if (data == 0x55) {
                PORTB.OUTTGL = PIN3_bm; // LED 토글
                uart_transmit(data); // 읽은 데이터 UART로 전송
            } else {
                uart_transmit(0xFF); // 에러 시 0xFF 전송
            }
        } else {
            uart_transmit(0xFE); // 쓰기 실패 시 0xFE 전송
        }
        _delay_ms(500); // 점멸 및 전송 주기
    }

    return 0; // 도달하지 않음
}

설명:

  • 기능: TWI1을 통해 24LC256 EEPROM에 데이터를 쓰고 PB3 LED 점멸
  • 설정: TWI1 마스터 모드, 100kHz, PF2(SDA)/PF3(SCL) 풀업 저항
  • 출력: 데이터 쓰기 성공 시 LED 점멸

5.3 예제 3: TWI0 인터럽트 기반 EEPROM 데이터 읽기

// File: i2c_twi0_interrupt.c
// Description: AVR128DB48 TWI0 인터럽트로 24LC256 EEPROM 데이터 읽기 + UART 전송
// Compiler: AVR-GCC
// Target: AVR128DB48

#ifndef F_CPU
#define F_CPU 24000000UL  // 시스템 클록 주파수 (24 MHz)
#endif

#include <avr/io.h>        // AVR 입출력 관련 헤더 파일
#include <avr/interrupt.h> // 인터럽트 처리 관련 헤더 파일
#include <util/delay.h>    // 지연 함수 관련 헤더 파일

#define I2C_BAUD(F_SCL) (((F_CPU / (2UL * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소
#define BAUD_RATE 9600    // UART 보드레이트 9600bps
#define BAUD_VALUE (((F_CPU * 64UL) / (16UL * BAUD_RATE)) - 1) // UART BAUD 값 (9999)

volatile uint8_t i2c_data = 0; // 읽은 데이터를 저장하는 전역 변수
volatile uint8_t i2c_state = 0; // I2C 상태를 추적하는 전역 변수 (인터럽트 상태 관리)

// 시스템 클럭 설정: 24MHz OSCHF, XOSC32K 참조
void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    uint32_t timeout = 24000000UL;
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm) && timeout--); // 안정화 대기
    if (timeout == 0) return; // 타임아웃 시 기본 클록 유지
    
    // 내부 OSCHF를 24MHz로 설정, XOSC32K 오토튜닝
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF 선택
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성화
}

// TWI0 초기화: PC2(SDA), PC3(SCL) 사용
void i2c_twi0_init(void) {
    PORTMUX.TWIROUTEA = PORTMUX_TWI0_ALT2_gc; // PC2/SDA, PC3/SCL 선택
    PORTC.DIRSET = PIN2_bm | PIN3_bm; // PC2, PC3 출력 설정
    PORTC.PIN2CTRL |= PORT_PULLUPEN_bm; // SDA 풀업 활성화
    PORTC.PIN3CTRL |= PORT_PULLUPEN_bm; // SCL 풀업 활성화
    
    TWI0.MCTRLA = TWI_ENABLE_bm | TWI_RIEN_bm | TWI_WIEN_bm; // TWI0 활성화, 읽기/쓰기 인터럽트 활성화
    TWI0.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; // I2C 버스 상태를 Idle로 초기화
}

// UART0 초기화: PA0(TXD0), PA1(RXD0)
void uart_init(void) {
    PORTA.DIRSET = PIN0_bm; // PA0(TXD0) 출력
    PORTA.DIRCLR = PIN1_bm; // PA1(RXD0) 입력
    PORTA.OUTCLR = PIN0_bm; // TXD0 초기 Low
    
    USART0.BAUD = BAUD_VALUE; // 보드레이트 설정
    USART0.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | // 비동기 모드
                   USART_PMODE_DISABLED_gc |     // 패리티 없음
                   USART_SBMODE_1BIT_gc |        // 1 스톱 비트
                   USART_CHSIZE_8BIT_gc;         // 8비트 데이터
    USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // 송신기 및 수신기 활성화
}

// UART 데이터 전송
void uart_transmit(uint8_t data) {
    while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터 비어질 때까지 대기
    USART0.TXDATAL = data; // 데이터 전송
}

// TWI0 마스터 인터럽트 서비스 루틴
ISR(TWI0_TWIM_vect) {
    if (TWI0.MSTATUS & TWI_WIF_bm) { // 쓰기 인터럽트 플래그 확인
        if (i2c_state == 1) { // 상태 1: 슬레이브 주소 전송 후
            TWI0.MDATA = 0x00; // 메모리 주소 상위 바이트(0x00) 전송
            i2c_state = 2; // 다음 상태로 전환
        } else if (i2c_state == 2) { // 상태 2: 상위 주소 전송 후
            TWI0.MDATA = 0x00; // 메모리 주소 하위 바이트(0x00) 전송
            i2c_state = 3; // 다음 상태로 전환
        } else if (i2c_state == 3) { // 상태 3: 하위 주소 전송 후
            TWI0.MADDR = (EEPROM_ADDR << 1) | 1; // 읽기 모드로 주소 재전송 (주소 + 읽기 비트)
            i2c_state = 4; // 다음 상태로 전환
        }
    } else if (TWI0.MSTATUS & TWI_RIF_bm) { // 읽기 인터럽트 플래그 확인
        if (i2c_state == 4) { // 상태 4: 데이터 읽기
            i2c_data = TWI0.MDATA; // 읽은 데이터를 전역 변수에 저장
            TWI0.MCTRLB = TWI_MCMD_STOP_gc; // Stop 조건 전송
            i2c_state = 0; // 초기 상태로 리셋
        }
    }
}

void eeprom_read_byte_async(uint16_t mem_addr) {
    // 비동기 EEPROM 데이터 읽기 시작
    i2c_state = 1; // 상태를 1로 설정 (주소 전송 시작)
    TWI0.MADDR = (EEPROM_ADDR << 1); // 쓰기 모드로 슬레이브 주소 전송
}

// GPIO 초기화: PB3(LED0) 출력 설정
void gpio_init(void) {
    PORTB.DIRSET = PIN3_bm; // PB3 출력 설정
    PORTB.OUTSET = PIN3_bm; // LED 초기값 High (켜짐)
}

int main(void) {
    set_system_clock(); // 시스템 클럭 설정
    i2c_twi0_init(); // TWI0 초기화
    uart_init(); // UART0 초기화
    gpio_init(); // GPIO 초기화
    sei(); // 글로벌 인터럽트 활성화

    while (1) {
        // EEPROM 0x00 주소에서 데이터 비동기 읽기
        eeprom_read_byte_async(0x00); // 비동기 읽기 시작
        while (i2c_state != 0); // 읽기 완료 대기 (i2c_state가 0이 될 때까지)
        if (i2c_data == 0x55) {
            PORTB.OUTTGL = PIN3_bm; // LED 토글
            uart_transmit(i2c_data); // 읽은 데이터 UART로 전송
        } else {
            uart_transmit(0xFF); // 에러 시 0xFF 전송
        }
        _delay_ms(500); // 점멸 및 전송 주기
    }

    return 0; // 도달하지 않음
}

설명:

  • 기능: TWI0 인터럽트를 사용하여 24LC256 EEPROM에서 데이터를 비동기적으로 읽고 PB3 LED를 토글
  • 설정: TWI0 마스터 모드, 100kHz, PC2(SDA)/PC3(SCL) 풀업 저항, 읽기/쓰기 인터럽트 활성화
  • 출력: EEPROM 데이터가 0x55일 때 LED 점멸

5.4 예제 4: TWI1 다중 바이트 EEPROM 쓰기/읽기

// File: i2c_twi1_multi_byte.c
// Description: AVR128DB48 TWI1으로 24LC256 EEPROM에 다중 바이트 쓰기/읽기 + UART 전송
// Compiler: AVR-GCC
// Target: AVR128DB48

#ifndef F_CPU
#define F_CPU 24000000UL  // 시스템 클록 주파수 (24 MHz)
#endif

#include <avr/io.h>        // AVR 입출력 관련 헤더 파일
#include <util/delay.h>    // 지연 함수 관련 헤더 파일

#define I2C_BAUD(F_SCL) (((F_CPU / (2UL * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소
#define BAUD_RATE 9600    // UART 보드레이트 9600bps
#define BAUD_VALUE (((F_CPU * 64UL) / (16UL * BAUD_RATE)) - 1) // UART BAUD 값 (9999)

// 시스템 클럭 설정: 24MHz OSCHF, XOSC32K 참조
void set_system_clock(void) {
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    uint32_t timeout = 24000000UL;
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm) && timeout--); // 안정화 대기
    if (timeout == 0) return; // 타임아웃 시 기본 클록 유지
    
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF 선택
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 프리스케일러 비활성화
}

// TWI1 초기화: PF2(SDA), PF3(SCL) 사용
void i2c_twi1_init(void) {
    PORTMUX.TWIROUTEA = PORTMUX_TWI1_ALT2_gc; // PF2/SDA, PF3/SCL 선택
    PORTF.DIRSET = PIN2_bm | PIN3_bm; // PF2, PF3 출력 설정
    PORTF.PIN2CTRL |= PORT_PULLUPEN_bm; // SDA 풀업 활성화
    PORTF.PIN3CTRL |= PORT_PULLUPEN_bm; // SCL 풀업 활성화
    
    TWI1.MCTRLA = TWI_ENABLE_bm; // TWI1 모듈 활성화
    TWI1.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI1.MSTATUS = TWI_BUSSTATE_IDLE_gc; // I2C 버스 상태를 Idle로 초기화
}

// UART0 초기화: PA0(TXD0), PA1(RXD0)
void uart_init(void) {
    PORTA.DIRSET = PIN0_bm; // PA0(TXD0) 출력
    PORTA.DIRCLR = PIN1_bm; // PA1(RXD0) 입력
    PORTA.OUTCLR = PIN0_bm; // TXD0 초기 Low
    
    USART0.BAUD = BAUD_VALUE; // 보드레이트 설정
    USART0.CTRLC = USART_CMODE_ASYNCHRONOUS_gc | // 비동기 모드
                   USART_PMODE_DISABLED_gc |     // 패리티 없음
                   USART_SBMODE_1BIT_gc |        // 1 스톱 비트
                   USART_CHSIZE_8BIT_gc;         // 8비트 데이터
    USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // 송신기 및 수신기 활성화
}

// UART 데이터 전송
void uart_transmit(uint8_t data) {
    while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터 비어질 때까지 대기
    USART0.TXDATAL = data; // 데이터 전송
}

// I2C 시작 조건 전송, 에러 핸들링 포함
uint8_t i2c_twi1_start(uint8_t addr) {
    TWI1.MADDR = (addr << 1); // 7비트 주소 + 쓰기(0)/읽기(1) 비트
    while (!(TWI1.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))) {
        if (TWI1.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI1.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm; // 에러 플래그 클리어
            return 1; // 버스 에러 또는 중재 손실
        }
    }
    if (TWI1.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK 수신
    }
    return 0; // 성공
}

// 데이터 쓰기
uint8_t i2c_twi1_write(uint8_t data) {
    TWI1.MDATA = data;
    while (!(TWI1.MSTATUS & TWI_WIF_bm)) {
        if (TWI1.MSTATUS & (TWI_BUSERR_bm | TWI_ARBLOST_bm)) {
            TWI1.MSTATUS |= TWI_BUSERR_bm | TWI_ARBLOST_bm;
            return 1; // 에러
        }
    }
    if (TWI1.MSTATUS & TWI_RXACK_bm) {
        return 2; // NACK
    }
    return 0; // 성공
}

// 데이터 읽기 (ACK 전송)
uint8_t i2c_twi1_read_ack(void) {
    TWI1.MCTRLB &= ~TWI_ACKACT_bm; // ACK 전송 설정
    while (!(TWI1.MSTATUS & TWI_RIF_bm));
    return TWI1.MDATA;
}

// 데이터 읽기 (NACK 전송, 마지막 바이트용)
uint8_t i2c_twi1_read_nack(void) {
    TWI1.MCTRLB |= TWI_ACKACT_bm; // NACK 전송 설정
    while (!(TWI1.MSTATUS & TWI_RIF_bm));
    return TWI1.MDATA;
}

// I2C 정지 조건 전송
void i2c_twi1_stop(void) {
    TWI1.MCTRLB = TWI_MCMD_STOP_gc;
}

// EEPROM에 다중 바이트 쓰기 (ACK 폴링 포함)
uint8_t eeprom_write_bytes(uint16_t mem_addr, uint8_t *data, uint8_t len) {
    uint8_t status;
    status = i2c_twi1_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return status;
    status = i2c_twi1_write(mem_addr >> 8); // 상위 주소
    if (status) return status;
    status = i2c_twi1_write(mem_addr & 0xFF); // 하위 주소
    if (status) return status;
    for (uint8_t i = 0; i < len; i++) {
        status = i2c_twi1_write(data[i]); // 데이터 쓰기
        if (status) return status;
    }
    i2c_twi1_stop();
    
    // ACK 폴링으로 쓰기 완료 확인
    do {
        status = i2c_twi1_start(EEPROM_ADDR);
        if (status == 2) _delay_ms(1); // NACK 시 1ms 대기
    } while (status == 2); // ACK 수신까지 반복
    i2c_twi1_stop();
    return status;
}

// EEPROM에서 다중 바이트 읽기
uint8_t eeprom_read_bytes(uint16_t mem_addr, uint8_t *data, uint8_t len) {
    uint8_t status;
    status = i2c_twi1_start(EEPROM_ADDR); // 쓰기 모드 시작
    if (status) return status;
    status = i2c_twi1_write(mem_addr >> 8); // 상위 주소
    if (status) return status;
    status = i2c_twi1_write(mem_addr & 0xFF); // 하위 주소
    if (status) return status;
    status = i2c_twi1_start(EEPROM_ADDR | 1); // 읽기 모드 시작
    if (status) return status;
    for (uint8_t i = 0; i < len - 1; i++) {
        data[i] = i2c_twi1_read_ack(); // 데이터 읽기 (ACK)
    }
    data[len - 1] = i2c_twi1_read_nack(); // 마지막 데이터 읽기 (NACK)
    i2c_twi1_stop();
    return 0; // 성공
}

// GPIO 초기화: PB3(LED0) 출력 설정
void gpio_init(void) {
    PORTB.DIRSET = PIN3_bm; // PB3 출력 설정
    PORTB.OUTSET = PIN3_bm; // LED 초기값 High (켜짐)
}

int main(void) {
    set_system_clock(); // 시스템 클럭 설정
    i2c_twi1_init(); // TWI1 초기화
    uart_init(); // UART0 초기화
    gpio_init(); // GPIO 초기화

    uint8_t write_data[3] = {0xAA, 0xBB, 0xCC}; // 쓰기용 데이터 배열
    uint8_t read_data[3] = {0}; // 읽기용 데이터 배열

    while (1) {
        // EEPROM 0x00 주소에 3바이트 쓰기
        uint8_t write_status = eeprom_write_bytes(0x00, write_data, 3); // [0xAA, 0xBB, 0xCC] 쓰기
        if (write_status == 0) { // 쓰기 성공
            _delay_ms(100);
            // EEPROM 0x00 주소에서 3바이트 읽기
            uint8_t read_status = eeprom_read_bytes(0x00, read_data, 3);
            if (read_status == 0 && read_data[0] == 0xAA && read_data[1] == 0xBB && read_data[2] == 0xCC) {
                PORTB.OUTTGL = PIN3_bm; // LED 토글
                for (uint8_t i = 0; i < 3; i++) {
                    uart_transmit(read_data[i]); // 읽은 데이터 UART로 전송
                }
            } else {
                uart_transmit(0xFF); // 읽기 실패 시 0xFF 전송
            }
        } else {
            uart_transmit(0xFE); // 쓰기 실패 시 0xFE 전송
        }
        _delay_ms(500); // 점멸 및 전송 주기
    }

    return 0; // 도달하지 않음
}

설명:

  • 기능: TWI1을 통해 24LC256 EEPROM에 다중 바이트(3바이트) 쓰기 및 읽기 후 PB3 LED 토글
  • 설정: TWI1 마스터 모드, 100kHz, PF2(SDA)/PF3(SCL) 풀업 저항
  • 출력: 읽은 데이터가 [0xAA, 0xBB, 0xCC]일 때 LED 점멸

6. TWI0와 TWI1 선택 기준

  • TWI0: 기본 I2C 채널로, Curiosity Nano와 같은 평가 보드에서 표준 핀(PC2, PC3)으로 사용. 단일 I2C 통신에 적합.
  • TWI1: 추가 I2C 장치 연결이 필요하거나, TWI0 핀이 다른 기능(UART, SPI 등)으로 사용 중일 때 활용. PF2, PF3 핀 사용.
  • 멀티플렉싱 주의: 핀 충돌 방지를 위해 데이터시트의 핀 기능 확인.

7. 사용 방법

7.1 환경 설정

  • AVR-GCC 설치: Atmel Studio 또는 MPLAB X IDE에 AVR-GCC 툴체인 설치
  • 헤더 파일: <avr/io.h>, <util/delay.h>, <avr/interrupt.h> 포함
  • 프로젝트 설정: AVR128DB48 타겟으로 프로젝트 생성
  • 클럭 정의: #define F_CPU 24000000UL

7.2 코드 실행

  • 각 예제를 별도의 .c 파일로 저장하거나, main.c에 복사
  • Atmel Studio/MPLAB X IDE에서 빌드 및 플래싱

7.3 하드웨어 준비

  • TWI0 연결: PC2(SDA), PC3(SCL)에 4.7kΩ 풀업 저항 및 24LC256 EEPROM 연결
  • TWI1 연결: PF2(SDA), PF3(SCL)에 4.7kΩ 풀업 저항 및 24LC256 EEPROM 연결
  • LED 출력: PB3에 LED 및 330Ω 저항 연결
  • 전원: 1.8V~5.5V 전원 공급

7.4 디버깅

  • Atmel Studio/MPLAB X IDE 디버거 사용
  • TWIx.MSTATUS, TWIx.MDATA 레지스터 확인
  • 오실로스코프로 SDA/SCL 신호 점검

8. 추가 팁

  • 클럭 설정: set_system_clock()로 XOSC32K 안정화 확인
  • 노이즈 감소: 외부 풀업 저항 및 PCB 배선 최적화
  • 인터럽트 사용: 예제 3처럼 인터럽트를 활용하여 CPU 부하 감소
  • 다중 바이트: 예제 4처럼 다중 바이트 전송으로 효율성 향상
  • Microchip 리소스: AVR128DB48 데이터시트, I2C Application Notes
  • 문제 해결:
    •    통신 실패: 풀업 저항 및 TWIx.MSTATUS.BUSERR 확인
    •    데이터 오류: TWIx.MSTATUS.RIF/WIF 플래그 점검
    •    느린 응답: 보율 설정 및 타임아웃 조정
  • 커뮤니티: Microchip Community, AVR Freaks 포럼 참고

9. 결론

이 문서는 AVR128DB48의 TWI0 및 TWI1 I2C 모듈 설정 방법과 Bitfield 구조를 활용한 예제 코드를 제공하여 I2C 기반 애플리케이션 구현을 지원합니다. TWI0는 기본 채널로 단일 장치 통신에 적합하며, TWI1은 추가 장치 연결이나 핀 충돌 회피 시 유용합니다. 인터럽트 및 다중 바이트 전송 예제를 통해 효율적인 I2C 통신을 구현할 수 있습니다.

키워드: AVR128DB48, I2C, TWI0, TWI1, 마이크로컨트롤러, Atmel Studio, MPLAB X IDE, EEPROM, 풀업 저항, 마스터 모드, 인터럽트, 다중 바이트, 24LC256