ARDUINO Open Physical Computing Platform 오탈자, 문의및보완이필요한내용은 으로알려주세요.
Chapter 20. I2C 와 SPI 통신을이용한아두이노연결 SPI(Serial Peripheral Interface) 는 I2C(Inter-Integrated Circuit) 와더불어마이크로컨트롤러와주변장치사이에디지털정보를간편하게전송할수있는방법을제공하기위해만들어진통신프로토콜이다. 아두이노소프트웨어에는기본적으로 SPI와 I2C를위한라이브러리인 SPI 라이브러리와 Wire 라이브러리가포함되어있다. SPI와 I2C 중어느것을선택하느냐는연결하고자하는장치에따라결정된다. 두가지모두지원하는장치도있지만대부분의장치는둘중하나만을지원한다. I2C의장점은신호선 2개만연결하면사용할수있다는점이다. 두신호선을바탕으로여러장치를연결하여제어할수있으며신호가올바르게수신되었음을알려주는승인신호도받을수있다. 하지만 I2C는 SPI에비해속도가느리다는단점이있다. 또한 I2C는반이중 (half-duplex) 방식으로송신과수신이동시에이루어질수없기때문에양방향통신이필요한경우에는전송속도가더느려진다. 또한신뢰할수있는신호전송을위해서는풀업저항을사용하여야한다. 이에비해 SPI의장점은속도가빠르며전이중 (full-duplex) 방식으로송신과수신이동시에이루어질수있다. 하지만활성장치를선택하기위해추가연결이필요하므로여러장치를연결하여사용하기위해서는연결선이증가하는단점이있다. 아두이노에서는일반적으로고속의데이터전송이필요한경우에는 SPI를사용하며많은데이터전송을요구하지않는센서의경우 I2C가사용된다. 한가지주의할점은 SPI나 I2C는통신방법만을정의할뿐전송되는데이터는장치에따라달라진다는점이다. 즉, 동일한데이터가전송된다고하여도송수신하는장치에서의의미는전혀달라질수있으므로장치에따른데이터의의미는 datasheet를참고하여야한다. 1. I2C I2C는필립스에서저속의주변기기를연결하기위해개발한규격으로 SCL(serial clock) 과 SDA(serial data) 두개의연결을가진다. 표준 Arduino UNO 보드에서는 SCL을위해아날로그 5번핀을, SDA를위해아날로그 4번핀을사용하며 Arduino Mega의경우 SCL을위해디지털 20번핀을, SDA를위해디지털 21번핀을사용한다. I2C 버스의한쪽에는유일한마스터 (master) 장치가연결되어다른슬레이브 (slave) 장치들과의정보전송을제어하게되며일반적으로아두이노가마스터의역할을수행한다. 슬레이브장치들은 7비트의고유주소에의해식별되므로최대 개의슬레이브장치가연결될 - 105 -
수있다. 그림 1 은 I2C 버스를통해여러개의슬레이브장치들이연결된예를보여주고있 다. 그림 1. I2C 연결 2. I2C 를이용한아두이노간통신 두대의아두이노를 SPI 통신을이용하여연결해보자. 두대의아두이노를먼저그림 2 에서 와같이연결한다. - 106 -
그림 2. 두대의아두이노를 I2C 통신을통해연결한회로도 I2C 통신은클록과데이터를위한두개의핀만을연결하면된다. 마스터는슬레이브로 1초간격으로 requestfrom 함수로 1바이트크기의데이터를요구하고데이터를수신하여 Serial 로출력한다. 마스터는데이터요구를위해슬레이브의주소를알고있어야하며슬레이브주소는 4번으로설정하였다. 코드 1. Master #include <Wire.h> #define SLAVE 4 // 슬레이브주소 void setup() { Wire.begin(); Serial.begin(9600); // I2C 통신을위한 Wire 라이브러리초기화 // 직렬통신초기화 void loop() { i2c_communication(); delay(1000); void i2c_communication() { Wire.requestFrom(SLAVE, 1); // 슬레이브로데이터요구및수신데이터처리 // 1 초대기 // 1 바이트크기의데이터요구 char c = Wire.read(); Serial.println(String(c, DEC)); // 수신데이터읽기 // 수신데이터출력 슬레이브는 Wire 라이브러리를초기화할때마스터에서식별가능한주소를지정해주어야한 다. 또한마스터에서데이터전송요구가발생한경우이를처리하는핸들러함수를 onrequest 함수를통해등록해주어야한다. 코드 2. Slave - 107 -
#include <Wire.h> #define SLAVE 4 byte count = 0; // 마스터로송신할카운터데이터 void setup() { Wire.begin(SLAVE); // Wire 라이브러리초기화. 주소를지정해야한다. // 마스터의데이터전송요구가있을때처리할함수등록 Wire.onRequest(sendToMaster); void loop () { void sendtomaster() { // 카운터값을증가시키고마스터로전송 Wire.write(++count); 그림 3. 코드 1 실행결과 3. SPI - 108 -
SPI는 I2C와다르게송신과수신을위한별도의연결선인 MOSI(Master Out Slave In) 와 MISO(Master In Slave Out) 그리고클록 (SCLK) 이존재한다. IC2에서는특정슬레이브를지정하기위해소프트웨어적인주소를사용하는것과달리 SPI는하드웨어적인연결인 SS(Slave Select) 가존재하여총 4개의연결선을가진다. MOSI, MISO, SCLK는모든슬레이브에공통이며 Arduino UNO의경우디지털 11, 12, 13번핀으로연결한다. Arduino UNO 의경우디지털 10번핀이 SS 핀으로정의되어있어많이사용하지만마스터로사용하는경우슬레이브별로하나의 SS 연결이필요하므로연결가능한디지털핀은모두 SS로사용할수있다. 10번핀은아두이노가 SPI 통신에서슬레이브로선택될수있도록하기위해사용하는핀이기도하다. 아두이노가슬레이브로동작하기위해서는 10번핀이 INPUT으로설정된상태에서 LOW 값이인가되어야한다. (SS가 LOW인장치는현재마스터와통신할수있는장치를나타낸다.) 아두이노프로그램에서제공되는 SPI 라이브러리는아두이노의마스터모드만을지원하며초기화과정에서해당핀의입출력상태를자동으로설정하므로마스터모드에서사용할때에는초기화이후슬레이브에연결된 SS 핀들을 HIGH 상태로설정해주어야한다는점만주의하면된다. SPI 신호 Arduino UNO Arduino Mega MOSI 11 51 MISO 12 50 SCLK 13 52 SS 10 53 표 1. SPI 연결핀 - 109 -
그림 4. SPI 연결 SPI는데이터전송을위해세부적인내용까지정의하고있지는않으며장치에따라약간씩다른방법으로구현하고있으므로데이터직렬화방법 (MSB 우선또는 LSB 우선 ), 클록동기화방법, 전송속도등에관하여장치의 data sheet를면밀히살펴보아야한다. SPI 라이브러리의전송속도는디폴트값으로 4MHz로설정되어있다. 4. SPI 라이브러리 SPI는전이중방식의동기식직렬통신프로토콜로짧은거리에있는주변기기들과통신하는용도로만들어졌으며아두이노프로그램에는 SPI 라이브러리가포함되어있다. SPI 통신을통해송수신되는데이터의의미는주변장치에따라달라지므로 SPI 라이브러리에는기본적인설정및데이터전송함수들만을정의하고있다. 실제 SPI 라이브러리에서정의하고있는클래스는 SPIclass이며이의전역객체인 SPI를통해 SPI 통신이이루어진다. Ÿ begin void begin(void) 반환값 : 없음 SPI 통신을초기화한다. Ÿ end void end(void) 반환값 : 없음 SPI 통신을종료한다. Ÿ setbitorder - 110 -
void setbitorder(uint8_t bitorder) bitorder : 데이터송수신시비트순서 반환값 : 없음 SPI 버스로데이터가직렬화되어전송될때전송순서를설정한다. LSBFIRST 또는 MSBFIRST 중하나의값을가진다. Ÿ setclockdivier void setclockdivier(uint8_t rate) rate : 분주비율 반환값 : 없음 시스템에서사용하는클록에상대적인분주비율을설정한다. AVR 기반의보드에서는 2, 4, 8, 16, 32, 64, 128 중하나의값을사용할수있으며이값들은 SPI_CLOCK_DIV2에서 SPI_CLOCK_DIV128 까지의상수로정의되어있다. 디폴트값은 SPI_CLOCK_DIV4로시스템클록의 1/4 속도로데이터를전송한다. Arduino UNO의경우 16MHz 클록을사용하므로 4MHz가 SPI의기본주파수에해당한다. Ÿ setdatamode void setdatamode(uint8_t mode) mode : 전송모드 반환값 : 없음 데이터의전송모드를설정한다. 전송모드는클록의 phase와 polarity 조합에따라 4 가지로나눌수있으며각각 CPHA (Clock PHAse) 와 CPOL (Clock POLarity) 또는 SPH (Signal PHase) 와 SPO (Signal POlarity) 라고부른다. phase는데이터가샘플링되는시점을결정한다. CPHA = 0이면클록의상승에지에서데이터가샘플링되고 CPHA = 1이면하강에지에서데이터가샘플링된다. polarity는클록이비활성상태일때의기본값을결정한다. CPOL = 0이면기본값은 0이며 CPOL = 1이면기본값은 1이된다. 이두가지조합에의해모두네가지의전송모드가존재한다. - 111 -
Mode Polarity (CPOL) Phase (CPHA) SPI_MODE0 0 0 SPI_MODE1 0 1 SPI_MODE2 1 0 SPI_MODE3 1 1 표 2. SPI 통신에서의전송모드 그림 5 는각모드에따라샘플링이이루어지는시점과클록의관계를나타낸것이다. 그림 5. 모드에따른클록및샘플링시점 polarity 와 phase 의의미를완전히이해할필요는없지만마스터와슬레이브사이에동일한 모드를사용하여야만정상적인통신이가능하므로기기가사용하는모드를 data sheet 에서 반드시확인하여야한다는점은기억하기바란다. Ÿ transfer byte transfer(byte data) data : 송송할데이터 반환값 : 수신된데이터 SPI 버스를통해한바이트의데이터를보내고받는다. 송신과수신은동시에진행된다. - 112 -
5. SPI 를이용한아두이노간통신 두대의아두이노를 SPI 통신을이용하여연결해보자. 두대의아두이노를먼저그림 6 에서 와같이연결한다. 그림 6. 두대의아두이노를 SPI 통신으로연결하는회로도 두대의아두이노중하나는마스터로동작하며 1초간격으로 Hello World 문자열을전송한다. 수신측에서는개행문자인 \n 을문자열의끝으로인식하여처리하므로문자열끝에개행문자가추가되어있다. 아두이노사이의 SPI 통신에서전송속도가높은경우데이터가정상적으로송수신되지않으므로분주비를낮추어전송속도를낮춰준다. 이경우슬레이브에서도마스터에서와동일한속도로설정해주어야한다. 마스터에서문자열의전송하는코드의예는코드 3과같다. 코드 3. Master #include <SPI.h> - 113 -
void setup (void) { SPI.begin (); // SPI 통신초기화 // pins_arduino.h에 SS는기본적으로 10번핀으로정의되어있다. digitalwrite(ss, HIGH); // 슬레이브가선택되지않은상태로유지 // 속도가빠른경우데이터가정확하게전달되지않으므로 // 분주비를높여전송속도를낮춘다. SPI.setClockDivider(SPI_CLOCK_DIV16); void loop (void) { const char *p = "Hello, World\n"; digitalwrite(ss, LOW); // 슬레이브를선택한다. for (int i = 0; i < strlen(p); i++){ // 문자열을전송한다. SPI.transfer(p[i]); digitalwrite(ss, HIGH); // 슬레이브선택을해제한다. delay(1000); 슬레이브측은인터럽트방식으로동작한다. SPI 통신을통해데이터가수신되면자동으로인터럽트서비스루틴 (ISR, Interrupt Service Routine) 이호출되며 ISR의매개변수는 SPI 통신을통한데이터수신인터럽트에해당하는인터럽트번호로 SPI_STC_vect로정의되어있다. SPI 라이브러리의경우마스터모드만을지원하므로슬레이브모드로동작하기위해서는해당레지스터를직접설정하여야하며세가지설정이필요하다. Ÿ SPI 통신을사용가능하도록한다. Ÿ SPI 통신에서슬레이브로동작하도록한다. Ÿ SPI 통신을통해데이터가수신된경우인터럽트가발생하도록한다. - 114 -
이세가지는모두 SPI Control Register 인 SPCR 의해당비트를설정해줌으로써가능하다. SPCR 레지스터는그림 5 와같다. 비트 7 6 5 4 3 2 1 0 SPIE SPE DORD MSTR CPOL CPHA SPR1 SPR0 읽기 / 쓰기 R/W R/W R/W R/W R/W R/W R/W R/W 초깃값 0 0 0 0 0 0 0 0 그림 7. SPCR 레지스터구조 SPCR 레지스터중먼저 SPE(SPI Enable) 비트를 1로설정함으로써 SPI 통신이가능하다. 4 번비트인 MSTR(Master/Slave Select) 비트가 1인경우마스터모드로동작하며 0인경우슬레이브모드로동작한다. 디폴트값이 0이므로설정하지않아도된다. 마지막으로 7번비트인 SPIE(SPI Interrupt Enable) 비트를 1로설정하면인터럽트발생이허용된다. MSTR, SPE, SPIE는각각 4, 6, 7로정의되어있다. #define MSTR 4 #define SPE 6 #define SPIE 7 각비트는 _BV 매크로를이용한비트연산을통해설정할수있다. _BV 매크로는해당위치 의비트만을 1 로하는마스크를생성하는매크로로다음과같이정의되어있다. #define _BV(bit) (1 << (bit)) 따라서 _BV 매크로를통해생성된마스크와비트 OR 연산을수행함으로써해당비트만을 1 로설정할수있으며마스크를반전시켜비트 AND 연산을수행함으로써해당비트만을 0 으 로설정할수있다. - 115 -
코드 4. Slave #include <SPI.h> char buf[100]; // 수신된문자저장을위한버퍼 // pos와 process_it은인터럽트처리루틴에서값을바꾸는변수이므로 // volatile 선언을통해업데이트된값이정확하게반영되도록한다. volatile byte pos; // 수신버퍼에문자를기록할위치 volatile boolean process_it; // 개행문자를만난경우출력하기위한플래그 void setup (void) { Serial.begin (9600); // 수신문자열출력을위한직렬통신초기화 // 디지털핀은디폴트값으로입력으로설정되어있으므로 // MOSI, SCLK, SS는입력으로설정하지않아도된다. // MISO 역시이예에서는사용하지않으므로생략할수있다. pinmode(miso, OUTPUT); // 마스터의전송속도에맞추어통신속도를설정한다. SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI 통신을사용할수있도록레지스터를설정한다. // SPCR : SPI Control Register SPCR = _BV(SPE); // SPE : SPI Enable // SPI 통신에서슬레이브로동작하도록설정한다. // 디폴트값이 0이므로생략할수있다. SPCR &= ~_BV(MSTR); // MSTR : Master Slave Select pos = 0; process_it = false; // 버퍼가비어있으므로 0 번부터수신문자기록 // Serial 로출력할문자열없음 // SPI 통신으로문자가수신될경우인터럽트발생을허용한다. SPCR = _BV(SPIE); // SPIE : SPI Interrupt Enable - 116 -
// SPI 통신으로문자가수신될때발생하는인터럽트처리루틴 ISR (SPI_STC_vect) { byte c = SPDR; // 수신된문자를얻어온다. if (pos < sizeof(buf)){ buf[pos++] = c; // 현재버퍼에저장할공간이있는경우 // 버퍼에수신된문자기록 if (c == '\n'){ process_it = true; // 개행문자를만나면수신된문자열을 Serial 로출력 void loop (void) { if (process_it){ buf[pos] = 0; Serial.print(buf); pos = 0; process_it = false; // Serial로출력할문자열이있는경우 // 문자열의끝표시 // 문자열을 Serial로출력 // 버퍼가비었음을표시 // Serial로출력할문자가없음을표시 - 117 -
그림 8. 코드 4 실행결과 - 118 -