만져보는 임베디드 시스템/아두이노 소프트웨어

아두이노 소프트웨어-xxii. SPI 라이브러리

hanjinee 2021. 1. 5. 13:43

안녕하세요 제타지니입니다. 

지난 번의 포스트를 통해 몇 가지 짚고 넘어가겠습니다. 

SPI 통신(Serial Peripheral Interface)는 Master - Slave 간의 1:N 방식의 통신입니다.

* 최소 4개의 연결선 (MOSI, MISO, SCLK, SS)가 필요하며, 슬레이브가 한 개 늘어날 때마다 SS 선도 한개씩 추가

* 모든 Slave 기기에 대하여 MOSI, MISO, SCLK는 공통

* 전이중(full-duplex)방식으로 송신과 수신이 동시에 이루어지기에 I2C 통신에 비해 빠르다.

* 아두이노에서는 일반적으로 짧은 거리의 고속 데이터 통신이 필요한 경우 SPI를 사용하며, 복잡한 통신의 경우에도 사용됨.

* MISO(Master In Slave Out), MOSI(Master Out Slave In), SCLK(Serial Clock), SS(Slave Select)

 

 

따라서 그림으로 나타내면 다음과 같이 나타낼 수 있지요!

이 SPI 는 데이터 전송을 위해 세부적인 내용까지는 정의하고 있지 않습니다. 장치에 따라서 다른 방법으로 구현하고 있기 때문에, 

[데이터 직렬화 방법(MSB or LSB)], [클록 동기화 방법], [전송 속도] 등에 대하여 장치의 데이터 시트를 면밀히 살펴보아야 합니다. SPI 라이브러리의 전송 속도는 default 값으로 4[MHz]로 설정되어 있습니다. 

 

*데이터 직렬화 방법(MSB or LSB) - MSB(Most Significant Bit) 순서인가, LSB(Least Significant Bit) 순서로 비트를 읽는가. SPI.setBitOrder 함수로 제어

*클록 동기화 방법- 데이터 클럭의 대기 상태가 HIGH 신호 일 때인가, LOW 신호일 때인가. 이는 SPI.setDataMode 함수로 제어(클럭의 극성)

*전송 속도 - SPI.setClockDivider로 제어

 

 

아두이노에는 기본 라이브러리 중의 하나로 SPI 라이브러리가 포함되어 있습니다. SPI 통신을 통해 송수신되는 데이터의 의미는 주변 장치에 따라 달라지기 때문에, SPI 라이브러리에는 기본적인 설정 및 데이터 전송 함수들만을 정의하고 있습니다. 

SPI 라이브러리에서 정의하고 있는 클래스는 SPIclass 이며, SPIclass의 전역 객체인 SPI를 통해 SPI 통신이 이루어집니다. 

 

아두이노의 SPI 라이브러리는 마스터 모드만을 지원하는데, 슬레이브 모드로 동작하기 위해서는 해당 레지스터를 직접 설정해야 하며 세 가지 설정이 필요합니다.이에 관해서는 함수를 설명한 뒤에 다시 보겠습니다.

SPI 라이브러리에 나와있는 함수를 보겠습니다. 

 

 함수

형태

의미

사용예제

begin

void begin(void)

매개변수: 없음

반환값: 없음

*SPI 통신 초기화

SPI.begin(); 

end

void end(void)

매개변수: 없음

반환값: 없음

*SPI 통신 종료

SPI.end();

setBitOrder

void setBitOrder(uiint8_t bitOrder)

bitOrder: 데이트 송수신시 비트 순서

 -LSBFIRST 또는 MSBFIRST 중 하나의 값

반환값: 없음

*데이터가 SPI 버스로 직렬 전송될 때 전송 순서를 설정

SPI.setBitOrder(LSBFIRST);

setClockDivider

void setClockDivider(uint8_t rate)

rate: 분주비율

반환값: 없음

*시스템에서 사용하는 클록에대적인 분주 비율 설정

*AVR 기반의 보드에서는 2, 4 8, 16, 32, 64,128 중 하나의 값을 사용 가능

