본문 바로가기
MCU/8051

8051 I2C 비트뱅(bit-bang)를 STM32 HAL API 스타일로 코드 구현

by linuxgo 2025. 8. 2.

STM32의 HAL API 스타일로 AT89C51용 I2C 비트뱅 코드를 재작성하겠습니다. STM32 HAL API는 HAL_I2C_ 접두사를 사용하며, 상태 반환값(HAL_OK, HAL_ERROR 등)과 구조체 기반 설정을 활용합니다. AT89C51에는 하드웨어 I2C 모듈이 없으므로, 비트뱅 방식으로 HAL 스타일의 인터페이스를 모방하여 구현합니다. 아래 코드는 STM32 HAL API의 구조와 네이밍 컨벤션을 따라 설계되었습니다.

가정 및 설정

  • 마이크로컨트롤러: AT89C51 (8051 기반)
  • I2C 핀:
    • SCL: P2.0
    • SDA: P2.1
  • 컴파일러: Keil uVision 또는 SDCC
  • 외부 풀업 저항: SCL과 SDA에 4.7kΩ 풀업 저항
  • 클럭 속도: 약 100kHz (비트뱅, 12MHz 시스템 클록 기준)
  • HAL 스타일: HAL_I2C_ 함수, HAL_StatusTypeDef 반환값, I2C_HandleTypeDef 구조체 사용

코드

#include <reg51.h>
#include <intrins.h>

// HAL 상태 정의
typedef enum {
    HAL_OK       = 0x00,
    HAL_ERROR    = 0x01,
    HAL_BUSY     = 0x02,
    HAL_TIMEOUT  = 0x03
} HAL_StatusTypeDef;

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

// I2C 핸들 구조체
typedef struct {
    unsigned char scl_pin; // SCL 핀 (미사용, STM32 스타일 모방)
    unsigned char sda_pin; // SDA 핀 (미사용, STM32 스타일 모방)
    unsigned int timeout;   // 타임아웃 (미사용, 예제 단순화)
} I2C_HandleTypeDef;

// 지연 함수
void I2C_Delay(void) {
    _nop_(); _nop_(); _nop_(); _nop_(); // 약 5us 지연 (12MHz 클록)
}

// I2C 초기화
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c) {
    // 핀 초기화 (오픈 드레인 모방)
    SDA = 1; // SDA HIGH
    SCL = 1; // SCL HIGH
    hi2c->timeout = 1000; // 타임아웃 설정 (미사용)
    return HAL_OK;
}

// I2C 시작 조건
HAL_StatusTypeDef HAL_I2C_Start(I2C_HandleTypeDef *hi2c) {
    SDA = 1; // SDA HIGH
    SCL = 1; // SCL HIGH
    I2C_Delay();
    SDA = 0; // SDA LOW (시작 조건)
    I2C_Delay();
    SCL = 0; // SCL LOW
    return HAL_OK;
}

// I2C 정지 조건
HAL_StatusTypeDef HAL_I2C_Stop(I2C_HandleTypeDef *hi2c) {
    SDA = 0; // SDA LOW
    SCL = 1; // SCL HIGH
    I2C_Delay();
    SDA = 1; // SDA HIGH (정지 조건)
    I2C_Delay();
    return HAL_OK;
}

// I2C 바이트 쓰기
HAL_StatusTypeDef HAL_I2C_Master_TransmitByte(I2C_HandleTypeDef *hi2c, unsigned char data) {
    unsigned char i;
    bit ack;

    // 8비트 데이터 전송
    for (i = 0; i < 8; i++) {
        SCL = 0; // SCL LOW
        SDA = (data & 0x80) ? 1 : 0; // MSB부터 전송
        I2C_Delay();
        SCL = 1; // SCL HIGH
        I2C_Delay();
        data <<= 1;
    }

    // ACK 확인
    SCL = 0; // SCL LOW
    SDA = 1; // SDA 입력 모드
    I2C_Delay();
    SCL = 1; // SCL HIGH
    I2C_Delay();
    ack = SDA; // ACK 비트 읽기
    SCL = 0; // SCL LOW

    return ack ? HAL_ERROR : HAL_OK;
}

// I2C 바이트 읽기
HAL_StatusTypeDef HAL_I2C_Master_ReceiveByte(I2C_HandleTypeDef *hi2c, unsigned char *data, bit send_ack) {
    unsigned char i;
    *data = 0;

    // 8비트 데이터 수신
    SDA = 1; // SDA 입력 모드
    for (i = 0; i < 8; i++) {
        *data <<= 1;
        SCL = 0; // SCL LOW
        I2C_Delay();
        SCL = 1; // SCL HIGH
        I2C_Delay();
        *data |= 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 HAL_OK;
}

// I2C 메모리 쓰기 (STM32 HAL 스타일)
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, unsigned char dev_addr, unsigned char mem_addr, unsigned char *pData, unsigned char size) {
    HAL_StatusTypeDef status;

    // 시작 조건
    status = HAL_I2C_Start(hi2c);
    if (status != HAL_OK) return status;

    // 슬레이브 주소 전송 (쓰기)
    status = HAL_I2C_Master_TransmitByte(hi2c, dev_addr << 1);
    if (status != HAL_OK) {
        HAL_I2C_Stop(hi2c);
        return status;
    }

    // 메모리 주소 전송
    status = HAL_I2C_Master_TransmitByte(hi2c, mem_addr);
    if (status != HAL_OK) {
        HAL_I2C_Stop(hi2c);
        return status;
    }

    // 데이터 전송
    while (size--) {
        status = HAL_I2C_Master_TransmitByte(hi2c, *pData++);
        if (status != HAL_OK) {
            HAL_I2C_Stop(hi2c);
            return status;
        }
    }

    // 정지 조건
    return HAL_I2C_Stop(hi2c);
}

