임베디드에서 기본적으로 알아야 할 통신 : 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
마스터 1개에 여러개의 슬레이브 중 하나 고르기
특정 슬레이브와 연결 될 때 S/S 가 LOW로 활성화 됨(시작) 다 사용한 후에는 HIGH가 된다(종료)
SPI 데이터 송수신

shift 레지스터 [0:7] 존재 : 임시 데이터 저장 공간 (1byte)
젤 끝의 데이터 (7) : MSB , 젤 앞 데이터 : LSB 라고 불림
MOSI를 통해 MSB 부터 한비트씩 밀어보냄
MISO를 통해 MSB 부터 한비트씩 밀어보냄 (둘 다 동시)
Full duplex : 통신의 양방향성 (마스터 슬래이브)
간단 정리

실습

아두이노 우노 보드 2개 사용
레지스터 설정

명확하게 Control , Status , Data 나눠져 있음
SPCR -SPI control 레지스터

SPE : SPI 기능 키기 1
MSTR : 마스터 모드 1 , 슬레이브 모드 0
SPR1 , SPR0 : Clock Rate(분주비로 clock 세팅)

CPOL , CPHA : SPI 모드 설정 ※마스터 슬레이브는 항상 같은 모드로 동작해야함
CPOL : 클럭 극성
CPOL=0 → 클럭이 평소엔 낮고, “올라갈 때”가 첫 엣지
CPOL=1 → 클럭이 평소엔 높고, “내려갈 때”가 첫 엣지
CPHA : 클럭 위상
CPHA=0 → 클럭이 변하자마자 데이터를 읽음 (빠르게 샘플링)
CPHA=1 → 한 번 변화 후 다음 엣지에서 읽음 (조금 늦게 샘플링)




Status 레지스터 : SPSR , SPDR
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 |