본문 바로가기
MCU/8051

8051 I2C 비트뱅(bit-bang) 코드 구현

by linuxgo 2025. 8. 2.

AT89C51 마이크로컨트롤러에서 I2C 프로토콜을 비트뱅 방식으로 구현하는 C 코드를 작성해드리겠습니다. 비트뱅 I2C는 소프트웨어로 SCL(클럭)과 SDA(데이터) 라인을 제어하여 I2C 통신을 구현하는 방식입니다. 아래는 기본적인 I2C 마스터 구현 코드입니다.

Hardware 설정

  • 마이크로컨트롤러: AT89C51 (8051 기반)
  • I2C 핀:
    •   SCL: P2.0
    •   SDA: P2.1
  • 컴파일러: Keil uVision 또는 SDCC와 같은 8051용 C 컴파일러
  • 외부 풀업 저항: I2C 라인(SCL, SDA)에 4.7kΩ 풀업 저항이 연결되어 있다고 가정
  • 속도: 약 100kHz I2C 클럭 속도 (비트뱅이므로 대략적인 타이밍)
#include <reg51.h> // AT89C51 헤더 파일
#include <intrins.h> // _nop_() 함수를 위한 헤더

// I2C 핀 정의
sbit SDA = P2^1; // SDA: P2.1
sbit SCL = P2^0; // SCL: P2.0

// 지연 함수 (타이밍 조정을 위해)
void I2C_Delay(void) {
    _nop_(); _nop_(); _nop_(); _nop_(); // 약 5us 지연 (12MHz 클록 기준)
}

// I2C 초기화
void I2C_Init(void) {
    SDA = 1; // SDA를 HIGH로 설정 (오픈 드레인)
    SCL = 1; // SCL을 HIGH로 설정 (오픈 드레인)
}

// I2C 시작 조건
void I2C_Start(void) {
    SDA = 1; // SDA HIGH
    SCL = 1; // SCL HIGH
    I2C_Delay();
    SDA = 0; // SDA를 LOW로 (시작 조건)
    I2C_Delay();
    SCL = 0; // SCL을 LOW로
}

// I2C 정지 조건
void I2C_Stop(void) {
    SDA = 0; // SDA LOW
    SCL = 1; // SCL HIGH
    I2C_Delay();
    SDA = 1; // SDA를 HIGH로 (정지 조건)
    I2C_Delay();
}

// I2C 1바이트 쓰기
bit I2C_WriteByte(unsigned char dat) {
    unsigned char i;
    bit ack;

    // 8비트 데이터 전송
    for (i = 0; i < 8; i++) {
        SCL = 0; // SCL LOW
        SDA = (dat & 0x80) ? 1 : 0; // MSB부터 전송
        I2C_Delay();
        SCL = 1; // SCL HIGH (데이터 읽기 타이밍)
        I2C_Delay();
        dat <<= 1; // 다음 비트로 이동
    }

    // ACK 확인
    SCL = 0; // SCL LOW
    SDA = 1; // SDA를 입력 모드로 (HIGH, 풀업 저항)
    I2C_Delay();
    SCL = 1; // SCL HIGH
    I2C_Delay();
    ack = SDA; // ACK 비트 읽기 (0: ACK, 1: NACK)
    SCL = 0; // SCL LOW
    return ack;
}

// I2C 1바이트 읽기
unsigned char I2C_ReadByte(bit send_ack) {
    unsigned char i, dat = 0;

    // 8비트 데이터 수신
    SDA = 1; // SDA를 입력 모드로
    for (i = 0; i < 8; i++) {
        dat <<= 1; // 데이터 비트 이동
        SCL = 0; // SCL LOW
        I2C_Delay();
        SCL = 1; // SCL HIGH (데이터 읽기 타이밍)
        I2C_Delay();
        dat |= SDA; // SDA에서 비트 읽기
    }

    // ACK 또는 NACK 전송
    SCL = 0; // SCL LOW
    SDA = send_ack ? 0 : 1; // ACK(0) 또는 NACK(1) 설정
    I2C_Delay();
    SCL = 1; // SCL HIGH
    I2C_Delay();
    SCL = 0; // SCL LOW
    SDA = 1; // SDA를 다시 입력 모드로
    return dat;
}

