본문 바로가기
Atmega328p

{Atmega328p}-SPI

by syyy1 2025. 10. 31.

임베디드에서 기본적으로 알아야 할 통신 : UART , SPI , I2C

Serial Peripheral Interface = SPI : 주변 장치와 직렬 통신하기 위한 방법

UART : 클럭 신호 사용 x

SPI : 클럭 신호를 사용한 동기 통신

동기 통신이 큰 차이점

 

# 궁금점 CAN 통신은 ?

구분 설명
SPI 마스터가 직접 클럭(SCK)을 만들어줌
CAN 각 노드가 자체 클럭을 가지고 있지만, 메시지의 비트 전환(edge)을 보고 계속 동기화
UART 별도 클럭 없이, Start bit/Stop bit로만 순간적인 동기화 (완전 비동기)

MASTER & SLAVE 

SCK : 클락선

MOSI : 마스터가 슬레이브에게 데이터 전송

MISO : 슬레이브가 마스터에게 데이터 응답

모든 통신의 주도건은 마스터에게 존재 : SCK 로 일단 클락 정보를 제공

 

SLAVE SELECT

SLAVE SELECT 

마스터 1개에 여러개의 슬레이브 중 하나 고르기 

특정 슬레이브와 연결 될 때 S/S 가 LOW로 활성화 됨(시작) 다 사용한 후에는 HIGH가 된다(종료)

 

 

SPI 데이터 송수신

shift 레지스터 [0:7] 존재 : 임시 데이터 저장 공간 (1byte) 

젤 끝의 데이터 (7) : MSB  , 젤 앞 데이터 : LSB 라고 불림

MOSI를 통해 MSB 부터 한비트씩 밀어보냄

MISO를 통해 MSB 부터 한비트씩 밀어보냄 (둘 다 동시)

Full duplex : 통신의 양방향성 (마스터 슬래이브)

 

간단 정리

실습

MASTER SLAVE

아두이노 우노 보드 2개 사용

 

레지스터 설정 

SPI Block Diagram

명확하게 Control , Status , Data 나눠져 있음

 

SPCR -SPI control 레지스터

SPE : SPI 기능 키기 1

MSTR : 마스터 모드 1 ,  슬레이브 모드 0

SPR1 , SPR0 :  Clock Rate(분주비로 clock 세팅)

SPR1 ,  SPR0

CPOL , CPHA  : SPI 모드 설정 ※마스터 슬레이브는 항상 같은 모드로 동작해야함

CPOL : 클럭 극성 

CPOL=0 → 클럭이 평소엔 낮고, “올라갈 때”가 첫 엣지

CPOL=1 → 클럭이 평소엔 높고, “내려갈 때”가 첫 엣지

 

CPHA : 클럭 위상

CPHA=0 → 클럭이 변하자마자 데이터를 읽음 (빠르게 샘플링)

CPHA=1 → 한 번 변화 후 다음 엣지에서 읽음 (조금 늦게 샘플링)

 

모드에 따른 통신 방식

 

 

Status 레지스터 : SPSR , SPDR

 

SPSR : 현재 상태

SPSR

SPIF0 : 데이터의 송수신이 완료되면 1로 자동으로 바꿈

SPI2X : clock 속도를 2배로 올려주는 레지스터

 

 

SPDR : 데이터를 주고 받는 창고 레지스터 : 여기에 데이터를 넣으면 자동으로 전송이 시작됨 > SPIF 자동으로 1 설정

 

코드

#define F_CPU 16000000UL         // CPU 클록 16MHz 기준
#include <avr/io.h>
#include <util/delay.h>

// 비트 세트 / 클리어 매크로
#define sbi(PORTX, BitX) (PORTX |= (1 << BitX))
#define cbi(PORTX, BitX) (PORTX &= ~(1 << BitX))

