1. AVR128DB48 SPI 모듈 개요
Microchip의 AVR128DB48 마이크로컨트롤러는 두 개의 SPI(Serial Peripheral Interface) 모듈(SPI0, SPI1)을 제공하여 고속 직렬 통신에 적합합니다. SPI 모듈은 마스터 및 슬레이브 모드를 지원하며, 센서, 플래시 메모리, 디스플레이 등의 장치와 통신에 사용됩니다. 이 문서에서는 AVR128DB48의 SPI0 및 SPI1 설정 방법, Bitfield 구조를 활용한 레지스터 설정, 그리고 실용적인 예제 코드를 제공하여 초보자와 숙련된 개발자 모두 쉽게 활용할 수 있도록 돕습니다.
주요 사양
- SPI 채널:
- SPI0: MOSI (PA4), MISO (PA5), SCK (PA6), SS (PA7)
- SPI1: MOSI (PC4), MISO (PC5), SCK (PC6), SS (PC7)
- 지원 모드: 마스터, 슬레이브
- 클럭 주파수: 최대 F_CPU/2 (마스터 모드)
- 주요 레지스터 (SPI0 및 SPI1 공통):
- SPIx.CTRLA: 제어 레지스터 A
- SPIx.CTRLB: 제어 레지스터 B
- SPIx.DATA: 데이터 레지스터
- SPIx.INTCTRL: 인터럽트 제어
- SPIx.STATUS: 상태 레지스터
- 인터럽트: 데이터 전송/수신 완료, 에러 감지
- 전력 관리: 저전력 모드 지원
- 전압 레벨: 1.8V~5.5V 지원
- 기타: 외부 SS 핀 관리 필요 (슬레이브 선택)
2. SPI Bitfield 설정 상세
AVR128DB48의 SPI 모듈은 <avr/io.h> 헤더 파일을 통해 Bitfield 구조로 접근합니다. SPI0와 SPI1은 동일한 레지스터 구조를 사용하며, SPI0.* 또는 SPI1.*로 구분됩니다. 주요 레지스터는 다음과 같습니다:
2.1 SPIx.CTRLA (제어 레지스터 A)
- bit.ENABLE: SPI 모듈 활성화 (1: 활성화, 0: 비활성화)
- bit.MASTER: 마스터 모드 선택 (1: 마스터, 0: 슬레이브)
- bit.CLK2X: 클럭 2배속 설정 (1: 2배속 활성화, 0: 비활성화, 마스터 모드에서 유효)
- bit.PRESC: 클럭 분주기 설정 (2비트)
- `0b00`: DIV4 (시스템 클럭을 4로 나눔)
- `0b01`: DIV16
- `0b10`: DIV64
- `0b11`: DIV128 - bit.DORD: 데이터 전송 순서 (0: MSB 먼저, 1: LSB 먼저)
- bit.BUFEN: 버퍼 모드 활성화 (1: 활성화, 0: 비활성화)
2.2 SPIx.CTRLB (제어 레지스터 B)
- bit.MODE: SPI 모드 (2비트, 클럭 위상/극성 설정)
- `0b00`: Mode 0 (CPOL=0, CPHA=0)
- `0b01`: Mode 1 (CPOL=0, CPHA=1)
- `0b10`: Mode 2 (CPOL=1, CPHA=0)
- `0b11`: Mode 3 (CPOL=1, CPHA=1) - bit.SSD: 슬레이브 선택 비활성화 (1: SS 핀 무시, 0: SS 핀 사용, 슬레이브 모드에서 유효)
- bit.BUFWR: 버퍼 쓰기 모드 (1: 쓰기 시 버퍼 사용, 0: 직접 쓰기, BUFEN=1일 때 유효)
2.3 SPIx.DATA (데이터 레지스터)
- 데이터 송수신용 8비트 레지스터
- 쓰기: 전송할 데이터를 저장하여 전송 시작
- 읽기: 수신된 데이터를 반환
- 특징: 마스터 모드에서는 쓰기 후 전송 완료까지 대기(`IF` 플래그 확인), 슬레이브 모드에서는 SS 신호에 따라 동작
2.4 SPIx.INTFLAGS (인터럽트 플래그 레지스터, 이전에 STATUS로 잘못 명명)
- bit.IF: 인터럽트 플래그 (1: 데이터 전송/수신 완료, 0: 진행 중, 쓰기 1로 지움)
- bit.WRCOL: 쓰기 충돌 플래그 (1: 데이터 레지스터 쓰기 충돌, 0: 정상, 쓰기 1로 지움)
- bit.BUFOVF: 버퍼 오버플로우 플래그 (1: 슬레이브 모드에서 버퍼 오버플로우, 0: 정상, 쓰기 1로 지움)
- bit.RXCIF: 수신 완료 플래그 (1: 수신 완료, 0: 진행 중, 쓰기 1로 지움)
- bit.TXCIF: 전송 완료 플래그 (1: 전송 완료, 0: 진행 중, 쓰기 1로 지움)
- bit.DREIF: 데이터 레지스터 비어짐 플래그 (1: 비어짐, 0: 차지됨, 쓰기 1로 지움)
- bit.SSIF: 슬레이브 선택 플래그 (1: SS 신호 감지, 0: 정상, 쓰기 1로 지움)
2.5 SPIx.INTCTRL (인터럽트 제어 레지스터, 새로 추가)
- bit.RXCIE: 수신 완료 인터럽트 (1: 활성화, 0: 비활성화)
- bit.TXCIE: 전송 완료 인터럽트 (1: 활성화, 0: 비활성화)
- bit.DREIE: 데이터 레지스터 비어짐 인터럽트 (1: 활성화, 0: 비활성화)
- bit.SSIE: 슬레이브 선택 인터럽트 (1: 활성화, 0: 비활성화)
- bit.IE: 글로벌 인터럽트 활성화 (1: 활성화, 0: 비활성화)
3. SPI 설정 절차
- 시스템 초기화:
- 시스템 클럭 설정 (24MHz, set_system_clock())
- 인터럽트 비활성화 (cli())
- 포트 설정:
- SPI0: PA4(MOSI), PA5(MISO), PA6(SCK), PA7(SS)
- PORTx.DIRSET으로 MOSI, SCK, SS 출력 설정, MISO 입력 설정
- 예: PORTA.DIRSET = (PIN4_bm | PIN6_bm | PIN7_bm); (출력)
- PORTA.DIRCLR = PIN5_bm; (MISO 입력)
- 초기 SS High 설정 권장: PORTA.OUTSET = PIN7_bm;
- SPI 모듈 활성화:
- SPI0.CTRLA로 마스터 모드 활성화 및 클럭 설정
- ENABLE = 1, MASTER = 1, PRESC (예: DIV4), CLK2X (선택적)
- SPI0.CTRLB로 SPI 모드 설정
- MODE (0~3), SSD = 1 (SS 수동 제어)
- SPI0.CTRLA로 마스터 모드 활성화 및 클럭 설정
- 마스터 동작:
- SS 핀 수동 제어 (마스터 모드)
- 송신 전 PORTA.OUTCLR = PIN7_bm; (SS Low), 완료 후 PORTA.OUTSET = PIN7_bm; (SS High)
- SPI0.DATA로 데이터 송수신
- 쓰기: SPI0.DATA = data;
- 읽기: data = SPI0.DATA;
- 전송 완료 대기: while (!(SPI0.INTFLAGS & SPI_IF_bm));
- SS 핀 수동 제어 (마스터 모드)
- 인터럽트 설정 (선택):
- SPI0.INTCTRL로 인터럽트 조건 설정
- RXCIE, TXCIE, DREIE, SSIE, IE 중 선택
- SPI0.INTCTRL로 인터럽트 조건 설정
- 상태 확인:
- SPI0.INTFLAGS로 전송 완료 및 에러 확인
- IF (전송/수신 완료), WRCOL (쓰기 충돌), BUFOVF (버퍼 오버플로우)
- SPI0.INTFLAGS로 전송 완료 및 에러 확인
4. SPI 설정 고려사항
- 클럭 설정: 정확한 클럭 분주기 계산 (F_SPI = F_CPU / (2 * (PRESC + 1)))
- SS 핀 관리: 마스터 모드에서 SS 핀을 GPIO로 수동 제어
- 모드 선택: 대상 장치의 SPI 모드(0~3) 확인
- 저전력: 불필요한 SPI 모듈 비활성화로 전력 소모 감소
- 채널 선택: SPI0은 기본 채널, SPI1은 추가 장치 연결이나 핀 충돌 회피 시 사용
5. SPI 예제 코드
아래는 AVR128DB48의 SPI0 및 SPI1을 활용한 예제 코드입니다. Atmel Studio 또는 MPLAB X IDE에서 AVR-GCC로 실행 가능합니다. 예제는 SPI 플래시 메모리(예: W25Q64)를 대상으로 작성되었습니다.
5.1 예제 1: SPI0 마스터로 플래시 메모리에 데이터 쓰기 및 읽기
// File: spi0_flash.c
// Description: AVR128DB48 SPI0 마스터로 W25Q64 플래시 메모리에 데이터 쓰기/읽기
// Compiler: AVR-GCC
// Target: AVR128DB48
#include <avr/io.h> // AVR 입출력 관련 헤더 파일
#include <util/delay.h> // 지연 함수 관련 헤더 파일
#define F_CPU 24000000UL // 시스템 클럭 주파수를 24MHz로 정의
#define SPI_SS_PIN PIN7_bm // PA7(SS) 핀
#define FLASH_ADDR 0x000000 // 플래시 메모리 시작 주소
void set_system_clock(void) {
// 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
_PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
// 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm); // 클록 소스 전환 완료 대기
}
void spi0_init(void) {
// PA4(MOSI), PA6(SCK), PA7(SS)를 출력으로, PA5(MISO)를 입력으로 설정
PORTA.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI, SCK, SS 출력
PORTA.DIRCLR = PIN5_bm; // MISO 입력
PORTA.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
// SPI0 마스터 모드 활성화
SPI0.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI0.CTRLB = SPI_MODE_0_gc | SPI_SSD_bm; // SPI 모드 0 (CPOL=0, CPHA=0), SS 수동 제어
}
uint8_t spi0_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI0.DATA = data; // 데이터 전송
while (!(SPI0.INTFLAGS & SPI_IF_bm)); // 전송 완료 대기 (INTFLAGS 사용)
return SPI0.DATA; // 수신 데이터 반환
}
void flash_write_enable(void) {
// 플래시 메모리 쓰기 활성화 명령 (0x06)
PORTA.OUTCLR = SPI_SS_PIN; // SS Low
spi0_transfer(0x06); // Write Enable 명령
PORTA.OUTSET = SPI_SS_PIN; // SS High
}
void flash_write_byte(uint32_t addr, uint8_t data) {
// 플래시 메모리에 1바이트 쓰기
flash_write_enable(); // 쓰기 활성화
PORTA.OUTCLR = SPI_SS_PIN; // SS Low
spi0_transfer(0x02); // 페이지 프로그램 명령
spi0_transfer((addr >> 16) & 0xFF); // 주소 상위 바이트
spi0_transfer((addr >> 8) & 0xFF); // 주소 중간 바이트
spi0_transfer(addr & 0xFF); // 주소 하위 바이트
spi0_transfer(data); // 데이터 쓰기
PORTA.OUTSET = SPI_SS_PIN; // SS High
_delay_ms(10); // W25Q64 페이지 프로그램 시간 (최대 5ms, 여유 있게 10ms)
while (spi0_transfer(0x05) & 0x01); // Write In Progress (WIP) 확인 (0x05: Read Status Register 1)
}
uint8_t flash_read_byte(uint32_t addr) {
// 플래시 메모리에서 1바이트 읽기
PORTA.OUTCLR = SPI_SS_PIN; // SS Low
spi0_transfer(0x03); // 읽기 명령
spi0_transfer((addr >> 16) & 0xFF); // 주소 상위 바이트
spi0_transfer((addr >> 8) & 0xFF); // 주소 중간 바이트
spi0_transfer(addr & 0xFF); // 주소 하위 바이트
uint8_t data = spi0_transfer(0x00); // 데이터 읽기 (더미 바이트 전송)
PORTA.OUTSET = SPI_SS_PIN; // SS High
return data; // 읽은 데이터 반환
}
void gpio_init(void) {
// PB3(LED0)를 출력으로 설정
PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
PORTB.OUTCLR = PIN3_bm; // PB3 초기값 Low (LED 꺼짐)
}
int main(void) {
set_system_clock(); // 시스템 클럭 설정
spi0_init(); // SPI0 초기화
gpio_init(); // GPIO 초기화
while (1) {
// 플래시 메모리 0x000000 주소에 0x55 쓰기
flash_write_byte(FLASH_ADDR, 0x55);
_delay_ms(100); // 100ms 대기
// 플래시 메모리 0x000000 주소에서 데이터 읽기
uint8_t data = flash_read_byte(FLASH_ADDR);
// 읽은 데이터가 0x55면 LED 토글
if (data == 0x55) {
PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
}
_delay_ms(500); // 500ms 대기 (LED 점멸 주기)
}
return 0; // 프로그램 종료 (도달하지 않음)
}
설명:
- 기능: SPI0을 통해 W25Q64 플래시 메모리에 데이터를 쓰고 읽어 PB3 LED를 토글
- 설정: SPI0 마스터 모드, 1.5MHz 클럭, PA4(MOSI)/PA5(MISO)/PA6(SCK)/PA7(SS)
- 출력: 플래시 메모리 데이터가 0x55일 때 LED 점멸
5.2 예제 2: SPI1 마스터로 플래시 메모리에 데이터 쓰기
// File: spi1_flash.c
// Description: AVR128DB48 SPI1 마스터로 W25Q64 플래시 메모리에 데이터 쓰기
// Compiler: AVR-GCC
// Target: AVR128DB48
#include <avr/io.h> // AVR 입출력 관련 헤더 파일
#include <util/delay.h> // 지연 함수 관련 헤더 파일
#define F_CPU 24000000UL // 시스템 클럭 주파수를 24MHz로 정의
#define SPI_SS_PIN PIN7_bm // PC7(SS) 핀
#define FLASH_ADDR 0x000000 // 플래시 메모리 시작 주소
void set_system_clock(void) {
// 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
_PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
// 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm); // 클록 소스 전환 완료 대기
}
void spi1_init(void) {
// SPI1 핀을 PORTC로 매핑 (기본 설정: PC4(MOSI1), PC5(MISO1), PC6(SCK1), PC7(SS1))
PORTMUX.SPIROUTEA = PORTMUX_SPI1_DEFAULT_gc; // SPI1을 PORTC에 활성화
// PC4(MOSI1), PC6(SCK1), PC7(SS1)를 출력으로, PC5(MISO1)를 입력으로 설정
PORTC.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI1, SCK1, SS1 출력
PORTC.DIRCLR = PIN5_bm; // MISO1 입력
PORTC.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
// SPI1 마스터 모드 활성화
SPI1.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI1.CTRLB = SPI_MODE_0_gc | SPI_SSD_bm; // SPI 모드 0 (CPOL=0, CPHA=0), SS 수동 제어
}
uint8_t spi1_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI1.DATA = data; // 데이터 전송
while (!(SPI1.INTFLAGS & SPI_IF_bm)); // 전송 완료 대기 (INTFLAGS 사용)
return SPI1.DATA; // 수신 데이터 반환
}
void flash_write_enable(void) {
// 플래시 메모리 쓰기 활성화 명령 (0x06)
PORTC.OUTCLR = SPI_SS_PIN; // SS Low
spi1_transfer(0x06); // Write Enable 명령
PORTC.OUTSET = SPI_SS_PIN; // SS High
}
void flash_write_byte(uint32_t addr, uint8_t data) {
// 플래시 메모리에 1바이트 쓰기
flash_write_enable(); // 쓰기 활성화
PORTC.OUTCLR = SPI_SS_PIN; // SS Low
spi1_transfer(0x02); // 페이지 프로그램 명령
spi1_transfer((addr >> 16) & 0xFF); // 주소 상위 바이트
spi1_transfer((addr >> 8) & 0xFF); // 주소 중간 바이트
spi1_transfer(addr & 0xFF); // 주소 하위 바이트
spi1_transfer(data); // 데이터 쓰기
PORTC.OUTSET = SPI_SS_PIN; // SS High
_delay_ms(10); // W25Q64 페이지 프로그램 시간 (최대 5ms, 여유 있게 10ms)
while (spi1_transfer(0x05) & 0x01); // Write In Progress (WIP) 확인 (0x05: Read Status Register 1)
}
void gpio_init(void) {
// PB3(LED0)를 출력으로 설정
PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
PORTB.OUTCLR = PIN3_bm; // PB3 초기값 Low (LED 꺼짐)
}
int main(void) {
set_system_clock(); // 시스템 클럭 설정
spi1_init(); // SPI1 초기화
gpio_init(); // GPIO 초기화
while (1) {
// 플래시 메모리 0x000000 주소에 0xAA 쓰기
flash_write_byte(FLASH_ADDR, 0xAA);
PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
_delay_ms(500); // 500ms 대기 (LED 점멸 주기)
}
return 0; // 프로그램 종료 (도달하지 않음)
}
설명:
- 기능: SPI1을 통해 W25Q64 플래시 메모리에 데이터를 쓰고 PB3 LED 점멸
- 설정: SPI1 마스터 모드, 1.5MHz 클럭, PC4(MOSI)/PC5(MISO)/PC6(SCK)/PC7(SS)
- 출력: 데이터 쓰기 성공 시 LED 점멸
5.3 예제 3: SPI0 인터럽트 기반 플래시 메모리 데이터 읽기
// File: spi0_interrupt.c
// Description: AVR128DB48 SPI0 인터럽트로 W25Q64 플래시 메모리 데이터 읽기
// Compiler: AVR-GCC
// Target: AVR128DB48
#include <avr/io.h> // AVR 입출력 관련 헤더 파일
#include <avr/interrupt.h> // 인터럽트 처리 관련 헤더 파일
#define F_CPU 24000000UL // 시스템 클럭 주파수를 24MHz로 정의
#define SPI_SS_PIN PIN7_bm // PA7(SS) 핀
#define FLASH_ADDR 0x000000 // 플래시 메모리 시작 주소
volatile uint8_t spi_data = 0; // 읽은 데이터를 저장하는 전역 변수
volatile uint8_t spi_state = 0; // SPI 상태를 추적하는 전역 변수
void set_system_clock(void) {
// 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
_PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
// 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm); // 클록 소스 전환 완료 대기
}
void spi0_init(void) {
// PA4(MOSI), PA6(SCK), PA7(SS)를 출력으로, PA5(MISO)를 입력으로 설정
PORTA.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI, SCK, SS 출력
PORTA.DIRCLR = PIN5_bm; // MISO 입력
PORTA.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
// SPI0 마스터 모드 및 인터럽트 활성화
SPI0.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI0.CTRLB = SPI_MODE_0_gc | SPI_SSD_bm; // SPI 모드 0 (CPOL=0, CPHA=0), SS 수동 제어
SPI0.INTCTRL = SPI_RXCIE_bm; // 수신 완료 인터럽트 활성화
}
ISR(SPI0_INT_vect) {
// SPI0 인터럽트 서비스 루틴
if (SPI0.INTFLAGS & SPI_RXCIF_bm) { // 수신 완료 인터럽트 플래그 확인
SPI0.INTFLAGS = SPI_RXCIF_bm; // 플래그 지움
if (spi_state == 1) { // 상태 1: 읽기 명령 전송 후
SPI0.DATA = (FLASH_ADDR >> 16) & 0xFF; // 주소 상위 바이트
spi_state = 2; // 다음 상태로 전환
} else if (spi_state == 2) { // 상태 2: 주소 상위 바이트 전송 후
SPI0.DATA = (FLASH_ADDR >> 8) & 0xFF; // 주소 중간 바이트
spi_state = 3; // 다음 상태로 전환
} else if (spi_state == 3) { // 상태 3: 주소 하위 바이트 전송 후
SPI0.DATA = FLASH_ADDR & 0xFF; // 주소 하위 바이트
spi_state = 4; // 다음 상태로 전환
} else if (spi_state == 4) { // 상태 4: 더미 바이트 전송 후
SPI0.DATA = 0x00; // 더미 바이트 전송
spi_state = 5; // 다음 상태로 전환
} else if (spi_state == 5) { // 상태 5: 데이터 읽기
spi_data = SPI0.DATA; // 읽은 데이터 저장
PORTA.OUTSET = SPI_SS_PIN; // SS High
spi_state = 0; // 초기 상태로 리셋
}
}
}
void flash_read_byte_async(uint32_t addr) {
// 비동기 플래시 메모리 데이터 읽기 시작
spi_state = 1; // 상태를 1로 설정
PORTA.OUTCLR = SPI_SS_PIN; // SS Low
SPI0.DATA = 0x03; // 읽기 명령 전송
}
void gpio_init(void) {
// PB3(LED0)를 출력으로 설정
PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
PORTB.OUTCLR = PIN3_bm; // PB3 초기값 Low (LED 꺼짐)
}
int main(void) {
set_system_clock(); // 시스템 클럭 설정
spi0_init(); // SPI0 초기화
gpio_init(); // GPIO 초기화
sei(); // 글로벌 인터럽트 활성화
while (1) {
// 플래시 메모리 0x000000 주소에서 데이터 비동기 읽기
flash_read_byte_async(FLASH_ADDR);
while (spi_state != 0); // 읽기 완료 대기
if (spi_data == 0x55) { // 읽은 데이터가 0x55인지 확인
PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
}
_delay_ms(500); // 500ms 대기 (LED 점멸 주기)
}
return 0; // 프로그램 종료 (도달하지 않음)
}
설명:
- 기능: SPI0 인터럽트를 사용하여 W25Q64 플래시 메모리에서 데이터를 비동기적으로 읽고 PB3 LED를 토글
- 설정: SPI0 마스터 모드, 1.5MHz 클럭, PA4(MOSI)/PA5(MISO)/PA6(SCK)/PA7(SS), 인터럽트 활성화
- 출력: 플래시 메모리 데이터가 0x55일 때 LED 점멸
5.4 예제 4: SPI1 다중 바이트 플래시 메모리 쓰기/읽기
// File: spi1_multi_byte.c
// Description: AVR128DB48 SPI1으로 W25Q64 플래시 메모리에 다중 바이트 쓰기/읽기 (PORTC 사용)
// Compiler: AVR-GCC
// Target: AVR128DB48
#include <avr/io.h> // AVR 입출력 관련 헤더 파일
#include <util/delay.h> // 지연 함수 관련 헤더 파일
#define F_CPU 24000000UL // 시스템 클럭 주파수를 24MHz로 정의
#define SPI_SS_PIN PIN7_bm // PC7(SS) 핀
#define FLASH_ADDR 0x000000 // 플래시 메모리 시작 주소
void set_system_clock(void) {
// 외부 32.768kHz 크리스털 오실레이터(XOSC32K) 활성화
_PROTECTED_WRITE(CLKCTRL.XOSC32KCTRLA, CLKCTRL_ENABLE_bm | CLKCTRL_RUNSTDBY_bm);
while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_XOSC32KS_bm)); // XOSC32K 안정화 대기
// 내부 OSCHF를 24MHz로 설정하고 XOSC32K 참조 오토튜닝 활성화
_PROTECTED_WRITE(CLKCTRL.OSCHFCTRLA, CLKCTRL_FRQSEL_24M_gc | CLKCTRL_AUTOTUNE_bm);
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLA, CLKCTRL_CLKSEL_OSCHF_gc); // OSCHF를 시스템 클럭 소스로 설정
_PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, 0x00); // 클럭 프리스케일러 비활성화
while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm); // 클록 소스 전환 완료 대기
}
void spi1_init(void) {
// SPI1 핀을 PORTC로 매핑 (기본 설정: PC4(MOSI1), PC5(MISO1), PC6(SCK1), PC7(SS1))
PORTMUX.SPIROUTEA = PORTMUX_SPI1_DEFAULT_gc; // SPI1을 PORTC에 활성화
// PC4(MOSI1), PC6(SCK1), PC7(SS1)를 출력으로, PC5(MISO1)를 입력으로 설정
PORTC.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI1, SCK1, SS1 출력
PORTC.DIRCLR = PIN5_bm; // MISO1 입력
PORTC.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
// SPI1 마스터 모드 활성화
SPI1.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI1.CTRLB = SPI_MODE_0_gc | SPI_SSD_bm; // SPI 모드 0 (CPOL=0, CPHA=0), SS 수동 제어
}
uint8_t spi1_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI1.DATA = data; // 데이터 전송
while (!(SPI1.INTFLAGS & SPI_IF_bm)); // 전송 완료 대기 (INTFLAGS 사용)
return SPI1.DATA; // 수신 데이터 반환
}
void flash_write_enable(void) {
// 플래시 메모리 쓰기 활성화 명령 (0x06)
PORTC.OUTCLR = SPI_SS_PIN; // SS Low
spi1_transfer(0x06); // Write Enable 명령
PORTC.OUTSET = SPI_SS_PIN; // SS High
}
void flash_write_bytes(uint32_t addr, uint8_t *data, uint8_t len) {
// 플래시 메모리에 다중 바이트 쓰기
flash_write_enable(); // 쓰기 활성화
PORTC.OUTCLR = SPI_SS_PIN; // SS Low
spi1_transfer(0x02); // 페이지 프로그램 명령
spi1_transfer((addr >> 16) & 0xFF); // 주소 상위 바이트
spi1_transfer((addr >> 8) & 0xFF); // 주소 중간 바이트
spi1_transfer(addr & 0xFF); // 주소 하위 바이트
for (uint8_t i = 0; i < len; i++) {
spi1_transfer(data[i]); // 데이터 쓰기
}
PORTC.OUTSET = SPI_SS_PIN; // SS High
_delay_ms(10); // W25Q64 페이지 프로그램 시간 (최대 5ms, 여유 있게 10ms)
while (spi1_transfer(0x05) & 0x01); // Write In Progress (WIP) 확인 (0x05: Read Status Register 1)
}
void flash_read_bytes(uint32_t addr, uint8_t *data, uint8_t len) {
// 플래시 메모리에서 다중 바이트 읽기
PORTC.OUTCLR = SPI_SS_PIN; // SS Low
spi1_transfer(0x03); // 읽기 명령
spi1_transfer((addr >> 16) & 0xFF); // 주소 상위 바이트
spi1_transfer((addr >> 8) & 0xFF); // 주소 중간 바이트
spi1_transfer(addr & 0xFF); // 주소 하위 바이트
for (uint8_t i = 0; i < len; i++) {
data[i] = spi1_transfer(0x00); // 데이터 읽기 (더미 바이트 전송)
}
PORTC.OUTSET = SPI_SS_PIN; // SS High
}
void gpio_init(void) {
// PB3(LED0)를 출력으로 설정
PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
PORTB.OUTCLR = PIN3_bm; // PB3 초기값 Low (LED 꺼짐)
}
int main(void) {
set_system_clock(); // 시스템 클럭 설정
spi1_init(); // SPI1 초기화
gpio_init(); // GPIO 초기화
uint8_t write_data[3] = {0xAA, 0xBB, 0xCC}; // 쓰기용 데이터 배열
uint8_t read_data[3] = {0}; // 읽기용 데이터 배열
while (1) {
// 플래시 메모리 0x000000 주소에 3바이트 쓰기
flash_write_bytes(FLASH_ADDR, write_data, 3); // [0xAA, 0xBB, 0xCC] 쓰기
_delay_ms(100); // 100ms 대기
// 플래시 메모리 0x000000 주소에서 3바이트 읽기
flash_read_bytes(FLASH_ADDR, read_data, 3); // 3바이트 읽기
// 읽은 데이터가 [0xAA, 0xBB, 0xCC]인지 확인
if (read_data[0] == 0xAA && read_data[1] == 0xBB && read_data[2] == 0xCC) {
PORTB.OUTTGL = PIN3_bm; // PB3 출력 토글 (LED 깜빡임)
}
_delay_ms(500); // 500ms 대기 (LED 점멸 주기)
}
return 0; // 프로그램 종료 (도달하지 않음)
}
설명:
- 기능: SPI1을 통해 W25Q64 플래시 메모리에 다중 바이트(3바이트) 쓰기 및 읽기 후 PB3 LED 토글
- 설정: SPI1 마스터 모드, 1.5MHz 클럭, PC4(MOSI)/PC5(MISO)/PC6(SCK)/PC7(SS)
- 출력: 읽은 데이터가 [0xAA, 0xBB, 0xCC]일 때 LED 점멸
6. SPI0와 SPI1 선택 기준
- SPI0: 기본 SPI 채널로, Curiosity Nano와 같은 평가 보드에서 표준 핀(PA4~PA7)으로 사용. 단일 SPI 통신에 적합.
- SPI1: 추가 SPI 장치 연결이 필요하거나, SPI0 핀이 다른 기능(UART, I2C 등)으로 사용 중일 때 활용. PC4~PC7 핀 사용.
- 멀티플렉싱 주의: 핀 충돌 방지를 위해 데이터시트의 핀 기능 확인.
7. 사용 방법
7.1 환경 설정
- AVR-GCC 설치: Atmel Studio 또는 MPLAB X IDE에 AVR-GCC 툴체인 설치
- 헤더 파일: <avr/io.h>, <util/delay.h>, <avr/interrupt.h> 포함
- 프로젝트 설정: AVR128DB48 타겟으로 프로젝트 생성
- 클럭 정의: #define F_CPU 24000000UL
7.2 코드 실행
- 각 예제를 별도의 .c 파일로 저장하거나, main.c에 복사
- Atmel Studio/MPLAB X IDE에서 빌드 및 플래싱
7.3 하드웨어 준비
- SPI0 연결: PA4(MOSI), PA5(MISO), PA6(SCK), PA7(SS)에 W25Q64 플래시 메모리 연결
- SPI1 연결: PC4(MOSI), PC5(MISO), PC6(SCK), PC7(SS)에 W25Q64 플래시 메모리 연결
- LED 출력: PB3에 LED 및 330Ω 저항 연결
- 전원: 1.8V~5.5V 전원 공급
7.4 디버깅
- Atmel Studio/MPLAB X IDE 디버거 사용
- SPIx.STATUS, SPIx.DATA 레지스터 확인
- 오실로스코프로 MOSI/MISO/SCK 신호 점검
8. 추가 팁
- 클럭 설정: set_system_clock()로 XOSC32K 안정화 확인
- 노이즈 감소: PCB 배선 최적화 및 SS 핀 안정적 제어
- 인터럽트 사용: 예제 3처럼 인터럽트를 활용하여 CPU 부하 감소
- 다중 바이트: 예제 4처럼 다중 바이트 전송으로 효율성 향상
- Microchip 리소스: AVR128DB48 데이터시트, SPI Application Notes
- 문제 해결:
- 통신 실패: SS 핀 상태 및 SPIx.STATUS.WRCOL 확인
- 데이터 오류: SPIx.STATUS.IF 플래그 점검
- 느린 응답: 클럭 분주기 조정
- 커뮤니티: Microchip Community, AVR Freaks 포럼 참고
9. 결론
이 문서는 AVR128DB48의 SPI0 및 SPI1 모듈 설정 방법과 Bitfield 구조를 활용한 예제 코드를 제공하여 SPI 기반 애플리케이션 구현을 지원합니다. SPI0는 기본 채널로 단일 장치 통신에 적합하며, SPI1은 추가 장치 연결이나 핀 충돌 회피 시 유용합니다. 인터럽트 및 다중 바이트 전송 예제를 통해 효율적인 SPI 통신을 구현할 수 있습니다.
키워드: AVR128DB48, SPI, SPI0, SPI1, 마이크로컨트롤러, Atmel Studio, MPLAB X IDE, 플래시 메모리, 마스터 모드, 인터럽트, 다중 바이트, W25Q64
'MCU > AVR' 카테고리의 다른 글
AVR128DB48 RTC 사용 방법 및 예제 코드 (0) | 2025.08.20 |
---|---|
AVR128DB48 LIN, IrDA, RS485, MPCM, 스타트 프레임 감지, 동기 모드 사용 방법 및 예제 코드 (1) | 2025.08.20 |
AVR128DB48 Event System 사용 방법 및 예제 코드(수정) (0) | 2025.08.20 |
AVR128DB48 Watchdog 사용 방법 및 예제 코드 (0) | 2025.08.19 |
AVR128DB48 I2C 사용 방법 및 예제 코드(수정) (0) | 2025.08.19 |
AVR128DB48 UART 사용 방법 및 예제 코드(수정) (1) | 2025.08.19 |
AVR128DB48 GPIO 사용 방법 및 예제 코드 (0) | 2025.08.18 |
AVR128DB48 Microchip Studio 프로젝트 생성 절차 및 기본 프로그램 작성(수정) (1) | 2025.08.18 |