본문 바로가기

임베디드

SPI 포트 기능 테스트

Allwinner S3/i3에는 SPI 통신 장치가 포함되어 있으며 출력과 입력 포트에 각각 64바이트의 송수신 FIFO 버퍼가 있다.

UART와는 다르게 CLOCK신호에 동기되어 데이터 송수신이 가능하므로 빠르고 신뢰도가 높은 장치간의 통신을 구현할 수 있다.

고속으로 많은 데이터 통신이 필요한 복잡한 센서나 소형 컬러 TFT LCD를 구동하는데 많이 사용된다.

 

SPI 포트는 MOSI(Master Out Slave In) 데이터 출력, MISO(Master In Slave Out) 데이터 입력, MCLK(Master Clock) 클럭 출력이 있다. 추가로 Slave의 Chip-select를 SS(Slave Select) 출력신호가 있다.

CLOCK의 위상과 데이터 동기를 상승 시점이냐 하강 시점에 따라 4가지 모드로 나누어 진다.

 

Kernel land가 아닌 User land에서 SPI 통신을 사용하려고 디바이스 트리 파일에 아래 내용을 추가하였다.

&spi0 {
       pinctrl-names = "default"; 
       pinctrl-0 = <&spi0_pins>; 
       status = "okay";
       #address-cells = <1>;
       #size-cells = <0>;
       spidev@0{
               compatible = "rohm,dh2228fv";
               reg = <0>;
               spi-max-frequency = <1000000>;
       };
};

호환 장치 이름이 "rohm,dh2228fv" 인것이 이상하지 않은가?   "spidev"라고 하면 깔끔할텐데 말이다.

커널 드라이버의 spidev.c 코드에 장치명 리스트가 아래와 같기 때문이다.

RPi 커널처럼 리스트에 "spidev"를 추가하는 패치를 넣고 싶지만, 앞으로 나올 업스트림 커널을 쉽게 포팅하기 위해 꾹 참았다.

static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "rohm,dh2228fv" },
	{ .compatible = "lineartechnology,ltc2488" },
	{ .compatible = "ge,achc" },
	{ .compatible = "semtech,sx1301" },
	{ .compatible = "lwn,bk4" },
	{ .compatible = "dh,dhcom-board" },
	{ .compatible = "menlo,m53cpld" },
	{},
};

Kernel의 .config에는 "CONFIG_SPI_SPIDEV=y"로 변경하여 해당 드러아버를 포함시켰다.

이렇게 변경을 하고 부팅하면 /dev/spidev0.0이라는 장치 노드가 보일것이다.

 

C언어로 간단한 SPI 통신 테스트를 해보자. MOSI와 MISO 핀을 연결하여 루프백 테스트를 한다.

테스트 프로그램의 소스코드는 아래와 같다.

/*
 * SPI testing utility (using spidev driver)
 *
 * Copyright (c) 2007  MontaVista Software, Inc.
 * Copyright (c) 2007  Anton Vorontsov <avorontsov@ru.mvista.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License.
 *
 * Cross-compile with cross-gcc -I/path/to/cross-kernel/include
 */
 
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
 
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
 
static void pabort(const char *s)
{
	perror(s);
	abort();
}
 
static const char *device = "/dev/spidev1.1";
static uint8_t mode;
static uint8_t bits = 8;
static uint32_t speed = 500000;
static uint16_t delay;
 
static void transfer(int fd)
{
	int ret;
	uint8_t tx[] = {
		0x01, 0x02, 0x03, 0x04,
	};
	uint8_t rx[ARRAY_SIZE(tx)] = {0, };
	struct spi_ioc_transfer tr;
	
	memset(&tr, 0, sizeof(tr));
	tr.tx_buf = (unsigned long)tx;
	tr.rx_buf = (unsigned long)rx;
	tr.len = ARRAY_SIZE(tx);
	tr.delay_usecs = delay;
	tr.speed_hz = speed;
	tr.bits_per_word = bits;
	tr.cs_change = 0;

	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret < 1)
		pabort("can't send spi message");
 
	for (ret = 0; ret < ARRAY_SIZE(tx); ret++) {
		if (!(ret % 6))
			puts("");
		printf("%.2X ", rx[ret]);
	}
	puts("");
}
 
static void print_usage(const char *prog)
{
	printf("Usage: %s [-DsbdlHOLC3]\n", prog);
	puts("  -D --device   device to use (default /dev/spidev1.1)\n"
	     "  -s --speed    max speed (Hz)\n"
	     "  -d --delay    delay (usec)\n"
	     "  -b --bpw      bits per word \n"
	     "  -l --loop     loopback\n"
	     "  -H --cpha     clock phase\n"
	     "  -O --cpol     clock polarity\n"
	     "  -L --lsb      least significant bit first\n"
	     "  -C --cs-high  chip select active high\n"
	     "  -3 --3wire    SI/SO signals shared\n");
	exit(1);
}
 
