1. AVR128DB48 I2C 모듈 개요
Microchip의 AVR128DB48 마이크로컨트롤러는 두 개의 I2C(TWI: Two-Wire Interface) 모듈(TWI0, TWI1)을 제공하여 센서, 디스플레이, 메모리 장치 등과의 통신에 적합합니다. I2C 모듈은 마스터 및 슬레이브 모드를 지원하며, 저전력 애플리케이션과 높은 호환성을 제공합니다. 이 문서에서는 AVR128DB48의 TWI0 및 TWI1 설정 방법, Bitfield 구조를 활용한 레지스터 설정, 그리고 실용적인 예제 코드를 제공하여 초보자와 숙련된 개발자 모두 쉽게 활용할 수 있도록 돕습니다.
주요 사양
- 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 설정 절차
- 시스템 초기화:
- 시스템 클럭 설정 (24MHz, set_system_clock())
- 인터럽트 비활성화 (cli())
- 포트 설정:
- 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으로 출력 설정
- I2C 모듈 활성화:
- TWIx.MCTRLA로 마스터 모드 활성화
- TWIx.MBAUD로 보율 설정
- 마스터 동작:
- TWIx.MADDR로 슬레이브 주소 전송
- TWIx.MDATA로 데이터 송수신
- 인터럽트 설정 (선택):
- TWIx.MCTRLB로 인터럽트 조건 설정
- 상태 확인:
- 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 쓰기/읽기 + 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
'MCU > AVR' 카테고리의 다른 글
AVR128DB48 LIN, IrDA, RS485, MPCM, 스타트 프레임 감지, 동기 모드 사용 방법 및 예제 코드 (1) | 2025.08.20 |
---|---|
AVR128DB48 Event System 사용 방법 및 예제 코드(수정) (0) | 2025.08.20 |
AVR128DB48 Watchdog 사용 방법 및 예제 코드 (0) | 2025.08.19 |
AVR128DB48 SPI 사용 방법 및 예제 코드 (0) | 2025.08.19 |
AVR128DB48 UART 사용 방법 및 예제 코드(수정) (1) | 2025.08.19 |
AVR128DB48 GPIO 사용 방법 및 예제 코드 (0) | 2025.08.18 |
AVR128DB48 Microchip Studio 프로젝트 생성 절차 및 기본 프로그램 작성(수정) (1) | 2025.08.18 |
AVR128DA48 Modbus ASCII 슬레이브 구현 (1) | 2025.08.16 |