본문 바로가기
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 구조를 활용한 레지스터 설정, 그리고 실용적인 예제 코드를 제공하여 초보자와 숙련된 개발자 모두 쉽게 활용할 수 있도록 돕습니다.

주요 사양

  • I2C 채널:
    • TWI0: SDA (PC2), SCL (PC3)
    • TWI1: SDA (PF2), SCL (PF3)
  • 지원 모드: 마스터, 슬레이브
  • 클럭 주파수: 최대 400kHz (Fast-mode), 100kHz (Standard-mode)
  • 주요 레지스터 (TWI0 및 TWI1 공통):
    • TWIx.MCTRLA: 마스터 제어 레지스터
    • TWIx.MBAUD: 마스터 보율 설정
    • TWIx.MSTATUS: 마스터 상태
    • TWIx.MDATA: 마스터 데이터
    • TWIx.SCTRLA: 슬레이브 제어 레지스터
    • TWIx.SADDR: 슬레이브 주소
  • 인터럽트: 데이터 전송/수신, 에러, 충돌 감지
  • 전력 관리: 저전력 모드에서 동작 지원
  • 전압 레벨: 1.8V~5.5V 지원
  • 기타: 외부 풀업 저항 (4.7kΩ 권장) 필수

2. I2C Bitfield 설정 상세

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

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

  • bit.ENABLE: TWI 모듈 활성화 (1: 활성화)
  • bit.SMEN: 스마트 모드 활성화
  • bit.TIMEOUT: 타임아웃 설정

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: PC2(SDA), PC3(SCL)에 외부 풀업 저항 연결
    •   TWI1: PF2(SDA), PF3(SCL)에 외부 풀업 저항 연결
    •   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을 대상으로 작성되었습니다.

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

// File: i2c_twi0_eeprom.c
// Description: AVR128DB48 TWI0 마스터로 24LC256 EEPROM에 데이터 쓰기/읽기
// Compiler: AVR-GCC
// Target: AVR128DB48

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

#define F_CPU 24000000UL  // 시스템 클록 주파수를 24MHz로 정의 (지연 함수 정확도)
#define I2C_BAUD(F_SCL) (((F_CPU / (2 * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소

void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    // CLKCTRL_ENABLE_bm: XOSC32K 활성화
    // CLKCTRL_RUNSTDBY_bm: 스탠바이 모드에서 XOSC32K 유지
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
    
    // 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    // CLKCTRL_FRQSEL_24M_gc: 24MHz 클럭 선택
    // CLKCTRL_AUTOTUNE_bm: 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화 (24MHz 그대로 사용)
}

void i2c_twi0_init(void) {
    // PC2(SDA), PC3(SCL)을 출력으로 설정
    PORTC.DIRSET = PIN2_bm | PIN3_bm; // PC2, PC3 핀을 출력으로 설정
    PORTC.PIN2CTRL |= PORT_PULLUPEN_bm; // PC2(SDA)에 내부 풀업 저항 활성화
    PORTC.PIN3CTRL |= PORT_PULLUPEN_bm; // PC3(SCL)에 내부 풀업 저항 활성화
    
    // TWI0 마스터 모드 활성화
    TWI0.MCTRLA = TWI_ENABLE_bm; // TWI0 모듈 활성화
    TWI0.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc; // I2C 버스 상태를 Idle로 초기화
}

void i2c_twi0_start(uint8_t addr) {
    // I2C 통신 시작: 슬레이브 주소 전송
    TWI0.MADDR = (addr << 1); // 7비트 주소를 왼쪽으로 시프트하고 쓰기 비트(0) 추가
    while (!(TWI0.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))); // 쓰기 또는 읽기 플래그 대기
}

void i2c_twi0_write(uint8_t data) {
    // 데이터 전송
    TWI0.MDATA = data; // 데이터 레지스터에 데이터 쓰기
    while (!(TWI0.MSTATUS & TWI_WIF_bm)); // 쓰기 완료 대기
}

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

void i2c_twi0_stop(void) {
    // I2C 통신 종료: Stop 조건 전송
    TWI0.MCTRLB = TWI_MCMD_STOP_gc; // Stop 명령 설정
}

