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: 활성화)
- bit.MASTER: 마스터 모드 선택 (1: 마스터, 0: 슬레이브)
- bit.CLK2X: 클럭 2배속 설정
- bit.PRESC: 클럭 분주기 설정 (DIV4, DIV16, DIV64, DIV128)
2.2 SPIx.CTRLB (제어 레지스터 B)
- bit.MODE: SPI 모드 (0~3, 클럭 위상/극성 설정)
- bit.SSD: 슬레이브 선택 비활성화
- bit.BUFEN: 버퍼 모드 활성화
2.3 SPIx.DATA (데이터 레지스터)
- 데이터 송수신용 레지스터
- 쓰기: 전송할 데이터
- 읽기: 수신된 데이터
2.4 SPIx.STATUS (상태 레지스터)
- bit.IF: 인터럽트 플래그 (전송/수신 완료)
- bit.WRCOL: 쓰기 충돌 플래그
3. SPI 설정 절차
- 시스템 초기화:
- 시스템 클럭 설정 (24MHz, set_system_clock())
- 인터럽트 비활성화 (cli())
- 포트 설정:
- SPI0: PA4(MOSI), PA5(MISO), PA6(SCK), PA7(SS)
- SPI1: PC4(MOSI), PC5(MISO), PC6(SCK), PC7(SS)
- PORTx.DIRSET으로 MOSI, SCK, SS 출력 설정, MISO 입력 설정
- SPI 모듈 활성화:
- SPIx.CTRLA로 마스터 모드 활성화 및 클럭 설정
- SPIx.CTRLB로 SPI 모드 설정
- 마스터 동작:
- SS 핀 수동 제어 (마스터 모드)
- SPIx.DATA로 데이터 송수신
- 인터럽트 설정 (선택):
- SPIx.INTCTRL로 인터럽트 조건 설정
- 상태 확인:
- SPIx.STATUS로 전송 완료 및 에러 확인
4. SPI 설정 고려사항
- 클럭 설정: 정확한 클럭 분주기 계산 (F_SPI = F_CPU / (2 * (PRESC + 1)))
- SS 핀 관리: 마스터 모드에서 SS 핀을 GPIO로 수동 제어
- 모드 선택: 대상 장치의 SPI 모드(0~3) 확인
- 저전력: 불필요한 SPI 모듈 비활성화로 전력 소모 감소
- 채널 선택: SPI0은 기본 채널, SPI1은 추가 장치 연결이나 핀 충돌 회피 시 사용
5. 실용적인 SPI 예제 코드 (Bitfield 구조)
아래는 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); // 클럭 프리스케일러 비활성화
}
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 입력
// SPI0 마스터 모드 활성화
SPI0.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI0.CTRLB = SPI_MODE_0_gc; // SPI 모드 0 (CPOL=0, CPHA=0)
PORTA.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
}
uint8_t spi0_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI0.DATA = data; // 데이터 전송
while (!(SPI0.STATUS & SPI_IF_bm)); // 전송 완료 대기
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(5); // 쓰기 시간 대기
}
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.OUTSET = PIN3_bm; // PB3 초기값 High (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); // 클럭 프리스케일러 비활성화
}
void spi1_init(void) {
// PC4(MOSI), PC6(SCK), PC7(SS)를 출력으로, PC5(MISO)를 입력으로 설정
PORTC.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI, SCK, SS 출력
PORTC.DIRCLR = PIN5_bm; // MISO 입력
// SPI1 마스터 모드 활성화
SPI1.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI1.CTRLB = SPI_MODE_0_gc; // SPI 모드 0 (CPOL=0, CPHA=0)
PORTC.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
}
uint8_t spi1_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI1.DATA = data; // 데이터 전송
while (!(SPI1.STATUS & SPI_IF_bm)); // 전송 완료 대기
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(5); // 쓰기 시간 대기
}
void gpio_init(void) {
// PB3(LED0)를 출력으로 설정
PORTB.DIRSET = PIN3_bm; // PB3 핀을 출력으로 설정
PORTB.OUTSET = PIN3_bm; // PB3 초기값 High (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); // 클럭 프리스케일러 비활성화
}
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 입력
// SPI0 마스터 모드 및 인터럽트 활성화
SPI0.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc | SPI_IE_bm; // SPI 활성화, 마스터 모드, 1.5MHz 클럭, 인터럽트 활성화
SPI0.CTRLB = SPI_MODE_0_gc; // SPI 모드 0 (CPOL=0, CPHA=0)
PORTA.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
}
ISR(SPI0_INT_vect) {
// SPI0 인터럽트 서비스 루틴
if (SPI0.STATUS & SPI_IF_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.OUTSET = PIN3_bm; // PB3 초기값 High (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 플래시 메모리에 다중 바이트 쓰기/읽기
// 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); // 클럭 프리스케일러 비활성화
}
void spi1_init(void) {
// PC4(MOSI), PC6(SCK), PC7(SS)를 출력으로, PC5(MISO)를 입력으로 설정
PORTC.DIRSET = PIN4_bm | PIN6_bm | PIN7_bm; // MOSI, SCK, SS 출력
PORTC.DIRCLR = PIN5_bm; // MISO 입력
// SPI1 마스터 모드 활성화
SPI1.CTRLA = SPI_ENABLE_bm | SPI_MASTER_bm | SPI_PRESC_DIV16_gc; // SPI 활성화, 마스터 모드, 1.5MHz 클럭
SPI1.CTRLB = SPI_MODE_0_gc; // SPI 모드 0 (CPOL=0, CPHA=0)
PORTC.OUTSET = SPI_SS_PIN; // SS 핀 초기값 High (비활성화)
}
uint8_t spi1_transfer(uint8_t data) {
// SPI 데이터 송수신
SPI1.DATA = data; // 데이터 전송
while (!(SPI1.STATUS & SPI_IF_bm)); // 전송 완료 대기
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(5); // 쓰기 시간 대기
}
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.OUTSET = PIN3_bm; // PB3 초기값 High (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] PWM 사용 방법 및 예제 코드 (0) | 2025.08.20 |
---|---|
[AVR128DB48] 이벤트 시스템 사용 방법 및 예제 코드 (0) | 2025.08.20 |
[AVR128DB48] ADC 및 DAC 사용 방법 및 예제 코드 (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] 프로젝트 설정 절차 및 기본 프로그램 작성 (0) | 2025.08.18 |