[아두이노] [강좌] 33. SPI 통신 (2) – SPI 함수 알아보기
이번 강좌에서는 SPI 통신을 사용하기 위해 아두이노에서 제공하는 함수들에 대해 알아보자.
사실 SPI 통신을 사용하는 센서나 모듈이 많음에도 불구하고 SPI 함수를 직접 다룰 일은 많지 않다. SPI 통신을 사용하는 모듈은 대부분 길이가 긴 데이터를 주고받고, 또 동작을 위한 단계가 복잡한 경우가 많기 때문에 보통은 해당 모듈의 제조사에서 라이브러리 소스를 제공하거나, 아두이노에서 제공하는 라이브러리를 사용한다.
예를 들어 이더넷 또는 Wifi 모듈의 경우 SPI를 사용하지만, 대부분의 모듈 제조사에서 자신들의 라이브러리를 제공하고 있으며, SD 쉴드의 경우 아두이노에서 “SD”라는 라이브러리를 제공하여 사용하도록 하고 있다. Wifi나 SD 쉴드를 직접 제어하기 위해 소스를 구현하고자 한다면, 일주일 밤낮을 새도 못할 것 같다. 물론 뚝딱 할 수 있는 고수들은 많겠지만. (그런 고수가 내 블로그 글 읽을 일 없을 테니까 ㅠㅠㅋㅋㅋㅋㅋㅋ)
아무튼, 그렇지만 간혹 SPI 통신을 직접 사용해야 하는 경우도 있고, 알아둬서 나쁠 일이 없으므로 배워보도록 하자.
지난 강좌에서 설명한대로, SPI 통신을 위해서는 MOSI, MISO, SCK, SS 핀이 필요하고, 각 핀은 아두이노 보드의 50~53번 핀에 할당되어 있다. 그 중 SS 핀은 변경이 가능한데, 만일 하나의 슬레이브만을 사용한다면 그대로 53번을 SS 핀으로 사용하면 되고(물론 변경해도 되고), 둘 이상의 슬레이브를 사용한다면 하나는 53번에, 나머지는 다른 핀에 연결해야 한다. 슬레이브 하나 당 SS 핀 하나.
만일 SS 핀을 변경했다면 소스 상에서도 SS 핀을 변경해줘야 한다. 음? SS 핀을 변경하는 방법은 맨 마지막에 “*참고”에서 다시 자세히 설명.
일단 함수 설명 고고!
SPI 함수는 SPI.h 파일에 정의되어 있으므로 “#include <SPI.h>” 구문을 추가한 후 사용할 수 있다.
SPI.begin()
SPI.begin(ss) – Due Only
SPI 통신을 위해 통신 포트를 초기화한다. 듀에(Due) 외의 보드에서는 매개 변수가 없으며, 듀에의 경우에만 매개 변수로 SS 핀의 번호를 지정할 수 있다. 아, 듀에. 탐난다. ㅋㅋㅋㅋㅋ
매개 변수
(듀에 외의 보드에서는 매개 변수 없음) ss – 듀에에서만 사용하는 매개 변수. SS 핀 번호를 설정한다.
|
SPI.setBitOrder(order)
데이터 전송 시 최상위 비트를 먼저 전송할지, 최하위 비트를 먼저 전송할지를 설정한다. SPI 공통의 설정을 변경하는 것이므로, 슬레이브 간에 설정이 다를 경우에는 SPI.beginTransaction() 함수를 이용해서 설정해야 한다. 듀에의 경우 매개 변수로 SS 핀이 추가되지만, 따로 설명은 생략하도록 하겠다. 듀에는 공식 홈페이지를 참고하세용.
매개 변수
order – LSBFIRST 또는 MSBFIRST 중에 선택하여 사용한다.
LSBFIRST – 최하위 비트부터 전송 MSBFIRST – 최상위 비트부터 전송 (default)
|
SPI.setClockDivider(divider)
SPI 통신 속도 설정을 위해 클럭 나누기 값(-_-)을 설정한다. 몇 클럭에 한 비트를 보낼 건지 정한다고 생각하면 쉽다. 지정된 값들 중 하나를 선택하여 사용할 수 있다. 이 함수 역시 공통으로 사용하는 SPI의 속도를 변경하므로 슬레이브 간의 설정이 다를 경우SPI.beginTransaction() 함수를 사용하여 각각 설정해야 한다.
매개 변수
divider – 다음 값들 중 하나를 선택한다.
- SPI_CLOCK_DIV2 - SPI_CLOCK_DIV4 - SPI_CLOCK_DIV8 - SPI_CLOCK_DIV16 - SPI_CLOCK_DIV32 - SPI_CLOCK_DIV64 - SPI_CLOCK_DIV128 |
예를 들어 ‘SPI_CLOCK_DIV8’을 선택할 경우 8 클럭 당 한 비트씩 전송하게 되므로, 16MHz의 메인 클럭을 가지는 메가(Mega 2560/ADK) 또는 우노(Uno)의 경우 2MHz(=16MHz/8)의 속도로 통신을 하게 된다. 2,000,000bps의 속도를 가지게 된다는 뜻. (1M = 1,000,000)
SPI.setDataMode(mode)
데이터 전송 시 사용할 모드를 선택한다. 모드는 4가지가 있으며, 클럭 신호를 HIGH→LOW(Falling)부터 시작할지, LOW→HIGH(Rising)부터 시작할지, 클럭이 HIGH→LOW(Falling)가 될 때 데이터를 쓸 건지, LOW→HIGH(Rising)가 될 때 데이터를 쓸 건지를 정하는 함수이다.
음?? 뭐라규??
다음 표를 보자.
| 클럭 시작 | 클럭 종료 | ||
모드 | 신호 | 데이터 | 신호 | 데이터 |
0 | Rising | 읽기 (Sample) | Falling | 쓰기 (Setup) |
1 | Rising | 쓰기 | Falling | 읽기 |
2 | Falling | 읽기 | Rising | 쓰기 |
3 | Falling | 쓰기 | Rising | 읽기 |
음. 그렇군.
다음은 메가의 칩인 ATmega2560의 데이터 시트 중 모드에 관한 부분이다. 타이밍도를 보면 이해하기 쉬울 듯.
하하하.
암튼 매개 변수.
매개 변수
mode – 다음 중 하나를 선택하여 설정한다.
- SPI_MODE0 (default) - SPI_MODE1 - SPI_MODE2 - SPI_MODE3 |
SPI.end()
SPI 통신을 종료하고, 사용하던 핀을 일반 디지털 핀으로 전환한다.
매개 변수
없음 |
SPI.beginTransaction(SPISettings)
연결된 슬레이브가 두 개 이상일 경우 슬레이브마다 SPI 통신 설정이 다를 수 있는데, 이럴 경우 각각의 통신마다 설정 값을 변경하기 위해 사용한다.
설정을 위해서는 ‘SPISettings’라는 클래스가 사용되는데, SPISettings 클래스를 선언할 때 생성자에 설정 값을 설정하여 객체를 생성한 후SPI.beginTransaction() 함수에 매개 변수로 전달한다.
SPISettings 클래스의 생성자는 다음과 같다.
SPISetting(speed, order, mode)
SPI.setOrder() 함수에 사용된 매개 변수와 SPI.setDataMode()에 사용된 매개 변수가 각각 order와 mode에 들어가며, speed 값은 실제 통신 속도 값을 넣는다. 2MHz를 사용하고 싶으면 2000000을, 1MHz를 사용하고 싶다면 1000000을 사용한다. 각각SPI.setClockDivider() 함수에 매개 변수로 CLOCK_DIV8, CLOCK_DIV16을 사용한 것과 같은 역할을 한다.
매개 변수
speed – 통신 속도 값 order – LSBFIRST 또는 MSBFIRST mode – SPI_MODE0~3 |
SPI.endTransaction() 함수까지 알아본 후 사용 예제를 살펴보도록 하자.
SPI.endTransaction()
다른 슬레이브 기기를 설정하기 위해 이 전의 설정을 종료한다. 매개 변수는 없다.
다음 예제는 두 개의 슬레이브를 각각 다른 설정으로 통신하는 예제 소스이다. 출처는 아두이노 공식 홈페이지.
SPISettingsTest.ino |
#include <SPI.h>
int ssA = 20; // 슬레이브 A의 SS 핀 int ssB = 21; // 슬레이브 B의 SS 핀
SPISettings settingA(2000000, MSBFIRST, SPI_MODE1); SPISettings settingB(16000000, LSBFIRST, SPI_MODE3);
byte val1, val2, val3;
void setup() { pinMode(ssA, OUTPUT); pinMode(ssB, OUTPUT);
SPI.begin(); }
void loop() { SPI.beginTransaction(settingA); // 슬레이브 A와 통신 시작 – settingA로 설정
digitalWrite(ssA, LOW); // 슬레이브 A 선택 신호(LOW) 출력 val1 = SPI.transfer(0); // 데이터 전송/수신 val2 = SPI.transfer(100); // 데이터 전송/수신 digitalWrite(ssA, HIGH); // 슬레이브 A 선택 해제 신호(HIGH) 출력 SPI.endTransaction(); // 슬레이브 A와 통신 종료 – 설정 해제
SPI.beginTransaction(settingB); // 슬레이브 B와 통신 시작 – settingB로 설정 digitalWrite(ssB, LOW); // 슬레이브 B 선택 신호(LOW) 출력 val3 = SPI.transfer(val1+val2); // 데이터 전송/수신 digitalWrite(ssB, HIGH); // 슬레이브 B 선택 해제 신호(HIGH) 출력 SPI.endTransaction(); // 슬레이브 B와 통신 종료 – 설정 해제
if(val3) { // ... } } |
어렵지 않다. 그럼 데이터를 전송하고 수신하는 함수를 알아보자.
byte SPI.transfer(data)
SPI 통신은 전송과 수신이 동시에 이뤄지기 때문에 전송 함수, 수신 함수가 따로 있지 않다. 전송하면 수신되는거. 수신된 데이터가 반환 값으로 반환된다.
매개 변수
data – 전송할 데이터 (byte 형)
반환 값
byte – 수신된 데이터 (byte 형) |
실제 SPI 통신에 사용되는 함수는 SPI.transfer() 함수 뿐이고, 나머지는 다 설정 함수네. 유의해야 할 점은 SPI.transfer() 함수가 사용되기 전 데이터를 전송할 슬레이브의 SS 핀을 LOW로 출력하고, 전송이 완료되면 HIGH로 변경해야 한다는 것.
함수 몇 개 안되는데, 왜 이렇게 내용이 길지…
다음 강좌에서는 직접 SPI 통신 함수를 사용해야 하는 몇 안 되는 센서들 중 하나인 ‘기압 센서’를 가지고 실습을 해보도록 하겠다. 그럼 안녕!