검색하기

SPI를 이용한 관성센서 제어

2018-09-21
SPI 통신의 이해 

 

 

임베디드 시스템 내부의 모듈 간 연결 시 I2C와 함께 많이 사용되는 통신으로써 Serial Peripheral Interface (SPI) 통신은 별도의 클럭신호선을 제공하면서 송수신로가 물리적으로 분리된 단거리 동기식 전이중 전송방식이다. 예를 들어, 온도센서의 데이터를 MCU가 읽거나 EEPROM에 데이터를 기록하고 읽을 때 그리고 10Mbps급 이더넷을 연결 시에도 사용된다. 일부 회사에서는 Synchronous Serial Interface (SSI)라고도 부른다. 특히 많이 사용되는 분야는 부팅용 EEPROM이나 SD카드 접속분야이다.

 

4개의 선로를 사용하는 Serial Peripheral Interface(이하 SPI) 통신은 I2C와 마찬가지로 클럭라인을 별도로 사용하는 동기식(synchronous) 직렬 데이터통신방식이다.

 

이것은 데이터 선로가 하나뿐인 I2C와 달리 물리적으로 분리된 송신 및 수신 데이터 선로를 사용하므로 전이중 통신이 가능한 장점이 있다. 하지만 주소 영역을 사용하지 않으므로 여러 개의 슬레이브 장치에 대한 추가를 위해 각 장치별 Slave select(SS) 신호선이 추가되어야 하는 문제점이 있다. 또한 여러 개의 마스터가 허용되는 I2C와 달리 하나의 마스터에 여러 개의 슬레이브 노드를 연결하는 경우에만 사용 가능하다SPI 통신상의 마스터 노드는 클럭을 제공하고 슬레이브 장치를 선택하는 기능을 가진다. 나머지 노드는 슬레이브 노드로써 개별적인 Slave select(chip select) 선로에 의해 선택된다.

 

SPI 통신은 다음과 같은 종류와 구성을 가진다.

● SCLK(Serial Clock) : 마스터가 생성하는 클럭 신호선로이며, SSIClk, SCK, CLK 등으로 이름으로 사용된다. 최대 40Mbps를 지원한다.

● MOSI(Master Output, Slave Input) : 마스터가 출력하는 직렬 데이터 신호이다. MSB부터 송신된다. SIMO, SSITX, SDI, DIN 등의 이름으로 사용된다.

● MISO(Master Input, Slave Output) : 마스터가 수신하는 직렬 데이터 신호로써 SOMI, SSIRX, SDO, DO 등의 이름으로 사용된다.

● NCS(Chip select-active low) : 마스터에 의한 슬레이브 선택용 제어 신호로써 SSIFss의 경우도 있다. 선택되지 않는 장치의 MISO 신호값은 하이임피던스값을 유지한다.

 

SPI는 표준화기구를 통한 표준이 없는 사실표준이므로 제조사별로 다양한 변형이 존재한다. 전송단위(위드)의 길이도 상이하고 LSB부터 먼저 송순하거나 MSB부터 먼저 송신하는 등 통신절차도 상이하며 핀 이름도 다른 이유가 여기에 있다. 더욱이 일부 SPI 칩은 슬레이브와 마스터 간 흐름 제어를 위한 신호선을 추가하여 5 Wire를 사용하는 변형도 있다. 또한 일부 변형된 칩은 MOSI MISO 핀을 하나로 통한 3 Wire방식을 지원하기도 한다. 일부는 두 장치 간 연결에만 사용할 경우 Slave select(SS) 신호선을 제거하여 이것을 3 Wire 방식이라고도 부른다.


SPI 통신에 대해서 더 구체적인 동작방식 대해서 알아두면 좋으나, 아두이노 사용자들은 그럴 필요가 없다. 왜냐하면 아두이노를 기반으로 만들어진 라이브러리들을 이용하면 통신방법을 알지 못한다고 해도 SPI 통신을 사용할 수 있기 때문이다. 그 외에도 I2C, UART 시리얼 통신이나 블루투스 등의 통신 또한 아두이노 기반의 라이브러리를 통해 손쉽게 이용할 수 있다. 그렇기 때문에 위에서 도시한 회로와 같이, 아두이노와 사용하고자 하는 장치(센서, 모듈 등)를 잘 연결만 해주면 장치에서 지원하는 통신이 가능해진다. 

 

  

(A) 4-Wire SPI

  

(B) 3-Wire SPI(1:1)

  

(C) 3-Wire SPI(Micro wire)

 

 

 

SPI 통신 모드


SPI통신은 SCK(Serial Clock)에 맞추어 데이터를 교환한다. 교환 시 데이터를 SCK에 동기화하는 방법에는 극성(CPOL : Clock Polarity)과 위상(CPHA : Clock Phase)의 값에 따라 4가지 방법이 존재한다.

