메뉴 건너뛰기

제어 - RaspberryPi.NCLab

SPI를 이용한 온도센서 사용 예제

소스파일 : spi_control.c


1) SPI(Serial Peripheral Interface) 이론


 직렬 주변기기 인터페이스 버스아키텍처 전이중 통신 모드로 동작하는 모토로라 아키텍처에 이름을 딴 동기화 직렬 데이터 연결 표준이다. 장치들은 마스터/슬레이브 모드로 통신하며, 마스터 장치:라즈베리파이로 데이터 프레임을 초기화한다. 여러 슬레이브 장치들은 개별 슬레이브 셀렉트(칩 셀렉트) 라인과 함께 동작할 수 있다.

더 구체적인 설명은 SPI wiki 페이지 참고

http://ko.wikipedia.org/wiki/%EC%A7%81%EB%A0%AC_%EC%A3%BC%EB%B3%80%EA%B8%B0%EA%B8%B0_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4_%EB%B2%84%EC%8A%A4


SPI 버스는 4가지 다음과 같이 4가지 논리 신호를 지정한다.


SPI Table.PNG


SCLK

: 동기화를 위한 신호, 통신 Clock에 해당한다.

MOSI ( Master Output Slave Input )

: Master에서 Slave로 가는 방향성이 있는 데이터 선

MISO ( Master Input Slave Output )

: Slave에서 Master로 가는 방향성이 있는 데이터 선

SS ( Slave Select )

: 하나의 Master 장치가 여러 개의 Slave 장치와 연결되어 있는 경우, 하나의 Slave를 선택하기 위한 선, 우리 예제에서는 GPIO25를 SS로 사용한다.


2) LM35 : 온도 센서

LM35.jpg


아날로그 출력을 가지는 온도 센서로 우리 예제에서 VCC와 GND는 각각 라즈베리파이의 5V, GND핀에 연결되고, 아날로그 출력은 MCP3208의 CH0에 연결된다.






3) MCP3208 : Analog Digital Converter

MCP3208_핀정보.PNG


 MCP3208은 최대 8개의 채널을 사용할 수 있으며, 한 번에 12bit의 데이터를 전송한다. 우리 예제에서는 온도 센서의 아날로그 출력을 디지털로 변환시켜서 라즈베리파이(Master)와 통신하는 Slave 역할을 한다.





MCP3208 Timing Diagram

mcp3208_TimingDiagram.PNG

a) MCP3208을 사용하기위해 CS(GPIO25)를 low로 내린다.

b) 라즈베리파이에서 몇 번 채널을 read할지 D0~2에 써서 MOSI(DIN)로 보낸다.

c) 해당 채널의 아날로그 출력을 디지털 신호로 MISO(Dout)로 보내준다.


mcp3208_configurationtable.PNG




4) 코드 구현

4-1) 정의 & 함수 선언

#define INPUT               0
#define OUTPUT           1
#define LOW               0
#define HIGH              1


#define BLOCK_SIZE      (4*1024)
#define BCM2708_PERI_BASE 0x20000000
#define GPIO_BASE       (BCM2708_PERI_BASE + 0x00200000)

#define SPI_CHANNEL 0
#define SPI_SPEED   1000000  // 1MHza

static unsigned char     spiMode   = 0 ;
static unsigned char     spiBPW    = 8 ;
static unsigned short    spiDelay  = 0;
static unsigned int    spiSpeeds [2] ;

static volatile unsigned int *gpio ;
static int         spiFds [2] ;

// 25번 GPIO 핀의 Fuction Select을 한다.
void pin_25_Mode( int mode);

// Write으로 Fuction Select된 25번 GPIO의
// HIGH 또는 LOW 값을 입력한다.
void digitalWrite_pin_25(int value);

// ADC의 인자로 넘긴 채널로 부터 12bit 값을 읽어온다.
int read_mcp3204_adc(unsigned char adcChannel);


// read_mcp_3204_adc 함수 내부에서 불러서 실제적으로 SPI 통신을 하는 함수
int SPIDataRW (int channel, unsigned char *data, int len);

