본문 바로가기
MCU/8051

8051 SPI 비트뱅 코드 구현 : 모드 선택 가능 예제

by linuxgo 2025. 8. 22.

AT89C51 마이크로컨트롤러에서 SPI(Serial Peripheral Interface)를 비트뱅 방식으로 구현한 C 코드를 소개합니다. 이 코드는 SCLK, MOSI, MISO, SS 라인을 소프트웨어로 제어하며, SPI 모드(0~3)를 사용자가 선택할 수 있도록 설계되었습니다. 8051 기반 SPI 통신을 배우는 초보자부터 고급 개발자까지 활용 가능한 예제입니다.

키워드: 8051 SPI, AT89C51, 비트뱅, SPI 모드 선택, 마이크로컨트롤러 통신

1. 하드웨어 설정

  • 마이크로컨트롤러: AT89C51 (8051 기반)
  • SPI 핀:
    • SCLK: P2.0 (클럭)
    • MOSI: P2.1 (마스터 출력, 슬레이브 입력)
    • MISO: P2.2 (마스터 입력, 슬레이브 출력)
    • SS: P2.3 (슬레이브 선택)
  • 컴파일러: Keil uVision 또는 SDCC
  • 속도: 약 100kHz (비트뱅 방식, 대략적 타이밍)
  • SPI 모드:
    • Mode 0: CPOL=0, CPHA=0 (SCLK 기본 LOW, 첫 번째 에지 샘플링)
    • Mode 1: CPOL=0, CPHA=1 (SCLK 기본 LOW, 두 번째 에지 샘플링)
    • Mode 2: CPOL=1, CPHA=0 (SCLK 기본 HIGH, 첫 번째 에지 샘플링)
    • Mode 3: CPOL=1, CPHA=1 (SCLK 기본 HIGH, 두 번째 에지 샴플링)

 

SPI MODE

 

참고: SPI 모드는 슬레이브 장치 데이터시트에 따라 설정하세요.

2. SPI 비트뱅 코드

아래는 AT89C51에서 SPI 비트뱅을 구현한 C 코드로, 모드 선택 기능을 포함합니다.

#include <reg51.h> // AT89C51 헤더 파일
#include <intrins.h> // _nop_() 함수를 위한 헤더

// SPI 핀 정의
sbit SCLK = P2^0; // SCLK: 클럭 (P2.0)
sbit MOSI = P2^1; // MOSI: 마스터 출력 (P2.1)
sbit MISO = P2^2; // MISO: 마스터 입력 (P2.2)
sbit SS = P2^3;   // SS: 슬레이브 선택 (P2.3)

// SPI 모드 설정 구조체
typedef struct {
    bit cpol; // 클럭 폴리티 (0: LOW, 1: HIGH)
    bit cpha; // 클럭 페이즈 (0: 첫 번째 에지, 1: 두 번째 에지)
} SPI_Mode;

// 전역 SPI 모드 변수
SPI_Mode spi_mode;

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

// SPI 초기화
void SPI_Init(unsigned char mode) {
    // SPI 모드 설정 (0~3)
    switch (mode) {
        case 0: spi_mode.cpol = 0; spi_mode.cpha = 0; break; // Mode 0
        case 1: spi_mode.cpol = 0; spi_mode.cpha = 1; break; // Mode 1
        case 2: spi_mode.cpol = 1; spi_mode.cpha = 0; break; // Mode 2
        case 3: spi_mode.cpol = 1; spi_mode.cpha = 1; break; // Mode 3
        default: spi_mode.cpol = 0; spi_mode.cpha = 0; // 기본값: Mode 0
    }

    SS = 1;   // SS HIGH (슬레이브 비활성화)
    SCLK = spi_mode.cpol; // SCLK를 CPOL에 따라 초기화
    MOSI = 0; // MOSI 초기화
    MISO = 1; // MISO 입력 모드
}