void eeprom_write_byte(uint16_t mem_addr, uint8_t data) {
    // EEPROM에 1바이트 쓰기
    i2c_twi0_start(EEPROM_ADDR); // EEPROM 주소로 통신 시작
    i2c_twi0_write(mem_addr >> 8); // 메모리 주소 상위 바이트 전송
    i2c_twi0_write(mem_addr & 0xFF); // 메모리 주소 하위 바이트 전송
    i2c_twi0_write(data); // 데이터 전송
    i2c_twi0_stop(); // 통신 종료
    _delay_ms(5); // EEPROM 쓰기 시간 대기 (5ms)
}

uint8_t eeprom_read_byte(uint16_t mem_addr) {
    // EEPROM에서 1바이트 읽기
    i2c_twi0_start(EEPROM_ADDR); // EEPROM 주소로 쓰기 모드 시작
    i2c_twi0_write(mem_addr >> 8); // 메모리 주소 상위 바이트 전송
    i2c_twi0_write(mem_addr & 0xFF); // 메모리 주소 하위 바이트 전송
    i2c_twi0_start(EEPROM_ADDR | 1); // 읽기 모드로 재시작 (주소 + 읽기 비트)
    uint8_t data = i2c_twi0_read_ack(); // 데이터 읽기 (ACK 전송)
    i2c_twi0_stop(); // 통신 종료
    return data; // 읽은 데이터 반환
}

void gpio_init(void) {
    // PB3(LED0)를 출력으로 설정
    PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
    PORTB.OUTSET = PIN3_bm; // PB3 초기값 High (LED 켬)
}

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

    while (1) {
        // EEPROM 0x00 주소에 0x55 쓰기
        eeprom_write_byte(0x00, 0x55); // 0x00 주소에 데이터 0x55 쓰기
        _delay_ms(100); // 100ms 대기
        
        // EEPROM 0x00 주소에서 데이터 읽기
        uint8_t data = eeprom_read_byte(0x00); // 0x00 주소에서 데이터 읽기
        
        // 읽은 데이터가 0x55면 LED 토글
        if (data == 0x55) {
            PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
        }
        _delay_ms(500); // 500ms 대기 (LED 점멸 주기)
    }

    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에 데이터 쓰기
// Compiler: AVR-GCC
// Target: AVR128DB48

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

#define F_CPU 24000000UL  // 시스템 클록 주파수를 24MHz로 정의
#define I2C_BAUD(F_SCL) (((F_CPU / (2 * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소

void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    // CLKCTRL_ENABLE_bm: XOSC32K 활성화
    // CLKCTRL_RUNSTDBY_bm: 스탠바이 모드에서 XOSC32K 유지
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
    
    // 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    // CLKCTRL_FRQSEL_24M_gc: 24MHz 클럭 선택
    // CLKCTRL_AUTOTUNE_bm: 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
}

void i2c_twi1_init(void) {
    // PF2(SDA), PF3(SCL)을 출력으로 설정
    PORTF.DIRSET = PIN2_bm | PIN3_bm; // PF2, PF3 핀을 출력으로 설정
    PORTF.PIN2CTRL |= PORT_PULLUPEN_bm; // PF2(SDA)에 내부 풀업 저항 활성화
    PORTF.PIN3CTRL |= PORT_PULLUPEN_bm; // PF3(SCL)에 내부 풀업 저항 활성화
    
    // TWI1 마스터 모드 활성화
    TWI1.MCTRLA = TWI_ENABLE_bm; // TWI1 모듈 활성화
    TWI1.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI1.MSTATUS = TWI_BUSSTATE_IDLE_gc; // I2C 버스 상태를 Idle로 초기화
}

void i2c_twi1_start(uint8_t addr) {
    // I2C 통신 시작: 슬레이브 주소 전송
    TWI1.MADDR = (addr << 1); // 7비트 주소를 왼쪽으로 시프트하고 쓰기 비트(0) 추가
    while (!(TWI1.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))); // 쓰기 또는 읽기 플래그 대기
}

void i2c_twi1_write(uint8_t data) {
    // 데이터 전송
    TWI1.MDATA = data; // 데이터 레지스터에 데이터 쓰기
    while (!(TWI1.MSTATUS & TWI_WIF_bm)); // 쓰기 완료 대기
}

void i2c_twi1_stop(void) {
    // I2C 통신 종료: Stop 조건 전송
    TWI1.MCTRLB = TWI_MCMD_STOP_gc; // Stop 명령 설정
}