4-2) main

 int main (void)
 {
     int adcChannel = 0;
     int adcValue   = 0;
     int fd;

     if ((fd = open ("/dev/mem", O_RDWR | O_SYNC) ) < 0)
     {
          return -1 ;
     }
	// 25번 GPIO는 MCP_3208와 SPI Master간의 Chip Select 신호로 쓰이며
	// 통신 동기화를 맞추는 역할을 한다.MCP_3208의 Timing Diagram을 참고/
     gpio = (unsigned int *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE,
             MAP_SHARED, fd, GPIO_BASE) ;

     if ((int)gpio == -1)
     {
         return -1 ;
     }
 
	// 리눅스에서 모든 주변장치(Peripheral)는 파일로 관리된다.
	// spi와 관련된 파일을 open하고 필요한 드리이버 API를 사용하게 된다.
     if ((fd = open ("/dev/spidev0.0", O_RDWR)) < 0)
     {
         printf("/dev/spidev0.0\n");
         return 1 ;
     }

     spiFds    [0] = fd ;

     pin_25_Mode(OUTPUT); // CS 신호로 사용하기 위해서 OUTPUT으로 
	// Fuction Set 한다.

     while(1)
     {
	// 값을 1초 마다 읽는다. 이때 300 - ((adcValue*500)>>10
	// 는 온도 센서 제조업체에서 제공하는 스케일링된 값이다.
         adcValue = read_mcp3208_adc(adcChannel);
         printf("adc0 Value = %u\n", 300 - ((adcValue*500)>>10));
         sleep(1);
     }

     return 0;

 }

4-3) SPIDataRW

 int SPIDataRW (int channel, unsigned char *data, int len)
 {	 //SPI 통신은 spi_ioc_transfer 구조체라는 일관된 인터페이스를
	 //통해 SPI Master/Slave 간 통신을 한다.
     struct spi_ioc_transfer spi ;

     channel &= 1 ;
	 // data를 통해 지정된 채널의 값을 읽어오도록 한다.
	 // tx_buf가 MCP_3208의 Timing Diagram에 D0, D1, D2에 해당한다.
	 // tx_buf를 그대로 이용하여 rx_buf로 12bit ADC된 값을 받는다
     spi.tx_buf        = (unsigned long)data ;
     spi.rx_buf        = (unsigned long)data ;
     spi.len           = len ;
     spi.delay_usecs   = spiDelay ;
     spi.speed_hz      = 1000000;
     spi.bits_per_word = spiBPW ;
	 //  spi_ioc_transfer 구조체의 configuration을 한다.

	 // SPI_IOC_MESSAGE(1) Command는 데이터를 써넣는 매크로인 _IOW로 
	 // 치환된다. 자세한 내용은 Linux/include/uapi/linux/spi/spidev.h을 참고
	 // ioctl 함수를 통해 쓰기 명령과 버퍼의 주소를 드라이버로 보낸다.
     return ioctl (spiFds [channel], SPI_IOC_MESSAGE(1), &spi) ;
 }

4-4) read_mcp3208_adc

 int read_mcp3208_adc(unsigned char adcChannel)
 {
     unsigned char buff[3];
     int adcValue = 0;

	 // MCP3208에서 사용할 채널 번호를 buff[0]과 buff[1]에
	 // 걸쳐 저장한다.
     buff[0] = 0x06 | ((adcChannel & 0x07) >> 2 );
     buff[1] = ((adcChannel & 0x07) << 6);
     buff[2] = 0x00;

     digitalWrite_pin_25(0);  // Low : CS Active

     SPIDataRW(SPI_CHANNEL, buff, 3);

     buff[1] = 0x0F & buff[1];
     adcValue = ( buff[1] << 8) | buff[2];

     digitalWrite_pin_25(1);  // High : CS InactiveFsp

     return adcValue;
 }



4-5) SS(GPIO25) Control

 void pin_25_Mode(int mode)
 {
	int fSel, shift, alt ;

	fSel    = 2;
      shift   = 15;

	if (mode == INPUT){
		*(gpio + fSel ) = (*(gpio + fSel) & ~(7 << shift)) ; // Sets bits to     zero = input
	}
	else if (mode == OUTPUT){
		*(gpio + fSel ) = (*(gpio + fSel) & ~(7 << shift)) | (1 << shift) ;
		printf("OUTPUT\n");
	}
}

void digitalWrite_pin_25(int value)
{
     int pin =25;
     int gpCLR = 10;
     int gpSET = 7;

     if (value == LOW)
         *(gpio + gpCLR) = 1 << (pin & 31) ;
     else
         *(gpio + gpSET) = 1 << (pin & 31) ;
}


5) 실습


5-1) 회로 연결SPI.jpg


5-2) C파일을 scp명령어를 이용하여 라즈베리파이로 보낸다.

ex) scp spi_control.c root@<RaspberryPi's IP Address>:/


5-3) 라즈베리파이에서 gcc를 이용하여 컴파일한다.

ex) gcc -o spi_control spi_control.c


5-4) 어플리케이션 실행한다.

ex) ./spi_control


온도센서에 라이터를 갖다대어 터미널에 출력을 확인한다.


spi_result.png


출처 : NCLab RaspberryPi강의노트 

Chapter06_라즈베리파이_GPIO&SPI_센서연동실습.ver1.0


위로