// SPI 바이트 전송 및 수신
unsigned char SPI_Transfer(unsigned char dat) {
    unsigned char i, rx_dat = 0;

    // 8비트 데이터 전송 및 수신
    for (i = 0; i < 8; i++) {
        if (spi_mode.cpha == 0) {
            // CPHA=0: 첫 번째 에지에서 데이터 설정, 두 번째 에지에서 샘플링
            MOSI = (dat & 0x80) ? 1 : 0; // MSB 먼저 설정
            dat <<= 1; // 다음 비트로 이동
            SCLK = !spi_mode.cpol; // 첫 번째 에지
            SPI_Delay();
            rx_dat <<= 1; // 수신 데이터 비트 이동
            rx_dat |= MISO; // MISO에서 비트 읽기
            SCLK = spi_mode.cpol; // 두 번째 에지
            SPI_Delay();
        } else {
            // CPHA=1: 첫 번째 에지에서 샘플링, 두 번째 에지에서 데이터 설정
            SCLK = !spi_mode.cpol; // 첫 번째 에지
            SPI_Delay();
            rx_dat <<= 1; // 수신 데이터 비트 이동
            rx_dat |= MISO; // MISO에서 비트 읽기
            SCLK = spi_mode.cpol; // 두 번째 에지
            SPI_Delay();
            MOSI = (dat & 0x80) ? 1 : 0; // MSB 먼저 설정
            dat <<= 1; // 다음 비트로 이동
        }
    }
    return rx_dat;
}

// SPI 데이터 쓰기
void SPI_Write(unsigned char device_addr, unsigned char reg_addr, unsigned char data) {
    SS = 0; // 슬레이브 활성화
    SPI_Delay();
    SPI_Transfer(device_addr); // 장치 주소 전송
    SPI_Transfer(reg_addr);    // 레지스터 주소 전송
    SPI_Transfer(data);        // 데이터 전송
    SS = 1; // 슬레이브 비활성화
    SPI_Delay();
}

// SPI 데이터 읽기
unsigned char SPI_Read(unsigned char device_addr, unsigned char reg_addr) {
    unsigned char dat;
    SS = 0; // 슬레이브 활성화
    SPI_Delay();
    SPI_Transfer(device_addr | 0x80); // 장치 주소 (읽기: MSB=1)
    SPI_Transfer(reg_addr);           // 레지스터 주소 전송
    dat = SPI_Transfer(0xFF);         // 더미 데이터 전송하여 데이터 읽기
    SS = 1; // 슬레이브 비활성화
    SPI_Delay();
    return dat;
}

// 메인 함수
void main(void) {
    SPI_Init(0); // SPI 초기화 (Mode 0, 필요 시 0~3으로 변경)

    // 예제: SPI 장치에 데이터 쓰기 및 읽기
    SPI_Write(0x00, 0x10, 0xAA); // 주소 0x10에 0xAA 쓰기
    unsigned char data = SPI_Read(0x00, 0x10); // 주소 0x10에서 데이터 읽기

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

3. 코드 설명

  1. 핀 정의:
    •   SCLK(P2.0), MOSI(P2.1), MISO(P2.2), SS(P2.3) 사용. 다른 포트로 변경 가능.
    •   MISO는 입력 모드로 설정.
  2. SPI 모드 선택:
    •   SPI_Mode 구조체로 CPOL(클럭 폴리티)와 CPHA(클럭 페이즈) 정의.
    •   SPI_Init(mode)에서 0~3 값을 받아 모드 설정 (Mode 0 기본).
  3. 지연 함수:
    •   SPI_Delay()는 12MHz 클록 기준 약 5us 지연. 클록 속도에 따라 _nop_() 횟수 조정.
  4. SPI 전송 함수:
    •   SPI_Transfer(): CPHA에 따라 데이터 설정/샘플링 타이밍 처리.
    •   SPI_Write()와 SPI_Read()로 장치 주소, 레지스터 주소, 데이터 처리.
  5. 메인 함수:
    •   Mode 0으로 초기화. 다른 모드는 SPI_Init(1~3)으로 변경 가능.
    •   예제는 장치 주소 0x00, 레지스터 0x10에 데이터 쓰기/읽기 수행.

4. 주의사항

  • 모드 설정: SPI_Init()의 모드 값(0~3)을 슬레이브 장치 데이터시트에 맞게 설정.
  • 타이밍 조정: SPI_Delay()는 12MHz 클록 기준. 다른 클록에서는 타이머 사용 권장.
  • 슬레이브 주소: SPI는 표준 주소 체계가 없으므로 데이터시트 확인 필수.
  • 하드웨어 연결: SS는 슬레이브마다 별도 제어. MISO는 입력 모드.

5. 디버깅 팁

  • 오실로스코프 사용: SCLK, MOSI, MISO, SS 신호의 타이밍과 무결성 확인.
  • 모드 호환성: SPI 모드가 슬레이브 장치와 맞는지 데이터시트로 점검.
  • 시뮬레이터: Keil uVision 디버거로 코드 동작 확인.

6. 관련 자료