이 문서는 Microchip AVR128DA48 마이크로컨트롤러에서 STM32 HAL API 스타일로 I2C 비트뱅을 구현하는 방법을 소개합니다. 하드웨어 I2C(TWI) 모듈을 사용하지 않고, 소프트웨어로 I2C 통신을 구현하며, 마스터/슬레이브 모드, 버스 리셋, 클럭 스트레칭 기능을 지원합니다. 시스템 클럭은 24MHz로 설정하며, 최신 AVR GPIO 문법을 사용합니다.
1. 프로젝트 개요
AVR128DA48은 강력한 8비트 AVR 마이크로컨트롤러로, 다양한 임베디드 애플리케이션에 적합합니다. 이 프로젝트는 STM32 HAL API 스타일을 따라 I2C 비트뱅을 구현하여, STM32 개발자들에게 친숙한 인터페이스를 제공합니다. 주요 기능은 다음과 같습니다:
- 클럭 설정: 내부 고속 오실레이터(OSCHF)를 24MHz로 설정, 자동 튜닝 및 외부 32kHz 크리스털 지원.
- I2C 비트뱅: 마스터 모드(HAL_I2C_Master_Transmit, HAL_I2C_Master_Receive, HAL_I2C_Mem_Write, HAL_I2C_Mem_Read), 슬레이브 모드(HAL_I2C_Slave_Transmit, HAL_I2C_Slave_Receive), 디바이스 준비 확인(HAL_I2C_IsDeviceReady).
- 리셋 기능: I2C 버스 오류 복구를 위한 HAL_I2C_ResetBus.
- 클럭 스트레칭: 슬레이브 장치의 클럭 지연을 지원.
- 에러 처리: ACK 실패, 타임아웃, 버스 오류를 감지 및 처리.
2. 하드웨어 설정
필요한 구성 요소
- 마이크로컨트롤러: AVR128DA48
- I2C 핀:
- SDA: PB0
- SCL: PB1
- 풀업 저항: SDA와 SCL에 각각 4.7kΩ 저항 연결 (오픈드레인 동작)
- 슬레이브 장치: I2C 슬레이브 (예: EEPROM, 주소 0x50)
- 개발 환경: MPLAB X IDE, XC8 컴파일러
핀 연결
- PB0 (SDA)와 PB1 (SCL)을 I2C 슬레이브 장치에 연결.
- 각 핀에 4.7kΩ 풀업 저항을 5V 또는 3.3V에 연결.
- 슬레이브 장치의 전원 및 접지 연결 확인.
3. 클럭 설정
시스템 클럭은 24MHz로 설정되며, 내부 고속 오실레이터(OSCHF)를 사용합니다. 자동 튜닝을 활성화하여 클럭 안정성을 높이고, 외부 32kHz 크리스털(XOSC32K)을 활성화하여 RTC를 지원합니다. 아래는 클럭 설정 코드입니다:
void SystemClock_Config(void) {
CCP = CCP_IOREG_gc; // 보호된 레지스터 변경을 위해 CCP 설정
CLKCTRL.XOSC32KCTRLA = (1 << CLKCTRL_ENABLE_bp); // 외부 32kHz 크리스털 활성화
RTC.CLKSEL = (0x02 << RTC_CLKSEL_gp); // RTC 클럭 소스를 XOSC32K로 설정
CCP = CCP_IOREG_gc;
CLKCTRL.OSCHFCTRLA = (0x09 << CLKCTRL_FREQSEL_gp) | (0x01 << CLKCTRL_AUTOTUNE_bp); // 내부 24MHz, 자동 튜닝
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLA = (0 << CLKCTRL_CLKSEL_gp); // 메인 클럭을 OSCHF로 설정
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = (0 << CLKCTRL_PEN_bp); // 프리스케일러 비활성화, CLK_PER = 24MHz
}
4. I2C 비트뱅 구현
I2C 비트뱅은 PB0(SDA)와 PB1(SCL)을 사용하여 소프트웨어로 I2C 프로토콜을 구현합니다. 주요 기능은 다음과 같습니다:
- 속도: 약 100kHz (5µs 지연).
- 마스터 모드: 데이터 전송/수신, 메모리 읽기/쓰기.
- 슬레이브 모드: 주소 감지 및 데이터 전송/수신.
- 리셋 기능: 버스 오류 복구를 위해 9번 클럭 펄스 생성.
- 클럭 스트레칭: 슬레이브의 SCL 지연 지원.
주요 함수
- HAL_I2C_Init: I2C 초기화 및 버스 리셋.
- HAL_I2C_ResetBus: I2C 버스 리셋 (9클럭 펄스 + 정지 조건).
- HAL_I2C_Master_Transmit: 마스터 모드 데이터 전송.
- HAL_I2C_Master_Receive: 마스터 모드 데이터 수신.
- HAL_I2C_Mem_Write: 메모리 쓰기 (EEPROM 등).
- HAL_I2C_Mem_Read: 메모리 읽기.
- HAL_I2C_Slave_Transmit: 슬레이브 모드 데이터 전송.
- HAL_I2C_Slave_Receive: 슬레이브 모드 데이터 수신.
- HAL_I2C_IsDeviceReady: 슬레이브 장치 응답 확인.
- HAL_I2C_GetState, HAL_I2C_GetError: 상태 및 에러 확인.
클럭 스트레칭
슬레이브 장치가 SCL을 LOW로 유지하여 처리 속도를 늦추는 경우, 마스터는 I2C_WaitSCLHigh 함수를 통해 대기합니다:
static HAL_StatusTypeDef I2C_WaitSCLHigh(I2C_HandleTypeDef* hi2c) {
uint16_t timeout = I2C_TIMEOUT_US / I2C_DELAY_US;
I2C_SCL_PORT.DIRCLR = I2C_SCL_PIN;
while (!I2C_SCL_READ() && timeout--) {
I2C_Delay();
}
I2C_SCL_PORT.DIRSET = I2C_SCL_PIN;
if (timeout == 0) {
hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT;
return HAL_TIMEOUT;
}
return HAL_OK;
}
5. 전체 코드
아래는 전체 코드로, 상세한 주석이 포함되어 있습니다. MPLAB X IDE에서 컴파일하여 사용하세요.
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
#include <stdbool.h>
// GPIO 핀 정의 (GPIO pin definitions)
#define I2C_SDA_PORT PORTB // SDA 핀의 포트: PORTB
#define I2C_SDA_PIN PIN0_bm // SDA 핀: PB0
#define I2C_SCL_PORT PORTB // SCL 핀의 포트: PORTB
#define I2C_SCL_PIN PIN1_bm // SCL 핀: PB1
// I2C 속도 및 타임아웃 설정 (I2C speed and timeout settings)
#define I2C_DELAY_US 5 // I2C 비트 지연 시간 (µs), 약 100kHz
#define I2C_TIMEOUT_US 1000 // 클럭 스트레칭 타임아웃 (µs)
// 슬레이브 주소 정의 (Slave address definition)
#define I2C_OWN_ADDRESS 0x50 // 기본 슬레이브 주소
// HAL 상태 정의 (HAL status definitions)
typedef enum {
HAL_OK = 0x00U, // 성공
HAL_ERROR = 0x01U, // 오류
HAL_BUSY = 0x02U, // 바쁨
HAL_TIMEOUT = 0x03U // 타임아웃
} HAL_StatusTypeDef;
// I2C 상태 정의 (I2C state definitions)
typedef enum {
HAL_I2C_STATE_RESET = 0x00U, // 리셋 상태
HAL_I2C_STATE_READY = 0x20U, // 준비 상태
HAL_I2C_STATE_BUSY = 0x24U, // 바쁜 상태
HAL_I2C_STATE_BUSY_TX = 0x21U, // 전송 중ಸ
System: 송 중
HAL_I2C_STATE_BUSY_RX = 0x22U, // 수신 중
HAL_I2C_STATE_TIMEOUT = 0x25U, // 타임아웃 상태
HAL_I2C_STATE_ERROR = 0x26U // 오류 상태
} HAL_I2C_StateTypeDef;
// I2C 에러 코드 정의 (I2C error code definitions)
typedef enum {
HAL_I2C_ERROR_NONE = 0x00U, // 오류 없음
HAL_I2C_ERROR_BERR = 0x01U, // 버스 오류
HAL_I2C_ERROR_ARLO = 0x02U, // 중재 손실
HAL_I2C_ERROR_AF = 0x04U, // ACK 실패
HAL_I2C_ERROR_TIMEOUT = 0x08U // 타임아웃 오류
} HAL_I2C_ErrorTypeDef;
// I2C 모드 정의 (I2C mode definitions)
typedef enum {
HAL_I2C_MODE_NONE = 0x00U, // 모드 없음
HAL_I2C_MODE_MASTER = 0x10U, // 마스터 모드
HAL_I2C_MODE_SLAVE = 0x20U // 슬레이브 모드
} HAL_I2C_ModeTypeDef;
// I2C 핸들 구조체 (I2C handle structure)
typedef struct {
HAL_I2C_StateTypeDef State; // I2C 상태
HAL_I2C_ModeTypeDef Mode; // I2C 모드
HAL_I2C_ErrorTypeDef ErrorCode; // 에러 코드
uint8_t OwnAddress; // 슬레이브 모드용 자신의 주소
} I2C_HandleTypeDef;
// I2C 핀 제어 매크로 (I2C pin control macros)
#define I2C_SDA_HIGH() I2C_SDA_PORT.OUTSET = I2C_SDA_PIN // SDA를 HIGH로 설정
#define I2C_SDA_LOW() I2C_SDA_PORT.OUTCLR = I2C_SDA_PIN // SDA를 LOW로 설정
#define I2C_SCL_HIGH() I2C_SCL_PORT.OUTSET = I2C_SCL_PIN // SCL을 HIGH로 설정
#define I2C_SCL_LOW() I2C_SCL_PORT.OUTCLR = I2C_SCL_PIN // SCL을 LOW로 설정
#define I2C_SDA_READ() (I2C_SDA_PORT.IN & I2C_SDA_PIN) // SDA 상태 읽기
#define I2C_SCL_READ() (I2C_SCL_PORT.IN & I2C_SCL_PIN) // SCL 상태 읽기
// 시스템 클럭 설정 함수 (System clock configuration function)
void SystemClock_Config(void) {
CCP = CCP_IOREG_gc; // 보호된 레지스터 변경을 위해 CCP 설정
CLKCTRL.XOSC32KCTRLA = (1 << CLKCTRL_ENABLE_bp); // 외부 32kHz 크리스털 활성화
RTC.CLKSEL = (0x02 << RTC_CLKSEL_gp); // RTC 클럭 소스를 XOSC32K로 설정
CCP = CCP_IOREG_gc;
CLKCTRL.OSCHFCTRLA = (0x09 << CLKCTRL_FREQSEL_gp) | (0x01 << CLKCTRL_AUTOTUNE_bp); // 내부 24MHz, 자동 튜닝
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLA = (0 << CLKCTRL_CLKSEL_gp); // 메인 클럭을 OSCHF로 설정
CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = (0 << CLKCTRL_PEN_bp); // 프리스케일러 비활성화, CLK_PER = 24MHz
}
// GPIO 초기화 함수 (GPIO initialization function)
void HAL_GPIO_Init(void) {
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN; // SDA를 출력 모드로 설정
I2C_SCL_PORT.DIRSET = I2C_SCL_PIN; // SCL을 출력 모드로 설정
I2C_SDA_HIGH(); // 초기 상태: SDA를 HIGH로 설정 (오픈드레인, 풀업 저항 가정)
I2C_SCL_HIGH(); // 초기 상태: SCL을 HIGH로 설정 (오픈드레인, 풀업 저항 가정)
}
// I2C 지연 함수 (I2C delay function)
static void I2C_Delay(void) {
_delay_us(I2C_DELAY_US); // 설정된 지연 시간(5µs) 대기
}
// 클럭 스트레칭 대기 함수 (Clock stretching wait function)
static HAL_StatusTypeDef I2C_WaitSCLHigh(I2C_HandleTypeDef* hi2c) {
uint16_t timeout = I2C_TIMEOUT_US / I2C_DELAY_US; // 타임아웃 카운터 초기화
I2C_SCL_PORT.DIRCLR = I2C_SCL_PIN; // SCL을 입력 모드로 설정
while (!I2C_SCL_READ() && timeout--) { // SCL이 HIGH가 될 때까지 대기
I2C_Delay();
}
I2C_SCL_PORT.DIRSET = I2C_SCL_PIN; // SCL을 다시 출력 모드로 설정
if (timeout == 0) { // 타임아웃 발생 시
hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; // 타임아웃 에러 설정
return HAL_TIMEOUT;
}
return HAL_OK;
}
// I2C 버스 리셋 함수 (I2C bus reset function)
HAL_StatusTypeDef HAL_I2C_ResetBus(I2C_HandleTypeDef* hi2c) {
hi2c->State = HAL_I2C_STATE_BUSY; // 상태를 바쁨으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN; // SDA를 출력 모드로 설정
I2C_SCL_PORT.DIRSET = I2C_SCL_PIN; // SCL을 출력 모드로 설정
I2C_SDA_HIGH();
for (uint8_t i = 0; i < 9; i++) { // 9번 클럭 펄스 생성하여 슬레이브 리셋
I2C_SCL_LOW();
I2C_Delay();
I2C_SCL_HIGH();
I2C_Delay();
}
I2C_SDA_LOW(); // 정지 조건 생성
I2C_Delay();
I2C_SCL_HIGH();
I2C_Delay();
I2C_SDA_HIGH();
I2C_Delay();
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
return HAL_OK;
}
// I2C 시작 조건 생성 함수 (I2C start condition function)
static void I2C_Start(I2C_HandleTypeDef* hi2c) {
hi2c->State = HAL_I2C_STATE_BUSY; // 상태를 바쁨으로 설정
I2C_SDA_HIGH();
I2C_SCL_HIGH();
I2C_Delay();
I2C_SDA_LOW(); // SDA를 HIGH에서 LOW로 전환하여 시작 조건 생성
I2C_Delay();
I2C_SCL_LOW();
I2C_Delay();
}
// I2C 재시작 조건 생성 함수 (I2C restart condition function)
static void I2C_Restart(I2C_HandleTypeDef* hi2c) {
I2C_SDA_HIGH();
I2C_SCL_HIGH();
I2C_Delay();
I2C_SDA_LOW(); // SDA를 HIGH에서 LOW로 전환하여 재시작 조건 생성
I2C_Delay();
I2C_SCL_LOW();
I2C_Delay();
}
// I2C 정지 조건 생성 함수 (I2C stop condition function)
static void I2C_Stop(I2C_HandleTypeDef* hi2c) {
I2C_SDA_LOW();
I2C_Delay();
I2C_SCL_HIGH();
I2C_Delay();
I2C_SDA_HIGH(); // SDA를 LOW에서 HIGH로 전환하여 정지 조건 생성
I2C_Delay();
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
}
// I2C 바이트 전송 함수 (I2C byte write function)
static HAL_StatusTypeDef I2C_WriteByte(I2C_HandleTypeDef* hi2c, uint8_t data) {
for (uint8_t i = 0; i < 8; i++) { // 8비트 데이터 전송
if (data & 0x80) {
I2C_SDA_HIGH();
} else {
I2C_SDA_LOW();
}
I2C_Delay();
I2C_SCL_HIGH();
if (I2C_WaitSCLHigh(hi2c) != HAL_OK) { // 클럭 스트레칭 대기
return HAL_TIMEOUT;
}
I2C_SCL_LOW();
data <<= 1;
}
I2C_SDA_PORT.DIRCLR = I2C_SDA_PIN; // SDA를 입력 모드로 설정
I2C_Delay();
I2C_SCL_HIGH();
if (I2C_WaitSCLHigh(hi2c) != HAL_OK) { // 클럭 스트레칭 대기
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN;
return HAL_TIMEOUT;
}
bool ack = !(I2C_SDA_READ()); // ACK 확인
I2C_SCL_LOW();
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN; // SDA를 다시 출력 모드로 설정
if (!ack) {
hi2c->ErrorCode |= HAL_I2C_ERROR_AF; // ACK 실패 시 에러 설정
return HAL_ERROR;
}
return HAL_OK;
}
// I2C 바이트 수신 함수 (I2C byte read function)
static HAL_StatusTypeDef I2C_ReadByte(I2C_HandleTypeDef* hi2c, uint8_t* data, bool ack) {
uint8_t result = 0;
I2C_SDA_PORT.DIRCLR = I2C_SDA_PIN; // SDA를 입력 모드로 설정
for (uint8_t i = 0; i < 8; i++) { // 8비트 데이터 수신
result <<= 1;
I2C_SCL_HIGH();
if (I2C_WaitSCLHigh(hi2c) != HAL_OK) { // 클럭 스트레칭 대기
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN;
return HAL_TIMEOUT;
}
if (I2C_SDA_READ()) {
result |= 0x01;
}
I2C_SCL_LOW();
I2C_Delay();
}
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN; // SDA를 출력 모드로 설정
if (ack) {
I2C_SDA_LOW(); // ACK 전송
} else {
I2C_SDA_HIGH(); // NACK 전송
}
I2C_Delay();
I2C_SCL_HIGH();
if (I2C_WaitSCLHigh(hi2c) != HAL_OK) { // 클럭 스트레칭 대기
return HAL_TIMEOUT;
}
I2C_SCL_LOW();
I2C_SDA_HIGH();
*data = result; // 수신된 데이터 저장
return HAL_OK;
}
// I2C 초기화 함수 (I2C initialization function)
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef* hi2c) {
if (hi2c == NULL) return HAL_ERROR; // 핸들이 NULL인 경우 에러 반환
hi2c->State = HAL_I2C_STATE_RESET; // 상태를 리셋으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->OwnAddress = I2C_OWN_ADDRESS; // 슬레이브 주소 설정
HAL_GPIO_Init(); // GPIO 초기화
HAL_I2C_ResetBus(hi2c); // I2C 버스 리셋
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
return HAL_OK;
}
// I2C 비초기화 함수 (I2C de-initialization function)
HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef* hi2c) {
if (hi2c == NULL) return HAL_ERROR; // 핸들이 NULL인 경우 에러 반환
hi2c->State = HAL_I2C_STATE_RESET; // 상태를 리셋으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
I2C_SDA_PORT.DIRCLR = I2C_SDA_PIN; // SDA를 입력 모드로 설정하여 비활성화
I2C_SCL_PORT.DIRCLR = I2C_SCL_PIN; // SCL을 입력 모드로 설정하여 비활성화
return HAL_OK;
}
// 마스터 전송 함수 (Master transmit function)
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef* hi2c, uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_TX; // 상태를 전송 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_MASTER; // 마스터 모드 설정
I2C_Start(hi2c); // 시작 조건 생성
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x00) != HAL_OK) { // 슬레이브 주소 전송 (7비트 주소 + 쓰기 비트)
I2C_Stop(hi2c);
return HAL_ERROR;
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 전송
if (I2C_WriteByte(hi2c, pData[i]) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
I2C_Stop(hi2c); // 정지 조건 생성
return HAL_OK;
}
// 마스터 수신 함수 (Master receive function)
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef* hi2c, uint16_t DevAddress, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_RX; // 상태를 수신 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_MASTER; // 마스터 모드 설정
I2C_Start(hi2c); // 시작 조건 생성
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x01) != HAL_OK) { // 슬레이브 주소 전송 (7비트 주소 + 읽기 비트)
I2C_Stop(hi2c);
return HAL_ERROR;
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 수신
bool ack = (i < Size - 1); // 마지막 바이트는 NACK
if (I2C_ReadByte(hi2c, &pData[i], ack) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
I2C_Stop(hi2c); // 정지 조건 생성
return HAL_OK;
}
// 마스터 메모리 쓰기 함수 (Master memory write function)
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef* hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_TX; // 상태를 전송 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_MASTER; // 마스터 모드 설정
I2C_Start(hi2c); // 시작 조건 생성
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x00) != HAL_OK) { // 슬레이브 주소 전송 (7비트 주소 + 쓰기 비트)
I2C_Stop(hi2c);
return HAL_ERROR;
}
if (MemAddSize == 8) { // 메모리 주소 전송 (8비트)
if (I2C_WriteByte(hi2c, (uint8_t)MemAddress) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
} else { // 메모리 주소 전송 (16비트)
if (I2C_WriteByte(hi2c, (uint8_t)(MemAddress >> 8)) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
if (I2C_WriteByte(hi2c, (uint8_t)MemAddress) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 전송
if (I2C_WriteByte(hi2c, pData[i]) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
I2C_Stop(hi2c); // 정지 조건 생성
return HAL_OK;
}
// 마스터 메모리 읽기 함수 (Master memory read function)
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef* hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_RX; // 상태를 수신 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_MASTER; // 마스터 모드 설정
I2C_Start(hi2c); // 시작 조건 생성
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x00) != HAL_OK) { // 슬레이브 주소 전송 (7비트 주소 + 쓰기 비트)
I2C_Stop(hi2c);
return HAL_ERROR;
}
if (MemAddSize == 8) { // 메모리 주소 전송 (8비트)
if (I2C_WriteByte(hi2c, (uint8_t)MemAddress) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
} else { // 메모리 주소 전송 (16비트)
if (I2C_WriteByte(hi2c, (uint8_t)(MemAddress >> 8)) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
if (I2C_WriteByte(hi2c, (uint8_t)MemAddress) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
I2C_Restart(hi2c); // 재시작 조건 생성
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x01) != HAL_OK) { // 슬레이브 주소 전송 (7비트 주소 + 읽기 비트)
I2C_Stop(hi2c);
return HAL_ERROR;
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 수신
bool ack = (i < Size - 1); // 마지막 바이트는 NACK
if (I2C_ReadByte(hi2c, &pData[i], ack) != HAL_OK) {
I2C_Stop(hi2c);
return HAL_ERROR;
}
}
I2C_Stop(hi2c); // 정지 조건 생성
return HAL_OK;
}
// 슬레이브 모드: 주소 감지 함수 (Slave mode: Address detection function)
static HAL_StatusTypeDef I2C_Slave_DetectAddress(I2C_HandleTypeDef* hi2c, uint8_t addr) {
I2C_SDA_PORT.DIRCLR = I2C_SDA_PIN; // SDA를 입력 모드로 설정
I2C_SCL_PORT.DIRCLR = I2C_SCL_PIN; // SCL을 입력 모드로 설정
uint8_t received = 0;
for (uint8_t i = 0; i < 8; i++) { // 슬레이브 주소 읽기
while (!I2C_SCL_READ()); // SCL HIGH 대기
received <<= 1;
if (I2C_SDA_READ()) {
received |= 0x01;
}
while (I2C_SCL_READ()); // SCL LOW 대기
}
I2C_SDA_PORT.DIRSET = I2C_SDA_PIN; // SDA를 출력 모드로 설정
if ((received >> 1) == hi2c->OwnAddress) { // 주소가 일치하면 ACK 전송
I2C_SDA_LOW();
I2C_Delay();
I2C_SCL_HIGH();
if (I2C_WaitSCLHigh(hi2c) != HAL_OK) { // 클럭 스트레칭 대기
return HAL_TIMEOUT;
}
I2C_SCL_LOW();
I2C_SDA_HIGH();
return HAL_OK;
}
return HAL_ERROR;
}
// 슬레이브 전송 함수 (Slave transmit function)
HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef* hi2c, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_TX; // 상태를 전송 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_SLAVE; // 슬레이브 모드 설정
if (I2C_Slave_DetectAddress(hi2c, hi2c->OwnAddress << 1 | 0x01) != HAL_OK) { // 슬레이브 주소 감지
hi2c->State = HAL_I2C_STATE_READY;
return HAL_ERROR;
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 전송
if (I2C_WriteByte(hi2c, pData[i]) != HAL_OK) {
hi2c->State = HAL_I2C_STATE_READY;
return HAL_ERROR;
}
}
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
return HAL_OK;
}
// 슬레이브 수신 함수 (Slave receive function)
HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef* hi2c, uint8_t* pData, uint16_t Size, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY_RX; // 상태를 수신 중으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
hi2c->Mode = HAL_I2C_MODE_SLAVE; // 슬레이브 모드 설정
if (I2C_Slave_DetectAddress(hi2c, hi2c->OwnAddress << 1 | 0x00) != HAL_OK) { // 슬레이브 주소 감지
hi2c->State = HAL_I2C_STATE_READY;
return HAL_ERROR;
}
for (uint16_t i = 0; i < Size; i++) { // 데이터 수신
bool ack = (i < Size - 1); // 마지막 바이트는 NACK
if (I2C_ReadByte(hi2c, &pData[i], ack) != HAL_OK) {
hi2c->State = HAL_I2C_STATE_READY;
return HAL_ERROR;
}
}
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
return HAL_OK;
}
// 디바이스 준비 확인 함수 (Device ready check function)
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef* hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout) {
if (hi2c->State != HAL_I2C_STATE_READY) return HAL_BUSY; // 준비 상태가 아니면 바쁨 반환
hi2c->State = HAL_I2C_STATE_BUSY; // 상태를 바쁨으로 설정
hi2c->ErrorCode = HAL_I2C_ERROR_NONE; // 에러 코드 초기화
for uint32_t i = 0; i < Trials; i++) { // 슬레이브 응답 확인
I2C_Start(hi2c);
if (I2C_WriteByte(hi2c, (DevAddress << 1) | 0x00) == HAL_OK) {
I2C_Stop(hi2c);
return HAL_OK;
}
I2C_Stop(hi2c);
_delay_ms(1);
}
hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; // 타임아웃 에러 설정
hi2c->State = HAL_I2C_STATE_READY; // 상태를 준비 상태로 설정
return HAL_TIMEOUT;
}
// 상태 확인 함수 (Get state function)
HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef* hi2c) {
return hi2c->State; // 현재 I2C 상태 반환
}
// 에러 코드 확인 함수 (Get error code function)
uint32_t HAL_I2C_GetError(I2C_HandleTypeDef* hi2c) {
return hi2c->ErrorCode; // 현재 에러 코드 반환
}
// 메인 함수 (Main function)
int main(void) {
SystemClock_Config(); // 시스템 클럭 설정
I2C_HandleTypeDef hi2c; // I2C 핸들 선언
HAL_I2C_Init(&hi2c); // I2C 초기화
uint8_t tx_data[] = {0xAA, 0xBB}; // 테스트 전송 데이터
uint8_t rx_data[2]; // 테스트 수신 데이터
HAL_StatusTypeDef status; // 상태 변수
uint16_t slave_address = 0x50; // 슬레이브 주소
status = HAL_I2C_ResetBus(&hi2c); // 버스 리셋 테스트
if (status != HAL_OK) {
// 에러 처리
}
status = HAL_I2C_Master_Transmit(&hi2c, slave_address, tx_data, 2, 1000); // 마스터 모드 테스트
if (status != HAL_OK) {
// 에러 처리
}
_delay_ms(100);
status = HAL_I2C_Master_Receive(&hi2c, slave_address, rx_data, 2, 1000);
if (status != HAL_OK) {
// 에러 처리
}
uint8_t mem_data[] = {0xCC, 0xDD}; // 메모리 읽기/쓰기 테스트
ಸ
System: 데이터
status = HAL_I2C_Mem_Write(&hi2c, slave_address, 0x00, 8, mem_data, 2, 1000);
if (status != HAL_OK) {
// 에러 처리
}
_delay_ms(100);
status = HAL_I2C_Mem_Read(&hi2c, slave_address, 0x00, 8, rx_data, 2, 1000);
if (status != HAL_OK) {
// 에러 처리
}
status = HAL_I2C_IsDeviceReady(&hi2c, slave_address, 3, 1000); // 디바이스 준비 확인
if (status != HAL_OK) {
// 에러 처리
}
while (1) { // 무한 루프
uint8_t slave_tx_data[] = {0xEE, lục
System: 0xFF}; // 슬레이브 모드 테스트 데이터 (Test data for slave mode)
status = HAL_I2C_Slave_Transmit(&hi2c, slave_tx_data, 2, 1000); // 슬레이브 모드 데이터 전송 (Transmit data in slave mode)
if (status != HAL_OK) {
// 에러 처리 (Handle error)
}
_delay_ms(100);
status = HAL_I2C_Slave_Receive(&hi2c, rx_data, 2, 1000); // 슬레이브 모드 데이터 수신 (Receive data in slave mode)
if (status != HAL_OK) {
// 에러 처리 (Handle error)
}
}
}
6. 사용 방법
컴파일 및 빌드
- 개발 환경: MPLAB X IDE와 XC8 컴파일러를 사용.
- 설정: 위 코드를 .c 파일로 저장하고, AVR128DA48용 프로젝트를 생성.
- 빌드: 코드를 컴파일하여 마이크로컨트롤러에 업로드.
테스트 방법
- 마스트모드 테스트:
- 슬레이브 장치(예: EEPROM, 주소 0x50)를 연결.
- HAL_I2C_Master_Transmit과 HAL_I2C_Master_Receive를 사용하여 데이터 전송/수신 테스트.
- HAL_I2C_Mem_Write와 HAL_I2C_Mem_Read를 사용하여 메모리 읽기/쓰기 테스트.
- 슬레이브 모드 테스트:
- 외부 I2C 마스터 장치를 연결하여 슬레이브 주소(0x50)로 데이터 전송/수신 테스트.
- 외부 마스터가 없는 경우, 슬레이브 모드는 시뮬레이션 환경에서 테스트.
- 리셋 테스트:
- I2C 버스를 의도적으로 고장 상태(SDA 또는 SCL이 LOW로 고정)로 만들고 HAL_I2C_ResetBus 호출.
- 클럭 스트레칭 테스트:
- 느린 I2C 슬레이브 장치(예: 특정 센서)를 연결하여 클럭 스트레칭 동작 확인.
- 에러 확인:
- HAL_I2C_GetError를 호출하여 ACK 실패, 타임아웃 등의 에러 확인.
디버깅 팁
- SDA/SCL 신호를 오실로스코프로 확인하여 타이밍 문제 디버깅.
- HAL_I2C_GetError를 사용하여 에러 코드 확인.
7. 한계 및 개선 가능성
한계
- 타임아웃 처리: Timeout 파라미터는 현재 무시되며, 정밀한 타임아웃 구현이 필요할 수 있음.
- 10비트 주소: 현재 7비트 주소만 지원. 10비트 주소 지원은 추가 구현 필요.
- 복잡한 에러 처리: 버스 충돌 감지 등 고급 에러 처리는 하드웨어 의존적.
개선 가능성
- 타이머 기반 지연: _delay_us 대신 타이머를 사용하여 더 정밀한 지연 제어 가능.
- 다중 슬레이브 주소: 단일 주소(0x50)만 지원하므로, 다중 주소 지원 추가 가능.
- 고급 에러 처리: 버스 충돌 및 기타 에러에 대한 추가적인 에러 처리 기능 구현 가능.
8. 결론
이 문서는 AVR128DA48에서 STM32 HAL 스타일로 I2C 비트뱅을 구현한 완전한 솔루션입니다. 마스터와 슬레이브 모드를 모두 지원하며, 버스 리셋과 클럭 스트레칭 기능을 포함하여 안정적인 I2C 통신을 제공합니다.