static void parse_opts(int argc, char *argv[])
{
	while (1) {
		static const struct option lopts[] = {
			{ "device",  1, 0, 'D' },
			{ "speed",   1, 0, 's' },
			{ "delay",   1, 0, 'd' },
			{ "bpw",     1, 0, 'b' },
			{ "loop",    0, 0, 'l' },
			{ "cpha",    0, 0, 'H' },
			{ "cpol",    0, 0, 'O' },
			{ "lsb",     0, 0, 'L' },
			{ "cs-high", 0, 0, 'C' },
			{ "3wire",   0, 0, '3' },
			{ "no-cs",   0, 0, 'N' },
			{ "ready",   0, 0, 'R' },
			{ NULL, 0, 0, 0 },
		};
		int c;
 
		c = getopt_long(argc, argv, "D:s:d:b:lHOLC3NR", lopts, NULL);
 
		if (c == -1)
			break;
 
		switch (c) {
		case 'D':
			device = optarg;
			break;
		case 's':
			speed = atoi(optarg);
			break;
		case 'd':
			delay = atoi(optarg);
			break;
		case 'b':
			bits = atoi(optarg);
			break;
		case 'l':
			mode |= SPI_LOOP;
			break;
		case 'H':
			mode |= SPI_CPHA;
			break;
		case 'O':
			mode |= SPI_CPOL;
			break;
		case 'L':
			mode |= SPI_LSB_FIRST;
			break;
		case 'C':
			mode |= SPI_CS_HIGH;
			break;
		case '3':
			mode |= SPI_3WIRE;
			break;
		case 'N':
			mode |= SPI_NO_CS;
			break;
		case 'R':
			mode |= SPI_READY;
			break;
		default:
			print_usage(argv[0]);
			break;
		}
	}
}
 
int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;
 
	parse_opts(argc, argv);
 
	fd = open(device, O_RDWR);
	if (fd < 0)
		pabort("can't open device");
 
	/*
	 * spi mode
	 */
	ret = ioctl(fd, SPI_IOC_WR_MODE, &mode);
	if (ret == -1)
		pabort("can't set spi mode");
 
	ret = ioctl(fd, SPI_IOC_RD_MODE, &mode);
	if (ret == -1)
		pabort("can't get spi mode");
 
	/*
	 * bits per word
	 */
	ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't set bits per word");
 
	ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &bits);
	if (ret == -1)
		pabort("can't get bits per word");
 
	/*
	 * max speed hz
	 */
	ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't set max speed hz");
 
	ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &speed);
	if (ret == -1)
		pabort("can't get max speed hz");
 
	printf("spi mode: %d\n", mode);
	printf("bits per word: %d\n", bits);
	printf("max speed: %d Hz (%d KHz)\n", speed, speed/1000);
 
	transfer(fd);
 
	close(fd);
 
	return ret;
}

테스트 소스코드를 타켓 보드에서 직접 다운로드 받아 컴파일하고 프로그램 사용 방법에 대한 help 화면도 보자.

# wget http://dn.odroid.com/Accessory/examples/spidev_test.c
# gcc -o spidev_test spidev_test.c
# ./spidev_test --help
./spidev_test: unrecognized option '--help'
Usage: ./spidev_test [-DsbdlHOLC3]
  -D --device   device to use (default /dev/spidev1.1)
  -s --speed    max speed (Hz)
  -d --delay    delay (usec)
  -b --bpw      bits per word 
  -l --loop     loopback
  -H --cpha     clock phase
  -O --cpol     clock polarity
  -L --lsb      least significant bit first
  -C --cs-high  chip select active high
  -3 --3wire    SI/SO signals shared

MISO핀과 MOSI핀을 연결하지 않은 상태로 테스트 하면 아래와 같은 결과가 나온다.

# ./spidev_test -D /dev/spidev0.0 -s 1000000
spi mode: 0
bits per word: 8
max speed: 1000000 Hz (1000 KHz)

00 00 00 00

두 핀을 연결한 상태에서는 아래와 같이 1, 2, 3, 4 순서대로 보낸 데이터가 그대로 수신되어야 정상이다.

# ./spidev_test -D /dev/spidev0.0 -s 1000000
spi mode: 0
bits per word: 8
max speed: 1000000 Hz (1000 KHz)

01 02 03 04 

위 테스트에서는 1Mbps 속도로 전송을 해보았는데 최대 얼마나 빠르게 가능한지 궁금하다.

PLL 및 관련 clock 설정 register들로 가늠해보면 200Mhz가능한것 갖지만, 실제 안정적인 통신은 20Mhz정도로 예상한다.

 

여기까지 변경한 관련 설정도 모두 Github에 commit하였다.

https://github.com/foxnux/linux/commit/6caee30eaa3a2ce4e086fcae7a7f0a185855d2b7

 

다음에는 무선랜 통신에 도전해 보자.

 

'임베디드' 카테고리의 다른 글

쾌속 부팅을 향한 첫 걸음  (0) 2020.01.13
USB 무선랜 드라이버 구동  (0) 2020.01.11
UART 포트 기능 테스트  (0) 2020.01.10
Buildroot 테스트  (0) 2020.01.06
ADC 키패드 입력 테스트  (0) 2020.01.06