void eeprom_write_byte(uint16_t mem_addr, uint8_t data) {
    // EEPROM에 1바이트 쓰기
    i2c_twi1_start(EEPROM_ADDR); // EEPROM 주소로 통신 시작
    i2c_twi1_write(mem_addr >> 8); // 메모리 주소 상위 바이트 전송
    i2c_twi1_write(mem_addr & 0xFF); // 메모리 주소 하위 바이트 전송
    i2c_twi1_write(data); // 데이터 전송
    i2c_twi1_stop(); // 통신 종료
    _delay_ms(5); // EEPROM 쓰기 시간 대기 (5ms)
}

void gpio_init(void) {
    // PB3(LED0)를 출력으로 설정
    PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
    PORTB.OUTSET = PIN3_bm; // PB3 초기값 High (LED 켬)
}

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

    while (1) {
        // EEPROM 0x00 주소에 0xAA 쓰기
        eeprom_write_byte(0x00, 0xAA); // 0x00 주소에 데이터 0xAA 쓰기
        PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
        _delay_ms(500); // 500ms 대기 (LED 점멸 주기)
    }

    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 데이터 읽기
// Compiler: AVR-GCC
// Target: AVR128DB48

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

#define F_CPU 24000000UL  // 시스템 클록 주파수를 24MHz로 정의
#define I2C_BAUD(F_SCL) (((F_CPU / (2 * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소

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

void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    // CLKCTRL_ENABLE_bm: XOSC32K 활성화
    // CLKCTRL_RUNSTDBY_bm: 스탠바이 모드에서 XOSC32K 유지
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
    
    // 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    // CLKCTRL_FRQSEL_24M_gc: 24MHz 클럭 선택
    // CLKCTRL_AUTOTUNE_bm: 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
}

void i2c_twi0_init(void) {
    // PC2(SDA), PC3(SCL)을 출력으로 설정
    PORTC.DIRSET = PIN2_bm | PIN3_bm; // PC2, PC3 핀을 출력으로 설정
    PORTC.PIN2CTRL |= PORT_PULLUPEN_bm; // PC2(SDA)에 내부 풀업 저항 활성화
    PORTC.PIN3CTRL |= PORT_PULLUPEN_bm; // PC3(SCL)에 내부 풀업 저항 활성화
    
    // TWI0 마스터 모드 및 인터럽트 활성화
    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로 초기화
}

ISR(TWI0_TWIM_vect) {
    // TWI0 마스터 인터럽트 서비스 루틴
    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); // 쓰기 모드로 슬레이브 주소 전송
}

void gpio_init(void) {
    // PB3(LED0)를 출력으로 설정
    PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
    PORTB.OUTSET = PIN3_bm; // PB3 초기값 High (LED 켬)
}

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

    while (1) {
        // EEPROM 0x00 주소에서 데이터 비동기 읽기
        eeprom_read_byte_async(0x00); // 비동기 읽기 시작
        while (i2c_state != 0); // 읽기 완료 대기 (i2c_state가 0이 될 때까지)
        if (i2c_data == 0x55) { // 읽은 데이터가 0x55인지 확인
            PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
        }
        _delay_ms(500); // 500ms 대기 (LED 점멸 주기)
    }

    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에 다중 바이트 쓰기/읽기
// Compiler: AVR-GCC
// Target: AVR128DB48

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

#define F_CPU 24000000UL  // 시스템 클록 주파수를 24MHz로 정의
#define I2C_BAUD(F_SCL) (((F_CPU / (2 * F_SCL)) - 5)) // I2C 보율 계산 (100kHz)
#define EEPROM_ADDR 0x50  // 24LC256 EEPROM의 7비트 I2C 주소

void set_system_clock(void) {
    // 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
    _PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
    // CLKCTRL_ENABLE_bm: XOSC32K 활성화
    // CLKCTRL_RUNSTDBY_bm: 스탠바이 모드에서 XOSC32K 유지
    while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
    
    // 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
    // CLKCTRL_FRQSEL_24M_gc: 24MHz 클럭 선택
    // CLKCTRL_AUTOTUNE_bm: 오토튜닝 활성화
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
    _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
}

