본문 바로가기
MCU/AVR

[AVR128DB48] SPI 사용 방법 및 예제 코드

by linuxgo 2025. 8. 19.
반응형

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 설정 절차

  1. 시스템 초기화:
    •    시스템 클럭 설정 (24MHz, set_system_clock())
    •    인터럽트 비활성화 (cli())
  2. 포트 설정:
    •    SPI0: PA4(MOSI), PA5(MISO), PA6(SCK), PA7(SS)
    •    SPI1: PC4(MOSI), PC5(MISO), PC6(SCK), PC7(SS)
    •    PORTx.DIRSET으로 MOSI, SCK, SS 출력 설정, MISO 입력 설정
  3. SPI 모듈 활성화:
    •    SPIx.CTRLA로 마스터 모드 활성화 및 클럭 설정
    •    SPIx.CTRLB로 SPI 모드 설정
  4. 마스터 동작:
    •    SS 핀 수동 제어 (마스터 모드)
    •    SPIx.DATA로 데이터 송수신
  5. 인터럽트 설정 (선택):
    •    SPIx.INTCTRL로 인터럽트 조건 설정
  6. 상태 확인:
    •    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

반응형