// ===============================
// UART 초기화 함수 (시리얼 통신용)
// ===============================
void UART_INIT(void)
{
    sbi(UCSRA, U2X);            // Baudrate 2배 모드
    UBRRH = 0x00;               // Baudrate 상위 바이트
    UBRRL = 207;                // Baudrate 9600bps 설정 (16MHz 기준)

    UCSRC = 0x06;               // 8비트 데이터, 1 스톱비트

    sbi(UCSRB, RXEN);           // 수신 허용
    sbi(UCSRB, TXEN);           // 송신 허용
}

// UART 수신 함수
unsigned char UART_receive(void)
{
    while (!(UCSRA & (1 << RXC)));  // 데이터 수신될 때까지 대기
    return UDR;                     // 수신 데이터 반환
}

// UART 송신 함수
unsigned char UART_transmit(unsigned char data)
{
    while (!(UCSRA & (1 << UDRE))); // 송신 버퍼 비워질 때까지 대기
    UDR = data;                     // 데이터 전송
    return 0;
}

// ===============================
// SPI 마스터 초기화 함수
// ===============================
void SPI_Master_Init(void)
{
    sbi(DDRB, 5);   // SCK  (PB5) 출력
    sbi(DDRB, 3);   // MOSI (PB3) 출력
    sbi(DDRB, 2);   // SS   (PB2) 출력
    cbi(DDRB, 4);   // MISO (PB4) 입력
    PORTB = 0xFF;   // 풀업 저항 설정

    sbi(SPCR, SPE);   // SPI 활성화
    sbi(SPCR, MSTR);  // 마스터 모드 설정
    // 기본 모드: SPI Mode 0, 클럭 = Fosc / 4
}

// ===============================
// SPI 슬레이브 초기화 함수
// ===============================
void SPI_Slave_Init(void)
{
    cbi(DDRB, 5);   // SCK  입력
    cbi(DDRB, 3);   // MOSI 입력
    cbi(DDRB, 2);   // SS   입력
    sbi(DDRB, 4);   // MISO 출력
    PORTB = 0xFF;   // 풀업 저항 설정

    sbi(SPCR, SPE); // SPI 활성화 (슬레이브 모드)
}

// ===============================
// SPI 송수신 함수 (1Byte 단위)
// ===============================
unsigned char SPI_TxRx(unsigned char data)
{
    SPDR = data;                        // 데이터 전송 시작
    while (!(SPSR & (1 << SPIF)));      // 송신 완료될 때까지 대기
    return SPDR;                        // 수신된 데이터 반환
}

// ===============================
// 메인 함수
// ===============================
int main(void)
{
    unsigned char data;   // 데이터 저장용 변수

    UART_INIT();          // UART 초기화 (시리얼 출력용)
    SPI_Master_Init();    // SPI 마스터 모드 설정
    // SPI_Slave_Init();  // 슬레이브로 동작시키려면 이 줄로 교체

    // SS(슬레이브 선택) 핀을 LOW로 → 통신 시작 신호
    cbi(PORTB, 2);

    while (1)
    {
        // 마스터: 'A' 문자를 SPI로 전송하고 수신된 데이터 받기
        data = SPI_TxRx('A');

        // UART로 수신 데이터 출력 (터미널 확인용)
        UART_transmit(data);

        _delay_ms(1000);  // 1초마다 반복
    }

    /*
    // 아래 코드는 슬레이브용 예시 (참고용)
    while (1)
    {
        // 1. 마스터로부터 데이터 수신 대기
        while (!(SPSR & (1 << SPIF)));

        // 2. 수신된 데이터에 +0x20 하여 다시 전송
        SPDR = SPDR + 0x20;
    }
    */

    return 0;
}

'Atmega328p' 카테고리의 다른 글

{Atmega328p}-EEPROM  (0) 2025.11.01
{Atmega328p}-TWI  (0) 2025.10.31
{atmega328p}-Phase Correct PWM Mode  (0) 2025.10.29
{Atmega328p}-Fast PWM Mode  (0) 2025.10.29
{Atmega328p}-Timer , Counter  (0) 2025.10.29