void i2c_twi1_init(void) {
    // PF2(SDA), PF3(SCL)을 출력으로 설정
    PORTF.DIRSET = PIN2_bm | PIN3_bm; // PF2, PF3 핀을 출력으로 설정
    PORTF.PIN2CTRL |= PORT_PULLUPEN_bm; // PF2(SDA)에 내부 풀업 저항 활성화
    PORTF.PIN3CTRL |= PORT_PULLUPEN_bm; // PF3(SCL)에 내부 풀업 저항 활성화
    
    // TWI1 마스터 모드 활성화
    TWI1.MCTRLA = TWI_ENABLE_bm; // TWI1 모듈 활성화
    TWI1.MBAUD = I2C_BAUD(100000); // 100kHz 보율 설정
    TWI1.MSTATUS = TWI_BUSSTATE_IDLE_gc; // I2C 버스 상태를 Idle로 초기화
}

void i2c_twi1_start(uint8_t addr) {
    // I2C 통신 시작: 슬레이브 주소 전송
    TWI1.MADDR = (addr << 1); // 7비트 주소를 왼쪽으로 시프트하고 쓰기 비트(0) 추가
    while (!(TWI1.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))); // 쓰기 또는 읽기 플래그 대기
}

void i2c_twi1_write(uint8_t data) {
    // 데이터 전송
    TWI1.MDATA = data; // 데이터 레지스터에 데이터 쓰기
    while (!(TWI1.MSTATUS & TWI_WIF_bm)); // 쓰기 완료 대기
}

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

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

void i2c_twi1_stop(void) {
    // I2C 통신 종료: Stop 조건 전송
    TWI1.MCTRLB = TWI_MCMD_STOP_gc; // Stop 명령 설정
}

void eeprom_write_bytes(uint16_t mem_addr, uint8_t *data, uint8_t len) {
    // EEPROM에 다중 바이트 쓰기
    i2c_twi1_start(EEPROM_ADDR); // EEPROM 주소로 통신 시작
    i2c_twi1_write(mem_addr >> 8); // 메모리 주소 상위 바이트 전송
    i2c_twi1_write(mem_addr & 0xFF); // 메모리 주소 하위 바이트 전송
    for (uint8_t i = 0; i < len; i++) { // 지정된 길이만큼 데이터 반복 전송
        i2c_twi1_write(data[i]); // 각 바이트 전송
    }
    i2c_twi1_stop(); // 통신 종료
    _delay_ms(5); // EEPROM 쓰기 시간 대기 (5ms)
}

void eeprom_read_bytes(uint16_t mem_addr, uint8_t *data, uint8_t len) {
    // EEPROM에서 다중 바이트 읽기
    i2c_twi1_start(EEPROM_ADDR); // EEPROM 주소로 쓰기 모드 시작
    i2c_twi1_write(mem_addr >> 8); // 메모리 주소 상위 바이트 전송
    i2c_twi1_write(mem_addr & 0xFF); // 메모리 주소 하위 바이트 전송
    i2c_twi1_start(EEPROM_ADDR | 1); // 읽기 모드로 재시작 (주소 + 읽기 비트)
    for (uint8_t i = 0; i < len - 1; i++) { // 마지막 바이트 전까지 ACK로 읽기
        data[i] = i2c_twi1_read_ack(); // 데이터 읽기 (ACK 전송)
    }
    data[len - 1] = i2c_twi1_read_nack(); // 마지막 바이트 읽기 (NACK 전송)
    i2c_twi1_stop(); // 통신 종료
}

void gpio_init(void) {
    // PB3(LED0)를 출력으로 설정
    PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
    PORTB.OUTSET = PIN3_bm; // PB3 초기값 High (LED 켬)
}

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

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

    while (1) {
        // EEPROM 0x00 주소에 3바이트 쓰기
        eeprom_write_bytes(0x00, write_data, 3); // [0xAA, 0xBB, 0xCC] 쓰기
        _delay_ms(100); // 100ms 대기
        
        // EEPROM 0x00 주소에서 3바이트 읽기
        eeprom_read_bytes(0x00, read_data, 3); // 3바이트 읽기
        
        // 읽은 데이터가 [0xAA, 0xBB, 0xCC]인지 확인
        if (read_data[0] == 0xAA && read_data[1] == 0xBB && read_data[2] == 0xCC) {
            PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
        }
        _delay_ms(500); // 500ms 대기 (LED 점멸 주기)
    }

    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

반응형