*위의 값들은 SPI_CLOCK_DIV2에서 SPI_CLOCK_DIV128 까지의 상수로 정의되어 있음

*디폴트 값은 SPI_CLOCK_DIV4

 -시스템 클록의 1/4 속도로 데이터를 전송

*아두이노 우노의 경우 16[MHz]를 사용하므로 4[MHz]가 기본 주파수에 해당

 SPI.setClockDivider(SPI_CLOCK_DIV4);

setDataMode

void setDataMode(uint8_t mode)

mode: 전송 모드

 -SPI_MODE0 ~ SPI_MODE3 중 하나

반환값: 없음

*표에 넣기에 너무 크므로 표 밑에서 설명

SPI.setDataMode(SPI_MODE3);

transfer

byte transfer(byte data)

data: 송신할 데이터

반환값: 수신된 데이터 

*SPI 버스를 통해 한 바이트의 데이터를 보내고 받음

*송신과 수신은 동시에 진행

 

 

setDataMode 추가 설명

전송 모드는 클록의 위상(phase)와 극성(polarity)의 조합에 따라 4가지로 나눌 수 있습니다.

위상을 CPHA(Clock PHAse)라고 부르고, 극성을 SPOL(Clock POLarity)라고 부릅니다. 

위상은 데이터가 샘플링되는 시점과 데이터가 전송되는 시점을 결정합니다.

Mode

Polarity (CPOL) 

Phase(CPHA) 

SPI_MODE0 

SPI_MODE1

0

1

SPI_MODE2

1

0

SPI_MODE3

 

SPI_MODE0 : CPOL = 0, CPHA = 0. 클록의 상승 에지에서 데이터가 샘플링되고 하강 에지에서 데이터가 전송됨

SPI_MODE1 : CPOL = 0, CPHA = 1. 클록의 하강 에지에서 데이터가 샘플링되고 상승 에지에서 데이터가 전송됨

SPI_MODE2 : CPOL = 1, CPHA = 0. 클록의 하강 에지에서 데이터가 샘플링되고 상승 에지에서 데이터가 전송됨

SPI_MODE3 : ㅣ점을 결정합니다. CPOL = 1, CPHA = 1. 클록의 상승 에지에서 데이터가 샘플링되고 하강 에지에서 데이터가 전송됨

그림을 보면 이해하기 쉽습니다. (Toggling edge = 전송 시점, Sampling edge = 샘플링 시점)

CPOL = 0 즉, 극성이 0으로 set되어 있다면, LOW에서 시작하는 것을 나타내고, 1로 set이 되어 있다면, HIGH에서 시작하는 것 ,즉, '반전' 형태입니다. 

 

CPHA = 0, 위상이 0으로 set되어 있다면, 제때 전송 / 샘플링을 하게 되고, 위상이 1로 set되어 있다면, 한시점 빨리 전송/ 샘플링 된다고 이해하시면 될 것 같습니다. 

 

 

 

슬레이브를 SPI통신을 가능하도록 설정하려면 세 가지가 필요한데요, 다음과 같습니다.

1. SPI 통신을 사용 가능하도록 설정

2. SPI 통신에서 슬레이브로 동작하도록 함

3. SPI 통신을 통해 데이터가 수신된 경우 인터럽트가 발생하도록 한다.  

 

이 세 가지는 모두 SPI 제어 레지스터인 SPCR(SPI Control Register)의 해당 비트를 설정해 줌으로써 가능합니다. 

 

SPCR 중 6번 비트인 SPE(SPI Enable) 비트를 1로 설정해 통신을 가능하게 만들고,

4번 비트인 MSTR(Master / Slave Select)가 1이면 Master로 동작, 0으로 설정하면 Slave 모드로 동작합니다. MSTR의 default값이 0이므로 설정하지 않아도 됩니다.

7번 비트인 SPIE(SPI Interrupt Enable)비트를 1로 설정하면 SPI 통신에 의한 인터럽트 발생이 허용됩니다. 

 