극성(CPOL : Clock Polarity)의 경우 클럭(Clock)의 기본값이 0(Low)인 경우에는 0, 클럭의 기본값이 1(High)인 경우에는 1이 되고 위상(CPHA : Clock Phase)의 경우 데이터를 앞쪽 에지(Leading Edge)에서 읽으면 0이고 뒤쪽 에지(Trailing Edge) 에서 읽으면 1이 된다.

이 두 가지 특성의 조합에 의해 아래와 같은 4가지 모드가 존재하게 된다.     

 


 

 


 

 

 

9축 모션센서  |  MPU-9250

 

우리가 사용할 센서는 9축 모션센서 MPU-9250이다. 그림 1-1GY-9250MPU-9250을 사용한 아두이노용 모듈 이다. MPU-9250은 가속도센서(3), 자이로센서(3)와 지자기센서(3)가 모두 포함된 9축 센서로, 3차원 공간에서 센서의 가속도와 각속도, 주위의 지자기를 측정해 벡터형태로 값을 나타내게 되고 I2C통신과 SPI통신을 모두 지원한다. 또한, MPU-9250은 모듈 내부에 전원 레귤레이터, 레벨 쉬프터가 내장되어 있어 3.3V ~ 5V 전원을 모두 사용할 수 있다.


아래의 표는 MPU-9250의 각 센서 별 특성을 나타낸다.

 


 

                                            < MPU-9250 내부센서 별 특성  > 



9축 모듈 GY-9250 앞면 / 뒷면 


먼저 SPI통신을 사용하기 위해서는 핀맵1-1과 같이 아두이노와 MPU-9250을 연결한다 

 


 

 


  

 

< SPI를 이용한 MPU-9250과 아두이노 연결 회로도 > 

 

소개되는 예제들은 아두이노 우노 기반으로 회로도와 프로그램이 설계되었으며, 라두이노를 이용해서 동일한 예제를 구현할 수 있도록 라두이노용 회로도와 프로그램을 추가한다.

라두이노용 회로도와 프로그램은 다음과 같으며 본 예제는 동일한 프로그램을 수정 없이 사용한다.

 

 

< SPI를 이용한 MPU-9250과 라두이노용 회로도 > 

 

 

 

 

 

 

 

SPI를 이용한 통신 예제 



 

 


 

 

 

코드 설명 


초기 설정 함수인 setup() 함수에서는 SPI 통신을 초기화 하고 MPU-9250과 연결되었는지 여부를 확인한다. 무한 루프 함수인 loop()함수에서는 3-축 가속도를 읽어 와서 시리얼 통신을 이용하여 PC 화면에 결과 값을 출력한다.

이 예제에서 주요 함수인 MPU9250_readByteSPI()함수는 SPI 통신 방법을 이용하여 아두이노가 MPU-9250의 레지스터 정보를 1-Byte 단위로 읽어온다.


 

SPI 통신에서 데이터를 읽고 쓰는 방법은 위의 그림과 같이 상위비트(MSB)1일 때 데이터를 읽어(Read)오고 0일 때 데이터를 쓴다(Write). 그리고 나머지 7-bits에서 접근 할 레지스터 주소 값이 들어간다. 예를 들어 MPU-9250WHO_AM_I 레지스터 값을 읽기 위해 0x75 | 0x80 = 0xF5 값을 전송해야한다. 그 다음 임의의 더미 데이터(0x00)를 쓰면 그와 동시에 해당 레지스터 주소의 데이터를 수신한다. SPI 통신을 이용하여 데이터를 수신하는 방법은 그림과 같다.

 


 

 

RA : MPU-9250의 내부 레지스터 주소

DATA : 송신 또는 수신 데이터

 

byte c = MPU9250_readByteSPI(WHO_AM_I_MPU9250);


위 코드는 MPU9250_readByteSPI()함수를 이용하여 MPU-9250의 고유 ID을 읽어온다. 반환 받는 값은 MPU-9250의 고유 ID0x71이어야 한다.


이처럼 MPU9250_readByteSPI()함수를 이용하여 원하는 MPU-9250의 레지스터 값을 읽어올 수 있다. 다음 페이지의 표 1-2MPU-9250의 전체 레지스터 맵 중 이 예제에서 사용하고 있는 일부분을 보여 주고 있다. 더 자세한 MPU-9250의 레지스터 맵 정보는 “MPU-9250 Register Map and Descriptions Revision 1.4”를 참고하자

 



 
다음 코드는 loop()함수에서 MPU9250_readByteSPI()함수를 이용하여 X,Y,Z 축의 속도 정보를 읽어 온다. 

 

rawData[0] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_XOUT_H );

rawData[1] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_XOUT_L );

rawData[2] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_YOUT_H );

rawData[3] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_YOUT_L );

rawData[4] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_ZOUT_H );

