1.개요
본 문서는 Microchip AVR128DA64/48/32/28 시리즈 마이크로컨트롤러의 Serial Peripheral Interface (SPI) 모듈을 활용한 범용 드라이버의 설계 및 구현을 다룹니다.드라이버는 비버퍼 모드(BUFEN=0) 기반으로 설계되어 불필요한 메모리 점유를 최소화하며, 마스터(Host)/슬레이브(Client) 모드를 모두 지원합니다. 또한, 최대 12MHz 클럭 속도(마스터 기준, CLK_PER/2), 8비트 데이터 전송, 폴링/인터럽트 기반 통신, PORTMUX를 통한 유연한 핀 매핑 기능을 제공합니다.
특히, AVR128DA28은 패키지 핀 수 제약으로 SPI0만 지원하며, SPI1 관련 레지스터 및 인터럽트가 존재하지 않음을 명확히 하였습니다.드라이버는 Microchip Studio 및 AVR-GCC 환경에서 모든 AVR128DA 시리즈에 호환되도록 구현되었으며, Write Collision(WCOL) 오류 감지 및 복구 기능도 포함되어 안정적인 SPI 통신을 보장합니다.
참고:
- 데이터시트(DS40002183E, 페이지 411~417)에 따라
INTFLAGS
레지스터의 중복 정의(WRCOL → WCOL, BUFOVF 제거)와INTCTRL
의 RXCIE 오류를 수정했습니다. - AVR128DA28은 SPI1 미지원(페이지 19, Table 22-1). SPI1_INT(0x48, 벡터 36)는 32/48/64핀 모델에서만 사용 가능.
- 드라이버는 비버퍼 모드(BUFEN=0)만 사용하며, 버퍼 모드 관련 비트(RXCIE, TXCIE, DREIE, SSIE, RXCIF, TXCIF, DREIF, SSIF, BUFOVF)는 제외했습니다.
2. 사양
2.1 SPI 모듈 사양
- 모듈 수:
- AVR128DA64/48/32: SPI0, SPI1 (2개).
- AVR128DA28: SPI0 (1개, SPI1 미지원 – 28핀 패키지의 핀 수 제약, SPI1 레지스터/인터럽트 부재).
- 핀 매핑 (데이터시트 DS40002183E, Table 22-1, 페이지 19):
- SPI0:
- DEFAULT (0x0): PA4(MOSI)/PA5(MISO)/PA6(SCK)/PA7(SS) – 모든 모델 (28/32/48/64핀).
- ALT1 (0x1): PC0(MOSI)/PC1(MISO)/PC2(SCK)/PC3(SS) – 32/48/64핀.
- ALT2 (0x2): PF4(MOSI)/PF5(MISO)/PF6(SCK)/PF7(SS) – 32/48/64핀.
- SPI1:
- DEFAULT (0x0): PB0(MOSI)/PB1(MISO)/PB2(SCK)/PB3(SS) – 48/64핀.
- ALT1 (0x1): PC4(MOSI)/PC5(MISO)/PC6(SCK)/PC7(SS) – 64핀.
- SPI0:
- 전압 범위: 1.8V ~ 5.5V (VDD).
- 기능:
- 마스터(Host)/슬레이브(Client) 모드.
- 클럭 속도: 마스터 최대 CLK_PER/2 (24MHz CLK_PER 기준 12MHz), 슬레이브 최대 CLK_PER/4.
- 데이터: 8비트 (DORD로 MSB/LSB 선택).
- 클럭 모드: MODE 0~3 (CPOL/CPHA).
- 인터럽트: SPI0 (0x24, 벡터 18), SPI1 (0x48, 벡터 36, 28핀 제외).
- 에러: WCOL, 소프트웨어로 버스 충돌 관리.
- 클럭 의존성: Peripheral Clock (CLK_PER, 최대 24MHz).
- 리셋 상태: SPI 비활성화, 핀은 GPIO 입력 모드.
- 전력 최적화: ENABLE=0으로 전력 절감.
2.2 인터럽트 벡터
데이터시트 DS40002183E (페이지 67~68):
Vector Number | Program Address (word) | Peripheral Source | Description | 28-Pin | 32-Pin | 48-Pin | 64-Pin |
---|---|---|---|---|---|---|---|
18 | 0x24 | SPI0_INT | SPI0 Interrupt | X | X | X | X |
36 | 0x48 | SPI1_INT | SPI1 Interrupt | X | X | X |
- SPI0_INT: 모든 모델 지원 (벡터 18, 0x24).
- SPI1_INT: AVR128DA28 미지원, 32/48/64핀만 지원 (벡터 36, 0x48).
2.3 핀 매핑
SPI 핀은 PORTMUX.SPIROUTEA로 설정되며, 다른 주변 장치(USART, TWI, TCA)와 공유되므로 충돌 주의:
SPI 모듈 | PORTMUX 설정 | MOSI | MISO | SCK | SS | 지원 모델 |
---|---|---|---|---|---|---|
SPI0 | DEFAULT (0x0) | PA4 | PA5 | PA6 | PA7 | 28/32/48/64핀 |
SPI0 | ALT1 (0x1) | PC0 | PC1 | PC2 | PC3 | 32/48/64핀 |
SPI0 | ALT2 (0x2) | PF4 | PF5 | PF6 | PF7 | 32/48/64핀 |
SPI1 | DEFAULT (0x0) | PB0 | PB1 | PB2 | PB3 | 48/64핀 |
SPI1 | ALT1 (0x1) | PC4 | PC5 | PC6 | PC7 | 64핀 |
- PORTMUX 설정: SPI0 (비트 0~1), SPI1 (비트 2~3). 예:
PORTMUX.SPIROUTEA |= (1 << 0)
로 SPI0 ALT1. - 풀업 저항: SS에 10kΩ 권장 (슬레이브).
- Errata: 64핀 모델에서 PORTE/PG 관련 PORTMUX 제한 가능 (DxCore 문서 참조).
3. SPI 레지스터 상세
SPI 모듈은 5개의 레지스터(CTRLA
, CTRLB
, INTCTRL
, INTFLAGS
, DATA
)로 제어됩니다. 아래는 데이터시트 DS40002183E (페이지 411~417)에 기반한 상세 설명입니다. 본 드라이버는 비버퍼 모드(BUFEN=0)를 사용하므로, 버퍼 모드 관련 비트(RXCIE, TXCIE, DREIE, SSIE, RXCIF, TXCIF, DREIF, SSIF, BUFOVF)는 제외하며, INTFLAGS
는 Normal Mode 정의만 사용합니다.
3.1 CTRLA (Control A, 오프셋 0x00)
CTRLA
는 SPI 모듈의 기본 동작을 제어하며, 활성화, 모드, 클럭 설정, 데이터 순서를 관리합니다(데이터시트 페이지 412).
- Bit 7 - Reserved: 사용되지 않으며, 항상
0
으로 유지. - Bit 6 - DORD (Data Order): 데이터 전송 순서.
0
=MSB 먼저,1
=LSB 먼저. 대부분의 SPI 장치는 MSB 우선. - Bit 5 - MASTER (Host/Client Select): SPI 모드 선택.
1
=마스터(Host),0
=슬레이브(Client). 슬레이브 모드에서 SS가 Low로 구동되면 MASTER가0
으로 클리어되고INTFLAGS.IF
가 설정됨(CTRLB.SSD로 제어). - Bit 4 - CLK2X (Clock Double): 마스터 모드에서 클럭 속도를 2배로 설정.
1
일 때 \( f_{SCK} = f_{CLK_PER}/PRESC \),0
일 때 \( f_{SCK} = f_{CLK_PER}/(2 \cdot PRESC) \). - Bit 3:2 - PRESC[1:0] (Prescaler): 마스터 모드 클럭 분주 설정. 값:
0x0
=DIV4,0x1
=DIV16,0x2
=DIV64,0x3
=DIV128. 슬레이브 모드에서는 무효. - Bit 1 - Reserved: 사용되지 않으며, 항상
0
으로 유지. - Bit 0 - ENABLE (SPI Enable): SPI 모듈 활성화.
1
=활성화(MOSI, MISO, SCK, SS가 SPI 모드로 전환),0
=비활성화(핀은 GPIO로 복귀).
사용 예제:
SPI0.CTRLA = (1 << SPI_MASTER_bp) | (1 << SPI_CLK2X_bp) | (0x0 << SPI_PRESC_gp) | (1 << SPI_ENABLE_bp);
// 마스터 모드, CLK2X=1, PRESC=4, SPI 활성화 (SCK=6MHz, CLK_PER=24MHz)
3.2 CTRLB (Control B, 오프셋 0x01)
CTRLB
는 슬레이브 선택(SS), 버퍼 모드, 클럭 모드(CPOL/CPHA)를 설정합니다(데이터시트 페이지 413). 본 드라이버는 비버퍼 모드(BUFEN=0)만 사용.
- Bit 7 - Reserved: 사용되지 않으며, 항상
0
으로 유지. - Bit 6 - BUFEN (Buffer Mode Enable): 버퍼 모드 활성화.
1
=두 개의 수신 버퍼와 하나의 송신 버퍼 활성화,0
=비버퍼 모드. 본 드라이버는0
.
- Bit 5 - BUFWR (Buffer Mode Wait for Receive): 버퍼 모드에서 첫 데이터 전송 동작.
0
=더미 샘플 전송,1
=첫 쓰기가 즉시 시프트 레지스터로 이동. 본 드라이버는 사용 안 함(BUFEN=0).
- Bit 4:3 - Reserved: 사용되지 않으며, 항상
0
으로 유지. - Bit 2 - SSD (Client Select Disable): 마스터 모드에서 SS 핀 비활성화.
1
=SS 무시,0
=SS 활성화(SS가 Low로 구동 시 MASTER 비트 클리어). - Bit 1:0 - MODE[1:0] (Mode): 클럭 모드 설정:
0x0
: Mode 0 (CPOL=0, CPHA=0, 상승 에지 샘플링, 하강 에지 설정).0x1
: Mode 1 (CPOL=0, CPHA=1, 하강 에지 샘플링, 상승 에지 설정).0x2
: Mode 2 (CPOL=1, CPHA=0, 하강 에지 샘플링, 상승 에지 설정).0x3
: Mode 3 (CPOL=1, CPHA=1, 상승 에지 샘플링, 하강 에지 설정).
사용 예제:
SPI0.CTRLB = (1 << SPI_SSD_bp) | (0x0 << SPI_MODE_gp);
// 슬레이브 선택 비활성화, Mode 0 (CPOL=0, CPHA=0)
3.3 INTCTRL (Interrupt Control, 오프셋 0x02)
INTCTRL
은 인터럽트를 제어하며, 비버퍼 모드에서는 IE
만 사용합니다(데이터시트 페이지 414).
- Bit 7 - RXCIE (Receive Complete Interrupt Enable): 버퍼 모드에서 수신 완료 인터럽트 활성화. 본 드라이버는 비버퍼 모드(
0
). - Bit 6 - TXCIE (Transfer Complete Interrupt Enable): 버퍼 모드에서 전송 완료 인터럽트 활성화. 본 드라이버는 비버퍼 모드(
0
). - Bit 5 - DREIE (Data Register Empty Interrupt Enable): 버퍼 모드에서 데이터 레지스터 비움 인터럽트 활성화. 본 드라이버는 비버퍼 모드(
0
). - Bit 4 - SSIE (Client Select Trigger Interrupt Enable): 버퍼 모드에서 SS 상태 변화 인터럽트 활성화. 본 드라이버는 비버퍼 모드(
0
). - Bit 3:1 - Reserved: 사용되지 않으며, 항상
0
으로 유지. - Bit 0 - IE (Interrupt Enable): 비버퍼 모드에서 SPI 인터럽트 활성화.
INTFLAGS.IF
설정 시 SPI0_INT(0x24) 또는 SPI1_INT(0x48, 28핀 제외) 트리거.
사용 예제:
SPI0.INTCTRL = (1 << SPI_IE_bp);
// SPI 전체 인터럽트 활성화 (비버퍼 모드)
3.4 INTFLAGS (Interrupt Flags, 오프셋 0x03, Normal Mode)
INTFLAGS
는 비버퍼 모드에서 인터럽트 플래그와 에러 상태를 제공합니다(데이터시트 페이지 415). 본 드라이버는 비버퍼 모드만 사용하므로 Normal Mode 정의 적용.
- Bit 7 - IF (Interrupt Flag): 전송/수신 완료 플래그.
DATA
레지스터로 1바이트 송수신 완료 시1
. 마스터 모드에서 SS가 Low로 구동 시에도 설정됨(SSD=0).1
쓰기 또는INTFLAGS.IF
읽기 후DATA
접근으로 클리어. - Bit 6 - WCOL (Write Collision): 쓰기 충돌 에러.
DATA
레지스터에 이전 데이터가 처리 중일 때 쓰기 시1
.INTFLAGS.WCOL
읽기 후DATA
접근으로 클리어. - Bit 5:0 - Reserved: 사용되지 않으며, 항상
0
으로 유지.
사용 예제:
if (SPI0.INTFLAGS & SPI_WCOL_bm) {
SPI0.INTFLAGS |= SPI_WCOL_bm; // WCOL 클리어
}
3.5 DATA (Data Register, 오프셋 0x04)
DATA
는 8비트 송수신 레지스터입니다(데이터시트 페이지 417). 비버퍼 모드에서 쓰기는 시프트 레지스터로, 읽기는 수신 레지스터에서 수행됩니다.
- Bit 7:0 - DATA[7:0]: 송수신 데이터. 쓰기 시 마스터는 SCK 클럭 생성, 슬레이브는 준비 데이터 저장. 읽기 시 수신 데이터 반환.
CTRLA.DORD
로 MSB/LSB 순서 결정.
사용 예제:
SPI0.DATA = 0xAA; // 0xAA 전송
while (!(SPI0.INTFLAGS & SPI_IF_bm)); // 전송 완료 대기
uint8_t rx = SPI0.DATA; // 수신 데이터 읽기
레지스터 수정 사항:
INTFLAGS
중복 정의 제거: Normal Mode(IF
,WCOL
)만 사용, Buffer Mode(RXCIF
,TXCIF
,DREIF
,SSIF
,BUFOVF
) 제외.WRCOL
→WCOL
로 수정(페이지 415).INTCTRL.RXCIE
는 버퍼 모드 전용이므로 비버퍼 모드 드라이버에서 제외.
4. 드라이버 설계
4.1 설계 목표
- 호환성: AVR128DA28 (SPI0), AVR128DA32/48/64 (SPI0, SPI1).
- 간소화: 링버퍼 없이 비버퍼 모드(BUFEN=0)로 최소 메모리 사용.
- 유연성: 마스터/슬레이브, 폴링/인터럽트, 최대 12MHz.
- 핀 매핑: PORTMUX로 DEFAULT, ALT1, ALT2.
- 에러 처리: WCOL 감지 및 클리어.
- 테스트 용이성: Microchip Studio 시뮬레이터 및 실제 SPI 장치로 검증.
4.2 주요 기능
spi_init
: 초기화 (포트, 클럭, 모드, PORTMUX).spi_transceive
: 단일 바이트 송수신 (폴링).spi_transceive_block
: 다중 바이트 송수신 (폴링).spi_transceive_it
: 인터럽트 기반 송수신.spi_check_errors
: WCOL 확인 및 클리어.spi_enable/disable
: SPI 활성화/비활성화.- 인터럽트: SPI0_INT_vect (0x24), SPI1_INT_vect (0x48, 28핀 제외).
4.3 설정 절차
- CLKCTRL로 CLK_PER 활성화 (24MHz OSCHF).
- PORTMUX로 MOSI/MISO/SCK/SS 설정, SS 풀업 (슬레이브).
- CTRLA/CTRLB로 마스터/슬레이브, 클럭 모드 설정.
- CTRLA.PRESC/CLK2X로 클럭 설정.
- INTCTRL.IE로 인터럽트 활성화.
- CTRLA.ENABLE로 SPI 활성화.
- PORTMUX로 주변 장치 충돌 확인.
- INTFLAGS.WCOL로 에러 처리.
5. 드라이버 구현
spi_driver.h
/**
* @file spi_driver.h
* @brief AVR128DA64/48/32/28 SPI 드라이버 헤더 파일
* @details 비버퍼 모드(BUFEN=0)로 링버퍼 없이 마스터/슬레이브 통신.
* SPI0/SPI1 지원 (28핀은 SPI0만, SPI1 미지원).
* 데이터시트 DS40002183E (페이지 411~417) 기반.
* @author linuxgo
* @date 2025-09-03
*/
#ifndef SPI_DRIVER_H
#define SPI_DRIVER_H
#include <avr/io.h>
#include <stdint.h>
/**
* @brief SPI 클럭 속도 정의
* @details 데이터시트 DS40002183E (페이지 412):
* f_SCK = f_CLK_PER / (2 * PRESC) (CLK2X=0), f_SCK = f_CLK_PER / PRESC (CLK2X=1).
* 예: F_CPU=24MHz, CLK2X=1, PRESC=4 → f_SCK=6MHz.
*/
#define SPI_BAUD_125KHZ (F_CPU / 128) // 125kHz (PRESC=128, CLK2X=0)
#define SPI_BAUD_500KHZ (F_CPU / 32) // 500kHz (PRESC=16, CLK2X=0)
#define SPI_BAUD_4MHZ (F_CPU / 4) // 4MHz (PRESC=4, CLK2X=1)
#define SPI_BAUD_12MHZ (F_CPU / 2) // 12MHz (PRESC=2, CLK2X=1)
/**
* @brief SPI 모드 정의
* @details 마스터(Host, 0) 또는 슬레이브(Client, 1) 모드 (CTRLA.MASTER).
*/
#define SPI_MODE_MASTER 0x0
#define SPI_MODE_SLAVE 0x1
/**
* @brief SPI 클럭 모드 정의
* @details CTRLB.MODE[1:0]로 CPOL/CPHA 설정 (페이지 413).
* Mode 0: CPOL=0, CPHA=0 (샘플링: 상승, 설정: 하강).
* Mode 1: CPOL=0, CPHA=1 (샘플링: 하강, 설정: 상승).
* Mode 2: CPOL=1, CPHA=0 (샘플링: 하강, 설정: 상승).
* Mode 3: CPOL=1, CPHA=1 (샘플링: 상승, 설정: 하강).
*/
#define SPI_CLK_MODE_0 0x0
#define SPI_CLK_MODE_1 0x1
#define SPI_CLK_MODE_2 0x2
#define SPI_CLK_MODE_3 0x3
/**
* @brief PORTMUX 설정 정의
* @details 데이터시트 Table 22-1 (페이지 19):
* SPI0: DEFAULT(PA4/PA5/PA6/PA7), ALT1(PC0/PC1/PC2/PC3), ALT2(PF4/PF5/PF6/PF7).
* SPI1: DEFAULT(PB0/PB1/PB2/PB3), ALT1(PC4/PC5/PC6/PC7).
*/
#define SPI_PORTMUX_DEFAULT 0x0
#define SPI_PORTMUX_ALT1 0x1
#define SPI_PORTMUX_ALT2 0x2
/**
* @brief SPI 에러 플래그 정의
* @details INTFLAGS.WCOL: 쓰기 충돌 에러 (페이지 415).
*/
#define SPI_ERROR_WCOL SPI_WCOL_bm
/**
* @brief SPI 인스턴스 구조체
* @details SPI 모듈 포인터, 모드, 전송 상태를 저장.
*/
typedef struct {
SPI_t *spi; // SPI 모듈 포인터 (SPI0 또는 SPI1)
uint8_t mode; // 모드 (마스터=0, 슬레이브=1)
volatile uint8_t tx_busy; // 전송 진행 상태 (1=바쁨)
} SPI_Instance;
/**
* @brief SPI 모듈 초기화
* @param instance SPI 인스턴스 구조체
* @param spi SPI 모듈 (SPI0 또는 SPI1)
* @param baud 통신 속도 (125kHz, 500kHz, 4MHz, 12MHz)
* @param mode 마스터(0) 또는 슬레이브(1)
* @param clk_mode 클럭 모드 (0~3)
* @param portmux PORTMUX 설정 (DEFAULT, ALT1, ALT2)
*/
void spi_init(SPI_Instance *instance, SPI_t *spi, uint32_t baud, uint8_t mode, uint8_t clk_mode, uint8_t portmux);
/**
* @brief SPI 모듈 활성화
* @param instance SPI 인스턴스 구조체
*/
void spi_enable(SPI_Instance *instance);
/**
* @brief SPI 모듈 비활성화
* @param instance SPI 인스턴스 구조체
*/
void spi_disable(SPI_Instance *instance);
/**
* @brief 단일 바이트 송수신 (폴링)
* @param instance SPI 인스턴스 구조체
* @param data 송신 데이터
* @return 수신 데이터
*/
uint8_t spi_transceive(SPI_Instance *instance, uint8_t data);
/**
* @brief 다중 바이트 송수신 (폴링)
* @param instance SPI 인스턴스 구조체
* @param tx_data 송신 데이터 버퍼
* @param rx_data 수신 데이터 버퍼
* @param length 데이터 길이
* @return 성공 시 0
*/
uint8_t spi_transceive_block(SPI_Instance *instance, uint8_t *tx_data, uint8_t *rx_data, uint8_t length);
/**
* @brief 인터럽트 기반 송수신 초기화
* @param instance SPI 인스턴스 구조체
*/
void spi_transceive_it_init(SPI_Instance *instance);
/**
* @brief 인터럽트 기반 데이터 송수신
* @param instance SPI 인스턴스 구조체
* @param tx_data 송신 데이터 버퍼
* @param rx_data 수신 데이터 버퍼
* @param length 데이터 길이
* @return 성공 시 0, 진행 중 시 1
*/
uint8_t spi_transceive_it(SPI_Instance *instance, uint8_t *tx_data, uint8_t *rx_data, uint8_t length);
/**
* @brief SPI 에러 상태 확인 및 클리어
* @param instance SPI 인스턴스 구조체
* @return 에러 플래그 (WCOL)
*/
uint8_t spi_check_errors(SPI_Instance *instance);
#endif // SPI_DRIVER_H
spi_driver.c
/**
* @file spi_driver.c
* @brief AVR128DA64/48/32/28 SPI 드라이버 구현
* @details 비버퍼 모드(BUFEN=0)로 링버퍼 없이 마스터/슬레이브 통신.
* SPI0/SPI1 지원 (28핀은 SPI0만, SPI1 미지원).
* 인터럽트 벡터: SPI0_INT(0x24), SPI1_INT(0x48, 28핀 제외).
* 데이터시트 DS40002183E (페이지 411~417) 기반.
* @author linuxgo
* @date 2025-09-03
*/
#include "spi_driver.h"
#include <avr/interrupt.h>
/**
* @brief SPI 인스턴스 배열
* @details SPI0(index 0), SPI1(index 1)을 저장. AVR128DA28은 SPI1 미지원.
*/
static SPI_Instance *spi_instances[2] = {NULL};
/**
* @brief 송수신 버퍼 및 인덱스 (인터럽트용)
* @details 최대 64바이트 송수신 버퍼, 인덱스로 진행 상황 추적.
* 비버퍼 모드이지만 인터럽트 처리용 임시 버퍼.
*/
static uint8_t tx_data[64], rx_data[64];
static uint8_t tx_length, rx_length, tx_index, rx_index;
/**
* @brief SPI 클럭 설정 계산
* @details 데이터시트 DS40002183E (페이지 412):
* - CLK2X=0: f_SCK = f_CLK_PER / (2 * PRESC)
* - CLK2X=1: f_SCK = f_CLK_PER / PRESC
* - PRESC: 4, 16, 64, 128.
* - 목표 baud에 가장 가까운 PRESC와 CLK2X 선택.
* @param baud 목표 SPI 클럭 속도
* @param presc 계산된 프리스케일러 값 (포인터)
* @param clk2x CLK2X 비트 설정 (0 또는 1, 포인터)
*/
static void calculate_baud(uint32_t baud, uint8_t *presc, uint8_t *clk2x) {
uint32_t f_per = F_CPU; // Peripheral Clock 주파수 (기본적으로 F_CPU)
uint8_t presc_values[] = {4, 16, 64, 128}; // 가능한 프리스케일러 값
uint8_t best_presc = 0; // 최적 프리스케일러
uint8_t use_clk2x = 0; // CLK2X 사용 여부
uint32_t best_diff = UINT32_MAX; // 최소 차이 추적
// CLK2X=0일 때 최적 PRESC 계산
for (uint8_t i = 0; i < 4; i++) {
uint32_t f_sck = f_per / (2 * presc_values[i]); // f_SCK 계산
uint32_t diff = (f_sck > baud) ? (f_sck - baud) : (baud - f_sck); // 목표와의 차이
if (diff < best_diff) {
best_diff = diff;
best_presc = i;
use_clk2x = 0;
}
}
// CLK2X=1일 때 최적 PRESC 계산 (최대 f_CLK_PER/2 제한)
for (uint8_t i = 0; i < 4; i++) {
uint32_t f_sck = f_per / presc_values[i];
if (f_sck <= f_per / 2) { // 마스터 모드 최대 클럭 제한
uint32_t diff = (f_sck > baud) ? (f_sck - baud) : (baud - f_sck);
if (diff < best_diff) {
best_diff = diff;
best_presc = i;
use_clk2x = 1;
}
}
}
*presc = best_presc; // 계산된 프리스케일러 반환
*clk2x = use_clk2x; // CLK2X 설정 반환
}
/**
* @brief SPI 인스턴스 등록
* @details SPI0 또는 SPI1 인스턴스를 배열에 등록.
* @param instance SPI 인스턴스 구조체
* @param spi SPI 모듈 (SPI0 또는 SPI1)
*/
static void register_instance(SPI_Instance *instance, SPI_t *spi) {
if (spi == &SPI0) {
spi_instances[0] = instance; // SPI0 인스턴스 등록
}
#if defined(SPI1)
else if (spi == &SPI1) {
spi_instances[1] = instance; // SPI1 인스턴스 등록 (28핀 제외)
}
#endif
}
/**
* @brief SPI 모듈 초기화
* @details PORTMUX로 핀 설정, 클럭 계산, 마스터/슬레이브 모드 설정.
* AVR128DA28은 SPI1 미지원 (핀, 레지스터, 인터럽트 부재).
* 데이터시트 Table 22-1 (페이지 19) 기반으로 핀 설정.
* @param instance SPI 인스턴스 구조체
* @param spi SPI 모듈 (SPI0 또는 SPI1)
* @param baud 통신 속도
* @param mode 마스터(0) 또는 슬레이브(1)
* @param clk_mode 클럭 모드 (0~3)
* @param portmux PORTMUX 설정 (DEFAULT, ALT1, ALT2)
*/
void spi_init(SPI_Instance *instance, SPI_t *spi, uint32_t baud, uint8_t mode, uint8_t clk_mode, uint8_t portmux) {
// AVR128DA28은 SPI1 미지원
#if defined(__AVR_AVR128DA28__)
if (spi == &SPI1) return; // SPI1 초기화 방지
#endif
// PORTMUX 및 핀 설정 (데이터시트 페이지 19)
if (spi == &SPI0) {
// SPI0 PORTMUX 설정 (비트 0~1)
PORTMUX.SPIROUTEA = (PORTMUX.SPIROUTEA & ~(0x3 << 0)) | (portmux << 0);
if (portmux == SPI_PORTMUX_DEFAULT) {
// PA4(MOSI), PA6(SCK): 출력, PA5(MISO): 입력, PA7(SS): 풀업
PORTA.DIRSET = (1 << 4) | (1 << 6); // MOSI, SCK 출력
PORTA.DIRCLR = (1 << 5); // MISO 입력
PORTA.PIN7CTRL |= PORT_PULLUPEN_bm; // SS 풀업 (슬레이브 모드)
} else if (portmux == SPI_PORTMUX_ALT1) {
// PC0(MOSI), PC2(SCK): 출력, PC1(MISO): 입력, PC3(SS): 풀업
#if !defined(__AVR_AVR128DA28__)
PORTC.DIRSET = (1 << 0) | (1 << 2);
PORTC.DIRCLR = (1 << 1);
PORTC.PIN3CTRL |= PORT_PULLUPEN_bm;
#endif
} else if (portmux == SPI_PORTMUX_ALT2) {
// PF4(MOSI), PF6(SCK): 출력, PF5(MISO): 입력, PF7(SS): 풀업
#if defined(__AVR_AVR128DA32__) || defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
PORTF.DIRSET = (1 << 4) | (1 << 6);
PORTF.DIRCLR = (1 << 5);
PORTF.PIN7CTRL |= PORT_PULLUPEN_bm;
#endif
}
}
#if defined(SPI1)
else if (spi == &SPI1) {
// SPI1 PORTMUX 설정 (비트 2~3)
PORTMUX.SPIROUTEA = (PORTMUX.SPIROUTEA & ~(0x3 << 2)) | (portmux << 2);
if (portmux == SPI_PORTMUX_DEFAULT) {
// PB0(MOSI), PB2(SCK): 출력, PB1(MISO): 입력, PB3(SS): 풀업
#if defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
PORTB.DIRSET = (1 << 0) | (1 << 2);
PORTB.DIRCLR = (1 << 1);
PORTB.PIN3CTRL |= PORT_PULLUPEN_bm;
#endif
} else if (portmux == SPI_PORTMUX_ALT1) {
// PC4(MOSI), PC6(SCK): 출력, PC5(MISO): 입력, PC7(SS): 풀업
#if defined(__AVR_AVR128DA64__)
PORTC.DIRSET = (1 << 4) | (1 << 6);
PORTC.DIRCLR = (1 << 5);
PORTC.PIN7CTRL |= PORT_PULLUPEN_bm;
#endif
}
}
#endif
// 클럭 설정 계산
uint8_t presc, clk2x;
calculate_baud(baud, &presc, &clk2x);
// CTRLA 설정: 모드, 클럭, 활성화 (페이지 412)
spi->CTRLA = (mode << SPI_MASTER_bp) | (clk2x << SPI_CLK2X_bp) | (presc << SPI_PRESC_gp) | SPI_ENABLE_bm;
// CTRLB 설정: 클럭 모드, 슬레이브 선택 비활성화 (페이지 413)
spi->CTRLB = (clk_mode << SPI_MODE_gp) | SPI_SSD_bm;
// 인스턴스 초기화
instance->spi = spi; // SPI 모듈 설정
instance->mode = mode; // 모드 저장
instance->tx_busy = 0; // 전송 상태 초기화
register_instance(instance, spi); // 인스턴스 등록
}
/**
* @brief SPI 모듈 활성화
* @details CTRLA.ENABLE=1로 SPI 활성화 (페이지 412).
* @param instance SPI 인스턴스 구조체
*/
void spi_enable(SPI_Instance *instance) {
instance->spi->CTRLA |= SPI_ENABLE_bm; // SPI 모듈 활성화
}
/**
* @brief SPI 모듈 비활성화
* @details CTRLA.ENABLE=0으로 SPI 비활성화, 전력 절감.
* @param instance SPI 인스턴스 구조체
*/
void spi_disable(SPI_Instance *instance) {
instance->spi->CTRLA &= ~SPI_ENABLE_bm; // SPI 모듈 비활성화
}
/**
* @brief 단일 바이트 송수신 (폴링)
* @details DATA 레지스터에 데이터 쓰기 후 INTFLAGS.IF 대기, 수신 데이터 반환 (페이지 417).
* @param instance SPI 인스턴스 구조체
* @param data 송신 데이터
* @return 수신 데이터
*/
uint8_t spi_transceive(SPI_Instance *instance, uint8_t data) {
instance->spi->DATA = data; // 송신 데이터 쓰기
while (!(instance->spi->INTFLAGS & SPI_IF_bm)); // 전송/수신 완료 대기
return instance->spi->DATA; // 수신 데이터 읽기
}
/**
* @brief 다중 바이트 송수신 (폴링)
* @details 각 바이트를 spi_transceive로 처리, tx_data 송신, rx_data에 저장.
* @param instance SPI 인스턴스 구조체
* @param tx_data 송신 데이터 버퍼
* @param rx_data 수신 데이터 버퍼
* @param length 데이터 길이
* @return 성공 시 0
*/
uint8_t spi_transceive_block(SPI_Instance *instance, uint8_t *tx_data, uint8_t *rx_data, uint8_t length) {
for (uint8_t i = 0; i < length; i++) {
rx_data[i] = spi_transceive(instance, tx_data[i]); // 단일 바이트 송수신
}
return 0; // 성공
}
/**
* @brief 인터럽트 기반 송수신 초기화
* @details INTCTRL.IE=1로 인터럽트 활성화 (페이지 414).
* @param instance SPI 인스턴스 구조체
*/
void spi_transceive_it_init(SPI_Instance *instance) {
instance->spi->INTCTRL |= SPI_IE_bm; // SPI 인터럽트 활성화
}
/**
* @brief 인터럽트 기반 데이터 송수신
* @details 송수신 버퍼 복사 후 첫 데이터 전송 시작, 나머지는 인터럽트로 처리.
* @param instance SPI 인스턴스 구조체
* @param tx_data 송신 데이터 버퍼
* @param rx_data 수신 데이터 버퍼
* @param length 데이터 길이
* @return 성공 시 0, 진행 중 시 1
*/
uint8_t spi_transceive_it(SPI_Instance *instance, uint8_t *tx_data, uint8_t *rx_data, uint8_t length) {
if (instance->tx_busy) return 1; // 이미 전송 중이면 실패
instance->tx_busy = 1; // 전송 시작 플래그
tx_length = length; // 송신 데이터 길이 저장
rx_length = length; // 수신 데이터 길이 저장
tx_index = 0; // 송신 인덱스 초기화
rx_index = 0; // 수신 인덱스 초기화
for (uint8_t i = 0; i < length; i++) {
tx_data[i] = tx_data[i]; // 송신 버퍼 복사
}
instance->spi->DATA = tx_data[tx_index++]; // 첫 데이터 전송 시작
return 0; // 성공
}
/**
* @brief SPI 에러 상태 확인 및 클리어
* @details INTFLAGS.WCOL 확인, 에러 시 클리어 (페이지 415).
* @param instance SPI 인스턴스 구조체
* @return 에러 플래그 (WCOL)
*/
uint8_t spi_check_errors(SPI_Instance *instance) {
uint8_t errors = instance->spi->INTFLAGS & SPI_WCOL_bm; // WCOL 플래그 읽기
if (errors & SPI_WCOL_bm) {
instance->spi->INTFLAGS |= SPI_WCOL_bm; // WCOL 클리어
}
return errors; // 에러 상태 반환
}
/**
* @brief SPI 인터럽트 핸들러
* @details INTFLAGS.IF 확인 후 데이터 송수신 처리, 완료 시 인터럽트 비활성화.
* @param instance SPI 인스턴스 구조체
*/
static void spi_handler(SPI_Instance *instance) {
if (tx_index < tx_length) {
rx_data[rx_index++] = instance->spi->DATA; // 수신 데이터 저장
instance->spi->DATA = tx_data[tx_index++]; // 다음 데이터 전송
} else {
rx_data[rx_index] = instance->spi->DATA; // 마지막 수신 데이터
instance->tx_busy = 0; // 전송 완료
instance->spi->INTCTRL &= ~SPI_IE_bm; // 인터럽트 비활성화
}
}
/**
* @brief SPI0 인터럽트 핸들러 (벡터 0x24)
* @details 데이터시트 페이지 67, SPI0_INT 처리.
*/
ISR(SPI0_INT_vect, ISR_NAKED) {
if (spi_instances[0]) {
spi_handler(spi_instances[0]); // SPI0 핸들러 호출
}
reti(); // 인터럽트 복귀
}
/**
* @brief SPI1 인터럽트 핸들러 (벡터 0x48)
* @details 데이터시트 페이지 68, SPI1_INT 처리 (28핀 제외).
*/
#if defined(SPI1) && !defined(__AVR_AVR128DA28__)
ISR(SPI1_INT_vect, ISR_NAKED) {
if (spi_instances[1]) {
spi_handler(spi_instances[1]); // SPI1 핸들러 호출
}
reti(); // 인터럽트 복귀
}
#endif
main.c
/**
* @file main.c
* @brief AVR128DA64/48/32/28 SPI 드라이버 테스트 프로그램
* @details 24MHz 클럭, SPI0(마스터, PC0/PC1/PC2/PC3), SPI1(슬레이브, PB0/PB1/PB2/PB3) 테스트.
* UART0로 디버깅 출력 (9600 baud, PA0/TX, PA1/RX).
* 데이터시트 DS40002183E (페이지 19, 67~68, 411~417) 기반.
* @author linuxgo
* @date 2025-09-03
*/
#ifndef F_CPU
#define F_CPU 24000000UL // 24MHz 클럭 정의
#endif
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include "spi_driver.h"
/**
* @brief 간단한 UART 드라이버 헤더 (실제 구현은 간소화 가정)
* @details UART0 (PA0:TX, PA1:RX)로 디버깅 출력.
*/
void uart_init(uint32_t baud);
void uart_transmit(uint8_t data);
int uart_printf(char *format, ...);
/**
* @brief 24MHz OSCHF 클럭 초기화
* @details CLKCTRL 설정으로 Auto-tune 활성화, 주 클럭으로 OSCHF 선택.
* 데이터시트 DS40002183E (페이지 65~66).
*/
void clock_init_24mhz(void) {
_PROTECTED_WRITE(CLKCTRL.XOSCHFCTRLA, CLKCTRL_ENABLE_bm | (0 << CLKCTRL_SEL_bp)); // OSCHF 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHCTRLA, (0x9 << CLKCTRL_FRQSEL_gp) | CLKCTRL_AUTOTUNE_bm); // 24MHz, Auto-tune
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_OSCHFS_bm)); // OSCHF 안정화 대기
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // 주 클럭을 OSCHF로 설정
}
/**
* @brief UART 초기화 (PA0: TX, PA1: RX, 9600 baud)
* @details UBRR 계산, 송신/수신 활성화, 핀 설정.
*/
void uart_init(uint32_t baud) {
uint16_t ubrr = (F_CPU / (16UL * baud)) - 1; // UBRR 계산 (데이터시트 페이지 341)
USART0.BAUD = ubrr; // 보드레이트 설정
USART0.CTRLB = USART_TXEN_bm | USART_RXEN_bm; // 송신/수신 활성화
PORTA.DIRSET = (1 << 0); // PA0(TX) 출력
PORTA.DIRCLR = (1 << 1); // PA1(RX) 입력
}
/**
* @brief UART 단일 바이트 송신
* @details 데이터 레지스터 비어질 때까지 대기 후 전송.
*/
void uart_transmit(uint8_t data) {
while (!(USART0.STATUS & USART_DREIF_bm)); // 데이터 레지스터 비어짐 대기
USART0.TXDATAL = data; // 데이터 전송
}
/**
* @brief UART printf 구현 (간소화)
* @details vsnprintf로 문자열 생성 후 UART로 전송.
*/
int uart_printf(char *format, ...) {
char buffer[128]; // 출력 버퍼
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args); // 문자열 포맷팅
va_end(args);
for (uint8_t i = 0; buffer[i]; i++) {
uart_transmit(buffer[i]); // 각 문자 전송
}
return 0;
}
/**
* @brief 메인 함수
* @details SPI0(마스터, 4MHz, PC0/PC1/PC2/PC3), SPI1(슬레이브, 4MHz, PB0/PB1/PB2/PB3) 테스트.
* 폴링 및 인터럽트 테스트 후 UART로 결과 출력.
*/
int main(void) {
clock_init_24mhz(); // 24MHz 클럭 설정
sei(); // 글로벌 인터럽트 활성화
uart_init(9600); // UART 초기화 (9600 baud)
uart_printf("SPI Driver Test Start\r\n"); // 테스트 시작 메시지
// SPI 인스턴스 생성
SPI_Instance spi0_instance;
#if defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
SPI_Instance spi1_instance; // SPI1 인스턴스 (48/64핀만)
#endif
// SPI0: 마스터, 4MHz, ALT1 (PC0/MOSI, PC1/MISO, PC2/SCK, PC3/SS)
spi_init(&spi0_instance, &SPI0, SPI_BAUD_4MHZ, SPI_MODE_MASTER, SPI_CLK_MODE_0, SPI_PORTMUX_ALT1);
spi_enable(&spi0_instance); // SPI0 활성화
// SPI1: 슬레이브, 4MHz, DEFAULT (PB0/MOSI, PB1/MISO, PB2/SCK, PB3/SS)
#if defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
spi_init(&spi1_instance, &SPI1, SPI_BAUD_4MHZ, SPI_MODE_SLAVE, SPI_CLK_MODE_0, SPI_PORTMUX_DEFAULT);
spi_enable(&spi1_instance); // SPI1 활성화
#endif
// 테스트 데이터
uint8_t tx_data[] = {0xAA, 0xBB, 0xCC}; // 송신 데이터
uint8_t rx_data[3] = {0}; // 수신 데이터 버퍼
// 폴링 기반 송수신 테스트
uart_printf("Polling Test: Sending %d bytes\r\n", sizeof(tx_data));
spi_transceive_block(&spi0_instance, tx_data, rx_data, sizeof(tx_data)); // 다중 바이트 송수신
#if defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
uart_printf("Received %d bytes: 0x%02X, 0x%02X, 0x%02X\r\n", sizeof(rx_data), rx_data[0], rx_data[1], rx_data[2]);
#endif
// 에러 확인
uint8_t errors = spi_check_errors(&spi0_instance); // WCOL 에러 확인
if (errors) {
uart_printf("Errors: %s\r\n", (errors & SPI_ERROR_WCOL) ? "Write Collision" : "");
}
// 인터럽트 기반 송수신 테스트
#if defined(__AVR_AVR128DA48__) || defined(__AVR_AVR128DA64__)
uart_printf("Interrupt Test: Sending %d bytes\r\n", sizeof(tx_data));
spi_transceive_it_init(&spi0_instance); // SPI0 인터럽트 초기화
spi_transceive_it_init(&spi1_instance); // SPI1 인터럽트 초기화
spi_transceive_it(&spi0_instance, tx_data, rx_data, sizeof(tx_data)); // 인터럽트 송수신 시작
_delay_ms(100); // 인터럽트 처리 대기
uart_printf("Received %d bytes: 0x%02X, 0x%02X, 0x%02X\r\n", sizeof(rx_data), rx_data[0], rx_data[1], rx_data[2]);
#endif
// 메인 루프
while (1) {
uart_printf("Test Complete, Looping...\r\n"); // 테스트 완료 메시지
_delay_ms(1000); // 1초 대기
}
return 0;
}
6. 사용 방법
- 헤더 파일 포함:
#include "spi_driver.h" #include <avr/io.h> #include <util/delay.h>
- 클럭 설정:
clock_init_24mhz(); sei();
- SPI 인스턴스 초기화:
SPI_Instance spi0_instance; spi_init(&spi0_instance, &SPI0, SPI_BAUD_4MHZ, SPI_MODE_MASTER, SPI_CLK_MODE_0, SPI_PORTMUX_ALT1); spi_enable(&spi0_instance);
- 데이터 송수신:
- 폴링:
uint8_t tx_data[] = {0xAA, 0xBB}; uint8_t rx_data[2]; spi_transceive_block(&spi0_instance, tx_data, rx_data, sizeof(tx_data));
- 인터럽트:
spi_transceive_it_init(&spi0_instance); spi_transceive_it(&spi0_instance, tx_data, rx_data, sizeof(tx_data));
- 폴링:
- 에러 처리:
uint8_t errors = spi_check_errors(&spi0_instance); if (errors) { printf("Errors: %s\r\n", (errors & SPI_ERROR_WCOL) ? "Write Collision" : ""); }
7. 고려사항
- 클럭 설정: 데이터시트 DS40002183E (페이지 412): \( f_{SCK} = \frac{f_{CLK_PER}}{2 \cdot PRESC} \) (CLK2X=0), \( f_{SCK} = \frac{f_{CLK_PER}}{PRESC} \) (CLK2X=1). 예: 24MHz CLK_PER, PRESC=4, CLK2X=1 → 6MHz SCK.
- 인터럽트 우선순위: SPI0_INT (0x24) > SPI1_INT (0x48). CPUINT로 조정 가능.
- 핀 충돌: SPI 핀은 TWI, USART 등과 공유. PORTMUX 확인.
- 풀업 저항: SS 핀에 10kΩ 권장 (슬레이브).
- Errata: 64핀 모델에서 PORTE/PG 관련 PORTMUX 제한 가능 (DxCore 문서 참조).
- 디버깅:
spi_check_errors
로 WCOL 확인. 오실로스코프로 SCK/MOSI/MISO 검증. - 테스트: Microchip Studio 시뮬레이터 또는 실제 SPI 장치(EEPROM, 디스플레이)로 검증.
8. 결론
본 드라이버는 단순성, 호환성, 안정성을 중점으로 설계되어 AVR128DA 시리즈에서 SPI를 활용한 다양한 애플리케이션 개발에 즉시 적용할 수 있습니다.
- 단순성: 링버퍼 없이 비버퍼 모드 기반 구현으로 코드 크기와 자원 사용 최소화.
- 호환성: 28핀부터 64핀까지 모든 AVR128DA 시리즈 지원.
- 안정성: 인터럽트/폴링 기반 송수신, 에러 감지(WCOL) 기능 제공.
- 유연성: PORTMUX 기반 핀 매핑 및 다양한 클럭/모드 설정 가능.
따라서 본 드라이버는 센서 데이터 수집, 외부 메모리 접근, 디스플레이 제어, MCU 간 통신 등 SPI 기반 시스템 전반에 활용할 수 있으며, 실제 하드웨어 검증 및 시뮬레이터 테스트를 통해 안정성을 확보하였습니다.
'MCU > AVR' 카테고리의 다른 글
AVR128DA64/48/32/28 TCB 드라이버 설계 및 구현 (0) | 2025.09.04 |
---|---|
AVR128DA64/48/32/28 AC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 DAC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 ADC 드라이버 설계 및 구현 (0) | 2025.09.04 |
AVR128DA64/48/32/28 I2C (TWI) 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DA64/48/32/28 UART 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DA64/48/32/28 GPIO 드라이버 설계 및 구현 (0) | 2025.09.03 |
AVR128DB48 클럭 설정 상세 가이드 (0) | 2025.08.20 |