본문 바로가기
미니 프로젝트/라즈베리파이 REST API for SPI

C와 python 연결하기 - ctypes

by 시샐 2023. 5. 9.
네트워크 API를 스터디하고 테스트하는 가장 간단한 방법은 python과 적절한 모듈을 사용하는 것이라고 생각되었다.
 
문제는 SPI를 구동시키는 라이브러리가 C로 되어 있으므로 Python과 C라는 이종의 코드를 서로 연결해서 사용해야 한다는 점인데, 요즘 시대가 어떤 시대인데 당연히 방법이 있을 것으로 생각했다. 
 
알아보니 여러가지 방법이 있는데, 가장 깔끔해 보이는 것은 C로 만든 라이브러리를 그대로 불러다 쓸 수 있는 ctypes라는 것이었다.

 

주요 내용은 다음 사이트에 잘 정리되어 있다.
 
 
여기서 일차적으로 문제점은, ctypes는 동적 라이브러리(.so)를 링크하여 사용하는 것인데, 기존에 사용한 라이브러리(spidev_lib)가 스태틱 라이브러리(.a) 로 만들어 져 있다는 것이었다. 먼저 이를 동적 라이브러리로 수정할 필요가 생겼다.
 
기존 라이브러리는 cmake를 이용해 만들어 진 것인데, 동적 라이브러리로 변경하려면 CMakeLists.txt의 일부 내용을 수정해야 한다. 그 내용 중,

 

 

add_library(spidev-lib src/spidev_lib.c)
add_library(spidev-lib++ src/spidev_lib++.cc)
 
이렇게 되어있는 부분을 다음과 같이 수정한다. (중간에 SHARED)를 추가한다
add_library(spidev-lib SHARED src/spidev_lib.c)
add_library(spidev-lib++ SHARED src/spidev_lib++.cc)

 

 
build 디렉토리로 가서 다시 make 과정을 해준다.
cd build
cmake ..
make
make install

 

 libspidev-lib.so, libspidev-lib++.so가 만들어 진 것을 확인할 수 있다.
이 파일은 /usr/local/lib에 복사되어 있다.
 
그런데 파이선에서 c 함수를 부르는데, 어떻게 해야할 지 감이 안오는 부분이 여러가지 있었다.

 

이를테면 spi_open() 함수는 파라미터로 포인터와 구조체를 넘겨야한다. 
 
spi_xfer() 함수에서도 버퍼로 쓰는 배열에 대한 포인터를 넘기게 되어있다.
 
이런 경우 각각 어떻게 처리해야 하는지 잘 모르는 상태에서, 실제 디바이스를 연결하여 테스트하는 것은 별로 좋은 방법이 아닐 것 같다는 생각이 들었다.
 
일단 spidev 라이브러리의 내용을 수정해서 파라미터가 잘 넘어 오는지만 확인하는 것이 좋지않을까?
그래서 일단 라이브러리의 코드를 다음과 같이 수정하였다.
 
//spidev_lib.c
...

int spi_open(char *device, spi_config_t config){
#if 0
	//기존 코드
	...
#else
	return config.bits_per_word;
}

int spi_xfer(int fd, uint8_t *tx_buffer, uint8_t tx_len, uint8_t *rx_buffer, uint8_t rx_len){
#if 0
	//기존 코드
	...
#else
	return tx_buffer[0];
}
 
뭐 이런 식으로 기존 코드를 모두 막고 파라미터로 받은 내용을 리턴한 후 파이선에서 출력하면,
파라미터가 제대로 전달되는지 확인할 수 있을 것이다. 
 

 

이제 테스트를 위한 파이선 코드를 작성할 차례이다. 
실제로는 라즈베리파이 python 프람프트에서 한 줄 한 줄 확인을 해보면서 .py 파일을 만들어 나갔다.
 
 
1. Structure 만들기

SPI 라이브러리에서는 초기 설정을 위해서 다음과 같은 구조체를 사용하고 있다.

 

typedef struct{
    uint8_t mode;
    uint8_t bits_per_word;
    uint32_t speed;
    uint16_t delay;
} spi_config_t;

 

파이썬 ctypes에서는 C와 연동을 위한 구조체를 class로 만들도록 되어있다. 상세한 내용은 상단의 파이썬 문서에 잘 정리되어 있는데, 쉽게 이해가 되는 내용은 아닌 것 같다. 

 

아무튼 어찌어찌해서 다음과 같은 파이썬 테스트 코드를 만들게 되었다.

 

from ctypes import *

class SpiConfig(Structure):
    _fields_ = [("mode", c_ubyte),
        ("bits_per_word", c_ubyte),
        ("speed", c_ulong),
        ("delay", c_uint)]
        
spi = CDLL("/usr/local/lib/libspidev-lib.so")
sdev = create_string_buffer(b"/dev/spidev0.0")
config = SpiCOnfig(0, 8, 4000000, 0)

fd = spi.spi_open(byref(sdev), config)
print(fd)

 

이 부분에서 문서를 잘 읽고 주의를 기울인 부분은 

 

1) 각 변수의 타입 설정하는 법  c_ubyte, ...

2) 스트링의 메모리를 할당 하는 법  create_string_buffer()

3) 함수 파라미터로 포인터 전달 하는 방법 byref()

 

등등 이었다.

 

spi_open() 함수를 호출하였고 일단 파라미터는 잘 전달 된 것을 확인했다.

시스템마다 변수 타입에 따라 그 크기가 다를 수 있기 때문에 이 부분이 어떻게 처리되는지 걱정이 됐는데 별 문제는 없었다. 

 

2. 배열 처리

 

다음과제는 실제 전송을 하는 부분인 spi_xfer()였는데, 이 함수의 파라미터에는 송수신을 위한 두 개의 버퍼가 사용되고 있다. 역시 문서를 참고해서 각각의 크기가 64개인 버퍼를 만들고 파라미터로 넘기는 테스트를 진행했다.

 

Buffer = c_ubyte * 64
TxBuffer = Buffer()
RxBuffer = Buffer()

TxBuffer[0] = 56
TxBuffer[1] = 78

ret = spi.spi_xfer(0, byref(TxBuffer), 2, byref(RxBuffer), 2)
print(ret)

 

Buffer는 64 바이트 크기의 배열 타입을 정의한 것이고,

이를 이용해서 TxBuffer와 RxBuffer를 만들었다. 테스트를 위해서 특정한 값을 써놓고 리턴값으로 받아 확인했는데, 역시 별 문제 없이 잘 되었다.

 

중요한 두 개의 함수를 부르는 데 큰 문제가 없는 듯 하니 이 방법으로 코딩을 계속 진행하기로 하였다. 

 

 

 

  

반응형