// 예제: I2C 장치에 데이터 쓰기
void I2C_WriteData(unsigned char device_addr, unsigned char reg_addr, unsigned char data) {
    I2C_Start(); // 시작 조건
    I2C_WriteByte(device_addr << 1); // 슬레이브 주소 (쓰기: LSB = 0)
    I2C_WriteByte(reg_addr); // 레지스터 주소
    I2C_WriteByte(data); // 데이터 쓰기
    I2C_Stop(); // 정지 조건
}

// 예제: I2C 장치에서 데이터 읽기
unsigned char I2C_ReadData(unsigned char device_addr, unsigned char reg_addr) {
    unsigned char dat;
    I2C_Start(); // 시작 조건
    I2C_WriteByte(device_addr << 1); // 슬레이브 주소 (쓰기: LSB = 0)
    I2C_WriteByte(reg_addr); // 레지스터 주소
    I2C_Start(); // 반복 시작 조건
    I2C_WriteByte((device_addr << 1) | 1); // 슬레이브 주소 (읽기: LSB = 1)
    dat = I2C_ReadByte(0); // 데이터 읽기 (NACK)
    I2C_Stop(); // 정지 조건
    return dat;
}

// 메인 함수
void main(void) {
    I2C_Init(); // I2C 초기화

    // 예제: EEPROM (24C02) 쓰기 및 읽기
    I2C_WriteData(0x50, 0x10, 0xAA); // 주소 0x10에 0xAA 쓰기 (0x50: EEPROM 주소)
    unsigned char data = I2C_ReadData(0x50, 0x10); // 주소 0x10에서 데이터 읽기

    while (1); // 무한 루프
}

코드 설명

  1. 핀 정의:
    •   SDASCL은 P2.0과 P2.1에 연결되어 있습니다. 필요에 따라 다른 포트로 변경 가능합니다.
    •   AT89C51은 오픈 드레인 출력이 없으므로, 외부 풀업 저항이 필수입니다.
  2. 지연 함수 (I2C_Delay):
    •   _nop_()을 사용하여 약 5us 지연을 구현 (12MHz 클록 기준).
    •   클록 속도에 따라 타이밍을 조정해야 할 수 있습니다.
  3. I2C 기본 함수:
    •   I2C_Start(): I2C 시작 조건 (SDA HIGH → LOW while SCL HIGH).
    •   I2C_Stop(): I2C 정지 조건 (SDA LOW → HIGH while SCL HIGH).
    •   I2C_WriteByte(): 1바이트 데이터를 전송하고 ACK를 확인.
    •   I2C_ReadByte(): 1바이트 데이터를 읽고 ACK/NACK을 전송.
  4. 응용 함수:
    •   I2C_WriteData(): 특정 슬레이브 장치의 레지스터에 데이터를 쓰는 함수.
    •   I2C_ReadData(): 특정 슬레이브 장치의 레지스터에서 데이터를 읽는 함수.
  5. 메인 함수:
    •   예제로 24C02 EEPROM(주소 0x50)에 데이터를 쓰고 읽는 동작을 포함.
    •   실제 사용 시 슬레이브 주소와 레지스터 주소를 장치 데이터시트에 맞게 수정해야 합니다.

주의사항

  • 타이밍 조정: I2C_Delay()는 시스템 클록(예: 12MHz)에 따라 조정 필요. 다른 클록 속도에서는 _nop_() 호출 횟수를 변경하거나 타이머를 사용할 수 있습니다.
  • 슬레이브 주소: I2C 장치의 주소는 데이터시트를 확인하여 설정. 위 코드에서는 7비트 주소(예: 0x50)를 사용하며, LSB는 읽기(1)/쓰기(0)를 나타냅니다.
  • 풀업 저항: SCL과 SDA 라인에 4.7kΩ 또는 10kΩ 풀업 저항이 필요합니다.
  • 인터럽트: 이 코드는 폴링 기반이며, 인터럽트를 사용하려면 추가 수정이 필요합니다.

디버깅 팁

  • 오실로스코프 또는 로직 애널라이저로 SCL/SDA 신호를 확인하여 타이밍과 신호 무결성을 점검.
  • ACK 비트가 제대로 수신되지 않으면 슬레이브 주소나 연결을 확인.
  • 실제 하드웨어 테스트 전 시뮬레이터(Keil uVision의 디버거 등)로 동작 확인.