// I2C 메모리 읽기 (STM32 HAL 스타일)
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, unsigned char dev_addr, unsigned char mem_addr, unsigned char *pData, unsigned char size) {
    HAL_StatusTypeDef status;

    // 시작 조건
    status = HAL_I2C_Start(hi2c);
    if (status != HAL_OK) return status;

    // 슬레이브 주소 전송 (쓰기)
    status = HAL_I2C_Master_TransmitByte(hi2c, dev_addr << 1);
    if (status != HAL_OK) {
        HAL_I2C_Stop(hi2c);
        return status;
    }

    // 메모리 주소 전송
    status = HAL_I2C_Master_TransmitByte(hi2c, mem_addr);
    if (status != HAL_OK) {
        HAL_I2C_Stop(hi2c);
        return status;
    }

    // 반복 시작 조건
    status = HAL_I2C_Start(hi2c);
    if (status != HAL_OK) return status;

    // 슬레이브 주소 전송 (읽기)
    status = HAL_I2C_Master_TransmitByte(hi2c, (dev_addr << 1) | 1);
    if (status != HAL_OK) {
        HAL_I2C_Stop(hi2c);
        return status;
    }

    // 데이터 수신
    while (size--) {
        status = HAL_I2C_Master_ReceiveByte(hi2c, pData++, (size > 0) ? 1 : 0);
        if (status != HAL_OK) {
            HAL_I2C_Stop(hi2c);
            return status;
        Facet
        }
    }

    // 정지 조건
    return HAL_I2C_Stop(hi2c);
}

// 메인 함수
void main(void) {
    I2C_HandleTypeDef hi2c;
    unsigned char data_write = 0xAA;
    unsigned char data_read = 0;

    // I2C 초기화
    HAL_I2C_Init(&hi2c);

    // 예제: EEPROM (24C02) 쓰기 및 읽기
    HAL_I2C_Mem_Write(&hi2c, 0x50, 0x10, &data_write, 1); // 주소 0x10에 0xAA 쓰기
    HAL_I2C_Mem_Read(&hi2c, 0x50, 0x10, &data_read, 1);  // 주소 0x10에서 데이터 읽기

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

코드 설명

  1. HAL 스타일 구조:
    •     HAL_StatusTypeDef: STM32 HAL처럼 HAL_OK, HAL_ERROR 등을 반환값으로 사용.
    •     I2C_HandleTypeDef: I2C 설정을 위한 구조체. AT89C51에서는 핀 설정 등이 단순화됨.
    •     함수 이름: HAL_I2C_Init, HAL_I2C_Mem_Write, HAL_I2C_Mem_Read 등 STM32 HAL 네이밍 컨벤션 준수.
  2. 핀 및 지연:
    •     SCL(P2.0), SDA(P2.1) 사용.
    •     I2C_Delay()는 12MHz 클록 기준 약 5us 지연으로 100kHz I2C 속도 구현.
  3. 주요 함수:
    •     HAL_I2C_Init(): I2C 핀 초기화.
    •     HAL_I2C_Start(), HAL_I2C_Stop(): 시작/정지 조건 생성.
    •     HAL_I2C_Master_TransmitByte(), HAL_I2C_Master_ReceiveByte(): 바이트 단위 데이터 송수신.
    •     HAL_I2C_Mem_Write(), HAL_I2C_Mem_Read(): STM32 HAL 스타일의 메모리 쓰기/읽기 함수.
  4. 메인 함수:
    •     24C02 EEPROM(주소 0x50)에 데이터 쓰기/읽기 예제 포함.
    •     실제 장치 주소와 레지스터는 데이터시트에 맞게 수정 필요.

STM32 HAL API와의 차이점

  •    하드웨어 제약: AT89C51은 하드웨어 I2C 모듈이 없으므로 비트뱅으로 구현.
  •    타임아웃: timeout 필드는 STM32 스타일로 포함했으나, 예제 단순화를 위해 사용하지 않음.
  •    인터럽트: STM32 HAL은 인터럽트/DMA 지원이 기본이나, 이 코드는 폴링 기반.

주의사항

  •    타이밍 조정: I2C_Delay()는 시스템 클록에 맞게 조정 필요. 12MHz 외의 클록에서는 _nop_() 횟수 변경.
  •    풀업 저항: SCL/SDA에 4.7kΩ 풀업 저항 필수.
  •    슬레이브 주소: 7비트 주소 사용, LSB는 읽기(1)/쓰기(0)로 설정.
  •    디버깅: 오실로스코프나 로직 애널라이저로 신호 확인 권장.