AT89C51에서 SPI 통신을 비트뱅(Bit-Banging) 방식으로 구현하고, 이를 STM32 HAL API와 유사한 스타일로 작성하는 예제를 제공하겠습니다. AT89C51은 8051 기반의 8비트 마이크로컨트롤러로, 하드웨어 SPI 모듈이 없으므로 비트뱅을 통해 SPI를 소프트웨어로 구현해야 합니다. STM32 HAL API의 구조를 참고하여 함수 중심으로 간결하고 모듈화된 코드를 작성하겠습니다.
목표
- SPI 비트뱅 구현: AT89C51의 GPIO를 사용하여 SPI 마스터 모드를 소프트웨어로 구현.
- STM32 HAL 스타일: HAL_SPI_Transmit, HAL_SPI_Receive와 같은 함수 인터페이스를 모방.
- 구성: SPI 기본 동작(클럭, MOSI, MISO, CS)을 제어하는 함수 제공.
- 가정: CPOL=0, CPHA=0 (모드 0)으로 설정, MSB 먼저 전송.
하드웨어 설정
AT89C51의 포트를 다음과 같이 사용한다고 가정:
- SCK (Serial Clock): P1.0
- MOSI (Master Out Slave In): P1.1
- MISO (Master In Slave Out): P1.2
- CS (Chip Select): P1.3
코드 예제
아래는 Keil C51 컴파일러를 기준으로 작성된 코드입니다. STM32 HAL API 스타일을 따라 함수 이름을 직관적으로 구성하고, 주석으로 동작을 설명했습니다.
#include <reg51.h>
#include <intrins.h>
// SPI 핀 정의
sbit SPI_SCK = P1^0; // 클럭
sbit SPI_MOSI = P1^1; // 마스터 출력
sbit SPI_MISO = P1^2; // 마스터 입력
sbit SPI_CS = P1^3; // 칩 셀렉트
// SPI 모드 정의
#define SPI_MODE_0 0 // CPOL=0, CPHA=0
#define SPI_MODE_1 1 // CPOL=0, CPHA=1
#define SPI_MODE_2 2 // CPOL=1, CPHA=0
#define SPI_MODE_3 3 // CPOL=1, CPHA=1
// 데이터 순서 정의
#define SPI_DATA_ORDER_MSB 0
#define SPI_DATA_ORDER_LSB 1
// SPI 설정 구조체
typedef struct {
unsigned char mode; // SPI 모드 (0~3)
unsigned char dataOrder; // 데이터 순서 (MSB/LSB)
unsigned int clockDelay; // 클럭 지연
} SPI_InitTypeDef;
// 인터럽트 기반 송수신을 위한 전역 변수
unsigned char *g_pTxData; // 전송 데이터 포인터
unsigned char *g_pRxData; // 수신 데이터 포인터
unsigned int g_Size; // 데이터 크기
unsigned char g_BitIndex; // 현재 비트 인덱스
unsigned char g_ByteIndex; // 현재 바이트 인덱스
bit g_TransferComplete; // 전송 완료 플래그
// 함수 프로토타입
void HAL_SPI_Init(SPI_InitTypeDef *SPI_Init);
void HAL_SPI_Transmit(unsigned char *pData, unsigned int Size);
void HAL_SPI_Receive(unsigned char *pData, unsigned int Size);
void HAL_SPI_TransmitReceive(unsigned char *pTxData, unsigned char *pRxData, unsigned int Size);
void HAL_SPI_Transmit_IT(unsigned char *pData, unsigned int Size);
void HAL_SPI_Receive_IT(unsigned char *pData, unsigned int Size);
void HAL_SPI_TransmitReceive_IT(unsigned char *pTxData, unsigned char *pRxData, unsigned int Size);
void SPI_Delay(unsigned int delay);
void W25Q64_ReadData(unsigned long addr, unsigned char *pData, unsigned int Size);
void W25Q64_WriteEnable(void);
void W25Q64_WriteData(unsigned long addr, unsigned char *pData, unsigned int Size);
void Timer0_Init(void);
// SPI 초기화
void HAL_SPI_Init(SPI_InitTypeDef *SPI_Init) {
// 핀 초기화
SPI_MOSI = 0;
SPI_CS = 1;
// CPOL에 따라 SCK 초기 상태 설정
if (SPI_Init->mode == SPI_MODE_0 || SPI_Init->mode == SPI_MODE_1) {
SPI_SCK = 0; // CPOL=0
} else {
SPI_SCK = 1; // CPOL=1
}
// 전역 변수 초기화
g_TransferComplete = 1;
}
// SPI 데이터 전송 (폴링)
void HAL_SPI_Transmit(unsigned char *pData, unsigned int Size) {
unsigned int i, j;
unsigned char txByte;
for (i = 0; i < Size; i++) {
txByte = pData[i];
for (j = 0; j < 8; j++) {
SPI_MOSI = (txByte & 0x80) ? 1 : 0;
SPI_SCK = !SPI_SCK; // 클럭 토글
SPI_Delay(10);
SPI_SCK = !SPI_SCK;
SPI_Delay(10);
txByte <<= 1;
}
}
}
// SPI 데이터 수신 (폴링)
void HAL_SPI_Receive(unsigned char *pData, unsigned int Size) {
unsigned int i, j;
unsigned char rxByte;
for (i = 0; i < Size; i++) {
rxByte = 0;
for (j = 0; j < 8; j++) {
rxByte <<= 1;
SPI_SCK = !SPI_SCK;
SPI_Delay(10);
rxByte |= SPI_MISO;
SPI_SCK = !SPI_SCK;
SPI_Delay(10);
}
pData[i] = rxByte;
}
}
// SPI 송수신 (폴링)
void HAL_SPI_TransmitReceive(unsigned char *pTxData, unsigned char *pRxData, unsigned int Size) {
unsigned int i, j;
unsigned char txByte, rxByte;
for (i = 0; i < Size; i++) {
txByte = pTxData[i];
rxByte = 0;
for (j = 0; j < 8; j++) {
SPI_MOSI = (txByte & 0x80) ? 1 : 0;
txByte <<= 1;
rxByte <<= 1;
SPI_SCK = !SPI_SCK;
SPI_Delay(10);
rxByte |= SPI_MISO;
SPI_SCK = !SPI_SCK;
SPI_Delay(10);
}
pRxData[i] = rxByte;
}
}
// 타이머 0 초기화 (인터럽트용)
void Timer0_Init(void) {
TMOD = 0x01; // 타이머 0, 모드 1 (16비트 타이머)
TH0 = 0xFF; // 약 10us 지연 (12MHz 기준)
TL0 = 0xA4;
TR0 = 1; // 타이머 시작
ET0 = 1; // 타이머 0 인터럽트 활성화
EA = 1; // 전체 인터럽트 활성화
}
// 타이머 0 인터럽트 서비스 루틴
void Timer0_ISR(void) interrupt 1 {
unsigned char txByte;
TR0 = 0; // 타이머 정지
if (!g_TransferComplete) {
if (g_BitIndex < 8) {
// 송신
txByte = g_pTxData[g_ByteIndex];
SPI_MOSI = (txByte & (0x80 >> g_BitIndex)) ? 1 : 0;
// 수신
g_pRxData[g_ByteIndex] <<= 1;
g_pRxData[g_ByteIndex] |= SPI_MISO;
SPI_SCK = !SPI_SCK; // 클럭 토글
g_BitIndex++;
if (g_BitIndex == 8) {
g_BitIndex = 0;
g_ByteIndex++;
if (g_ByteIndex >= g_Size) {
g_TransferComplete = 1;
SPI_CS = 1; // 전송 완료
}
}
}
// 타이머 재설정
TH0 = 0xFF;
TL0 = 0xA4;
TR0 = 1; // 타이머 재시작
}
}
// SPI 인터럽트 기반 전송
void HAL_SPI_Transmit_IT(unsigned char *pData, unsigned int Size) {
g_pTxData = pData;
g_pRxData = pData; // 더미 수신 버퍼
g_Size = Size;
g_BitIndex = 0;
g_ByteIndex = 0;
g_TransferComplete = 0;
SPI_CS = 0;
Timer0_Init();
}
// SPI 인터럽트 기반 수신
void HAL_SPI_Receive_IT(unsigned char *pData, unsigned int Size) {
g_pTxData = pData; // 더미 송신 버퍼
g_pRxData = pData;
g_Size = Size;
g_BitIndex = 0;
g_ByteIndex = 0;
g_TransferComplete = 0;
SPI_CS = 0;
Timer0_Init();
}
// SPI 인터럽트 기반 송수신
void HAL_SPI_TransmitReceive_IT(unsigned char *pTxData, unsigned char *pRxData, unsigned int Size) {
g_pTxData = pTxData;
g_pRxData = pRxData;
g_Size = Size;
g_BitIndex = 0;
g_ByteIndex = 0;
g_TransferComplete = 0;
SPI_CS = 0;
Timer0_Init();
}
// W25Q64 Write Enable
void W25Q64_WriteEnable(void) {
unsigned char cmd = 0x06; // Write Enable 명령
SPI_CS = 0;
HAL_SPI_Transmit(&cmd, 1);
SPI_CS = 1;
}
// W25Q64 데이터 읽기
void W25Q64_ReadData(unsigned long addr, unsigned char *pData, unsigned int Size) {
unsigned char cmd[4];
cmd[0] = 0x03; // Read Data 명령
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
SPI_CS = 0;
HAL_SPI_Transmit(cmd, 4);
HAL_SPI_Receive(pData, Size);
SPI_CS = 1;
}
// W25Q64 데이터 쓰기
void W25Q64_WriteData(unsigned long addr, unsigned char *pData, unsigned int Size) {
unsigned char cmd[4];
cmd[0] = 0x02; // Page Program 명령
cmd[1] = (addr >> 16) & 0xFF;
cmd[2] = (addr >> 8) & 0xFF;
cmd[3] = addr & 0xFF;
W25Q64_WriteEnable();
SPI_CS = 0;
HAL_SPI_Transmit(cmd, 4);
HAL_SPI_Transmit(pData, Size);
SPI_CS = 1;
}
// 지연 함수
void SPI_Delay(unsigned int delay) {
unsigned int i;
for (i = 0; i < delay; i++);
}
// 사용 예제
void main() {
SPI_InitTypeDef SPI_InitStructure;
unsigned char txData[] = {0x55, 0xAA};
unsigned char rxData[2];
unsigned char flashData[4];
// SPI 설정
SPI_InitStructure.mode = SPI_MODE_0;
SPI_InitStructure.dataOrder = SPI_DATA_ORDER_MSB;
SPI_InitStructure.clockDelay = 10;
HAL_SPI_Init(&SPI_InitStructure);
// 일반 송수신 테스트
SPI_CS = 0;
HAL_SPI_TransmitReceive(txData, rxData, 2);
SPI_CS = 1;
// W25Q64 읽기 테스트
W25Q64_ReadData(0x000000, flashData, 4);
// W25Q64 쓰기 테스트
W25Q64_WriteData(0x000000, txData, 2);
// 인터럽트 기반 송수신 테스트
SPI_CS = 0;
HAL_SPI_TransmitReceive_IT(txData, rxData, 2);
while (!g_TransferComplete); // 전송 완료 대기
SPI_CS = 1;
while(1);
}
코드 설명
- 핀 정의:
- sbit를 사용하여 AT89C51의 P1 포트를 SPI 신호선으로 지정.
- SCK, MOSI, MISO, CS를 각각 P1.0~P1.3에 매핑.
- SPI 설정 구조체:
- SPI_InitTypeDef는 STM32 HAL의 SPI_InitTypeDef를 모방하여 SPI 모드, 데이터 순서, 클럭 속도를 설정.
- 현재는 간단히 Mode 0(CPOL=0, CPHA=0)만 지원하도록 작성.
- SPI 모드 지원:
- SPI_MODE_0 ~ SPI_MODE_3 정의를 추가하여 CPOL/CPHA 조합 지원.
- HAL_SPI_Init에서 CPOL에 따라 SCK 초기 상태 설정.
- 비트뱅 루틴에서 SCK 토글을 동적으로 처리하여 모든 모드 지원.
- SPI 함수:
- HAL_SPI_Init: GPIO 핀 초기화 및 SPI 설정.
- HAL_SPI_Transmit: 데이터를 비트 단위로 MOSI를 통해 전송.
- HAL_SPI_Receive: MISO에서 데이터를 비트 단위로 수신.
- HAL_SPI_TransmitReceive: 송수신 동시 수행 (Full-Duplex).
- 각 함수는 STM32 HAL의 인터페이스(HAL_SPI_Transmit, HAL_SPI_Receive 등)를 모방.
- 비트뱅 구현:
- 클럭(SCK)을 수동으로 High/Low 전환하며 데이터 송수신.
- SPI_Delay로 클럭 주기를 조절하여 통신 속도 제어.
- 인터럽트 구현:
- 타이머 0를 사용하여 비트 단위 전송/수신을 인터럽트로 처리.
- HAL_SPI_Transmit_IT, HAL_SPI_Receive_IT, HAL_SPI_TransmitReceive_IT 함수 추가.
- 전역 변수로 전송/수신 상태 관리(g_TransferComplete 등).
- W25Q64 플래시 통신:
- W25Q64_WriteEnable: 쓰기 활성화 명령(0x06) 전송.
- W25Q64_ReadData: 주소와 함께 읽기 명령(0x03) 전송 후 데이터 수신.
- W25Q64_WriteData: 쓰기 명령(0x02)과 데이터 전송.
- STM32 HAL 스타일:
- 함수 이름과 구조체(SPI_InitTypeDef)를 HAL 스타일로 유지.
- 인터럽트 기반 함수는 STM32의 _IT 접미사를 모방.
- 타이머 설정:
- 12MHz 클럭 기준 약 10us 간격으로 타이머 0 설정.
- 인터럽트에서 비트 단위로 송수신 처리.
- 사용 방법
- 컴파일: Keil C51로 컴파일 후 AT89C51에 업로드.
- 하드웨어 연결: W25Q64 플래시 메모리와 SPI 핀 연결(SCK, MOSI, MISO, CS).
- 테스트: main 함수의 예제를 통해 폴링/인터럽트 기반 통신 및 플래시 읽기/쓰기 테스트.
- 디버깅: 플래시 동작 확인을 위해 오실로스코프 또는 로직 애널라이저 사용 권장.
- 사용 예제:
- main 함수에서 SPI 초기화 후 2바이트 데이터를 송수신.
- CS 핀을 수동으로 제어하여 슬레이브 장치를 활성화/비활성화.
STM32 HAL과의 유사성
- 함수 명명: HAL_SPI_ 접두사를 사용하여 STM32 HAL 스타일 유지.
- 구조체 기반 초기화: SPI_InitTypeDef로 설정을 관리.
- 모듈화: 송신, 수신, 송수신 함수를 분리하여 STM32 HAL의 구조와 유사하게 설계.
주의사항
- 클럭 속도: SPI_Delay의 지연 값은 AT89C51의 클럭 주파수(예: 12MHz)와 슬레이브 장치의 요구사항에 따라 조정해야 함.
- 타이밍: SPI_Delay와 타이머 값은 시스템 클럭(예: 12MHz)과 슬레이브 장치의 최대 클럭 속도(예: W25Q64는 104MHz까지 가능)에 맞춰 조정.
- 확장성: 현재는 Mode 0만 구현. CPOL/CPHA 조합을 추가하려면 HAL_SPI_Init와 비트뱅 로직을 확장.
- 인터럽트: AT89C51은 리소스가 제한적이므로 인터럽트 기반 구현은 추가 작업 필요.
- 테스트: 실제 하드웨어에서 슬레이브 장치(SPI 플래시, 센서 등)와 연결하여 테스트 권장.
- W25Q64: 쓰기 전에 섹터 지우기(Erase) 명령(0x20 등)이 필요할 수 있음. 필요 시 추가 구현 가능.
- 확장: 다른 SPI 모드나 LSB 우선 전송이 필요하면 dataOrder를 활용해 확장 가능.
'MCU > 8051' 카테고리의 다른 글
8051 SPI 비트뱅 코드 구현 : 모드 선택 가능 예제 (3) | 2025.08.22 |
---|---|
8051 I2C 비트뱅(bit-bang)를 STM32 HAL API 스타일로 코드 구현 (0) | 2025.08.02 |
8051 I2C 비트뱅(bit-bang) 코드 구현 (0) | 2025.08.02 |