rawData[5] = MPU9250_readByteSPI(MPU9250_ADDRESS, ACCEL_ZOUT_L );


이렇게 수신된 6-bytes의 데이터 다음 코드에 의해서 각각 16bitsX,Y,Z의 가속도 값으로 변환한다.

 

accelCount[0] = ((int16_t)rawData[0] <;;;;;;;;;<;;;;;;;;; 8) | rawData[1] ;

accelCount[1] = ((int16_t)rawData[2] <;;;;;;;;;<;;;;;;;;; 8) | rawData[3] ;

accelCount[2] = ((int16_t)rawData[4] <;;;;;;;;;<;;;;;;;;; 8) | rawData[5] ;


 

MPU9250의 가속도센서의 초기 측정 범위는 ±2g로 설정되어 있기 때문에 실제 가속도 값으로 변환하기 위해선 2.0gMPU925016-bit 분해능으로 나눈 값(aRes = 2.0f / 32768.0f;) 을 곱한다.


ax = (float)accelCount[0] aRes;

ay = (float)accelCount[1] aRes;

az = (float)accelCount[2] aRes;

 

변환된 3축 가속도 값은 mG로 변환하기 위해 1000을 곱한 후 화면에 출력한다.


Serial.print("ax = ");

Serial.print((int)1000 ax);

Serial.print(" ay = ");

Serial.print((int)1000 ay);

Serial.print(" az = ");

Serial.print((int)1000 az);

Serial.println(" mg");

 

결과 그림은 다음과 같다.

 

 

 

SPI.begin()

설명

SCK, MOSI, SS를 출력으로 설정 SCK, MOSILOW, SSHIGH로 설정하여 SPI 버스 초기화

 

 

SPI.beingTransaction(SPISettings(speedMaximum, dataOrder, dataMode))

매개변수

speedMaximum : 최대 통신 속도

dataOrder : MSBFIRST 또는 LSBFIRST

dataMode : SPI_MODE0, SPI_MODE1, SPI_MODE2, SPI_MODE3


설명

연결된 슬레이브의 통신 설정 값을 변경하기 위해 사용

정의된 SPISettings를 사용하여 SPI버스를 초기화

 

SPI.transfer(data)

매개변수

data : 전송할 데이터 (byte)

반환값

byte : 수신된 데이터 (byte)

설명

데이터를 주고 받기위한 SPI의 기본 함수 

 

 

  

SPI.endTransaction()

설명

다른 슬레이브 사용하기 위해 이전의 SPI 통신 설정 종료

 

 

 

Serial.begin(speed)

매개변수


speed : 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200

(초당 비트 수)


설명

직렬 데이터 전송을 위해 데이터 속도를 설정

데이터 통신을 하는 상대방의 통신 속도와 같아야 한다

 

Serial.print(value, fomat) / Serial.println(value)

매개변수

value : 출력 할 모든 데이터 유형

fomat(선택적 매개변수) : 출력 할 데이터의 형식 지정 2진법(BIN), 8진법(OCT), 10진법(DEC) 16진법(HEX) 또는 부동 소수점의 경우 소수 자릿수를 지정

설명

데이터를 전송하는 함수

매개 변수에는 여러 가지 타입이 사용 될 수 있는데 ASCII 텍스트로 직렬포트에 데이터를 전송하기 때문에 매개 변수의 선언된 형식에 따라서 전혀 다른 데이터가 출력될 수 있기 때문에 주의해야한다.


예시)

int a = 97;

float b = 97;

char c = 97;


void setup()

Serial.begin(9600);


Serial.println(97);

Serial.println(a);

Serial.println(b);

Serial.println(c);

Serial.println(97, BIN);

Serial.println(97, OCT);

Serial.println(97, DEC);

Serial.println(97, HEX);

Serial.println(97.123456);

Serial.println(97.123456, 0);

Serial.println(97.123456, 5);

 

- 출력 값 : 97

- 출력 값 : 97

- 출력 값 : 97.00

- 출력 값 : a

- 출력 값 : 1100001

- 출력 값 : 141

- 출력 값 : 97

- 출력 값 : 61

- 출력 값 : 97.12

- 출력 값 : 97

- 출력 값 : 97.12346


위에 보이는 예시처럼 모두 같은 97이라는 숫자를 입력했지만 int, float, char 등등 변수가 선언된 종류에 따라 출력 값이 달라진다. 특히 문자(char)로 선언한 경우에는 97(10진수)‘a’라는 문자로 출력이 되었는데 이것은 ASCII 코드표에 97이라는 10진수는 ‘a’라는 문자를 숫자로 표현한 값이라고 생각하면 된다. 그리고 선택적 매개변수(fomat)를 사용해서 다른 형식의 값으로 출력 할 수 있다.

 

delay(ms)

매개변수

ms : 일시정지할 시간

설명

매개변수로 지정된 시간 동안 프로그램 일시정지