여기에는 _BV 매크로를 이용한 비트 연산을 통해 쉽게 설정이 가능합니다.

_BV 매크로는 해당 위치의 비트를 1로 설정하는 마스크를 생성하는 매크로 입니다. 

코드를 한번 보시면서 이해해 보시겠습니다. 

 

 

 


// ====================================================================
// SPI 슬레이브 모드를 하기 위한 아두이노 예제
// 16 MHz 아두이노는 > 초당 500개 단어 가능
// J.Beale July 19 2011
// ====================================================================

#define SCK_PIN   13  // D13 = pin19 = PortB.5
#define MISO_PIN  12  // D12 = pin18 = PortB.4
#define MOSI_PIN  11  // D11 = pin17 = PortB.3
#define SS_PIN    10  // D10 = pin16 = PortB.2

#define UL unsigned long
#define US unsigned short

void SlaveInit(void) {
 // Set MISO output, all others input
 pinMode(SCK_PIN, INPUT);
 pinMode(MOSI_PIN, INPUT);
 pinMode(MISO_PIN, OUTPUT);  // (only if bidirectional mode needed)
 pinMode(SS_PIN, INPUT);

 /*  Setup SPI control register SPCR
 SPIE - 1로 설정시 Interrupt 가능
 SPE - 1로 설정시 SPI on
 DORD - 데이터 전송. 1로 설정시 LSBFIRST, 0으로 설정시 MSBFIRST
 MSTR - 1로 설정시 Master 모드, 0으로 설정시 Slave 모드
 CPOL - 극성
 CPHA - 위상
 SPR1 and SPR0 - 속도 설정, 00 가장 빠름 (4MHz) 11 가장느림 (250KHz)   */
 
 // 적절한 SPI 모드 세팅
 // SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); 
}

// SPI 상태 레지스터: SPSR
// SPI 데이터 레지스터: SPDR

// ================================================================
// high-order byte 순서로 2바이트 읽는 함수
// ================================================================
unsigned short Read2Bytes(void) {
   union {
   unsigned short svar;
   byte c[2];
 } w;        // 분리된 바이트로, 2-바이트 단어에 접근
 
 while(!(SPSR & (1<<SPIF))) ; // 8비트를 받으면 SPIF 비트 세트
 w.c[1] = SPDR;               // high-order 바이트 저장
 while(!(SPSR & (1<<SPIF))) ; // 8비트를 받으면 SPIF 비트 세트
 w.c[0] = SPDR;               // low-order 바이트 저장
 return (w.svar); // unsigned short 값을 전송
}

void setup() {
 Serial.begin(115200);
 SlaveInit();  // slave mode 셋
 delay(10);
 Serial.println("SPI port reader v0.1"); 
}

// ============================================================
// main loop: 외부 SPI 마스터로부터 짧은 단어 (2 bytes)를 읽음 
// 시리얼 포트를 통해 데이터를 전송
// 16MHz 아두이노에서는 초당 500단어 이상을 작업 가능
// ============================================================
void loop() {
 unsigned short word1;
 byte flag1;

    // SS_PIN = Digital_10 = ATmega328 Pin 16 =  PORTB.2
   // Note: digitalRead() 4.1 마이크로초 걸림
   // NOTE: SS_PIN 은 SPI 모듈이 활성화 상태일 때 적절히 작동할 수 없음
   while (digitalRead(SS_PIN)==1) {} // SlaveSelect가 Low 상태일때 까지 대기 (active)
   
   SPCR = (1<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0); // SPI on
   word1 = Read2Bytes();          // read unsigned short value
   SPCR = (0<<SPE)|(0<<DORD)|(0<<MSTR)|(0<<CPOL)|(0<<CPHA)|(0<<SPR1)|(1<<SPR0);  // SPI off
 
//    float seconds = millis()/1000.0;  // time stamp takes more serial time, of course
//    Serial.print(seconds,3);   
//    Serial.print(",");
   Serial.print(word1);
   Serial.println();

}  // end loop()

 

728x90