This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
products:sbc:common:applications:gpio:spi [2022/06/29 21:51] 127.0.0.1 external edit |
products:sbc:common:applications:gpio:spi [2025/02/17 21:40] (current) nick [Enable SPI] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | # SPI | ||
- | Common part. | + | ===== SPI Reference table ===== |
- | <tabbox VIM1> | + | | |
+ | ^ VIM3/ | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
+ | ^ VIM4 | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
+ | ^ VIM1S | SPI_A | 29 | SPI_A_MOSI | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
+ | | ::: | ::: | ||
- | VIM1 | + | ===== Enable SPI ===== |
+ | |||
+ | In order to use the SPI, you need to enable the SPI function via [[products: | ||
+ | |||
+ | <tabbox VIM3/ | ||
+ | |||
+ | Edit ''/ | ||
+ | |||
+ | Take VIM3 as an example to enable '' | ||
+ | |||
+ | ```shell | ||
+ | overlays=spi1 | ||
+ | ``` | ||
+ | |||
+ | <WRAP important > | ||
+ | As SPI pins are also used for other function, e.g. UART_C, PWM_F, so that you also need to remove node '' | ||
+ | </ | ||
+ | |||
+ | |||
+ | After reboot, you will see the spi device node. | ||
+ | |||
+ | ```shell | ||
+ | $ ls / | ||
+ | / | ||
+ | ``` | ||
+ | |||
+ | <tabbox VIM3 with 5.15 kernel> | ||
+ | |||
+ | Edit ''/ | ||
+ | |||
+ | |||
+ | e.g. Enable '' | ||
+ | |||
+ | ```shell | ||
+ | fdt_overlays=spi1 | ||
+ | ``` | ||
+ | |||
+ | After reboot, you will see the SPI device node. | ||
+ | |||
+ | ```shell | ||
+ | $ ls / | ||
+ | / | ||
+ | ``` | ||
+ | |||
+ | <tabbox VIM3L with 5.15 kernel> | ||
+ | |||
+ | Edit ''/ | ||
+ | |||
+ | |||
+ | e.g. Enable '' | ||
+ | |||
+ | ```shell | ||
+ | fdt_overlays=spi1 | ||
+ | |||
+ | ``` | ||
+ | |||
+ | After reboot, you will see the SPI device node. | ||
+ | |||
+ | ```shell | ||
+ | $ ls / | ||
+ | / | ||
+ | ``` | ||
+ | |||
+ | <tabbox VIM4> | ||
+ | |||
+ | Edit ''/ | ||
+ | |||
+ | |||
+ | e.g. Enable '' | ||
+ | |||
+ | ```shell | ||
+ | fdt_overlays=spi0 | ||
+ | |||
+ | ``` | ||
+ | |||
+ | After reboot, you will see the SPI device node. | ||
+ | |||
+ | ```shell | ||
+ | $ ls / | ||
+ | / | ||
+ | ``` | ||
+ | <tabbox VIM1S> | ||
+ | |||
+ | Edit ''/ | ||
+ | |||
+ | |||
+ | e.g. Enable '' | ||
+ | |||
+ | ```shell | ||
+ | fdt_overlays=spi0 | ||
+ | |||
+ | ``` | ||
+ | |||
+ | After reboot, you will see the SPI device node. | ||
+ | |||
+ | ```shell | ||
+ | $ ls / | ||
+ | / | ||
+ | ``` | ||
- | <tabbox VIM2> | ||
- | VIM2 | ||
- | <tabbox VIM3> | ||
- | VIM3 | ||
- | <tabbox VIM3L> | ||
- | VIM3L | ||
- | <tabbox VIM4> | ||
- | VIM4 | ||
</ | </ | ||
+ | |||
+ | ===== Disable SPI ===== | ||
+ | |||
+ | If you want to use normal GPIO instead of SPI, you can remove the SPI node in [[products: | ||
+ | |||
+ | |||
+ | ===== Demo Source Code ===== | ||
+ | |||
+ | ```c spidev_test.c | ||
+ | // SPDX-License-Identifier: | ||
+ | /* | ||
+ | * SPI testing utility (using spidev driver) | ||
+ | * | ||
+ | * Copyright (c) 2007 MontaVista Software, Inc. | ||
+ | * Copyright (c) 2007 Anton Vorontsov < | ||
+ | * | ||
+ | * Cross-compile with cross-gcc -I/ | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) | ||
+ | |||
+ | static void pabort(const char *s) | ||
+ | { | ||
+ | perror(s); | ||
+ | abort(); | ||
+ | } | ||
+ | |||
+ | static const char *device = "/ | ||
+ | static uint32_t mode; | ||
+ | static uint8_t bits = 8; | ||
+ | static char *input_file; | ||
+ | static char *output_file; | ||
+ | static uint32_t speed = 500000; | ||
+ | static uint16_t delay; | ||
+ | static int verbose; | ||
+ | static int transfer_size; | ||
+ | static int iterations; | ||
+ | static int interval = 5; /* interval in seconds for showing transfer rate */ | ||
+ | |||
+ | uint8_t default_tx[] = { | ||
+ | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
+ | 0x40, 0x00, 0x00, 0x00, 0x00, 0x95, | ||
+ | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
+ | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
+ | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, | ||
+ | 0xF0, 0x0D, | ||
+ | }; | ||
+ | |||
+ | uint8_t default_rx[ARRAY_SIZE(default_tx)] = {0, }; | ||
+ | char *input_tx; | ||
+ | |||
+ | static void hex_dump(const void *src, size_t length, size_t line_size, | ||
+ | char *prefix) | ||
+ | { | ||
+ | int i = 0; | ||
+ | const unsigned char *address = src; | ||
+ | const unsigned char *line = address; | ||
+ | unsigned char c; | ||
+ | |||
+ | printf(" | ||
+ | while (length-- > 0) { | ||
+ | printf(" | ||
+ | if (!(++i % line_size) || (length == 0 && i % line_size)) { | ||
+ | if (length == 0) { | ||
+ | while (i++ % line_size) | ||
+ | printf(" | ||
+ | } | ||
+ | printf(" | ||
+ | while (line < address) { | ||
+ | c = *line++; | ||
+ | printf(" | ||
+ | } | ||
+ | printf(" | ||
+ | if (length > 0) | ||
+ | printf(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /* | ||
+ | | ||
+ | | ||
+ | */ | ||
+ | static int unescape(char *_dst, char *_src, size_t len) | ||
+ | { | ||
+ | int ret = 0; | ||
+ | int match; | ||
+ | char *src = _src; | ||
+ | char *dst = _dst; | ||
+ | unsigned int ch; | ||
+ | |||
+ | while (*src) { | ||
+ | if (*src == ' | ||
+ | match = sscanf(src + 2, " | ||
+ | if (!match) | ||
+ | pabort(" | ||
+ | |||
+ | src += 4; | ||
+ | *dst++ = (unsigned char)ch; | ||
+ | } else { | ||
+ | *dst++ = *src++; | ||
+ | } | ||
+ | ret++; | ||
+ | } | ||
+ | return ret; | ||
+ | } | ||
+ | |||
+ | static void transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len) | ||
+ | { | ||
+ | int ret; | ||
+ | int out_fd; | ||
+ | struct spi_ioc_transfer tr = { | ||
+ | .tx_buf = (unsigned long)tx, | ||
+ | .rx_buf = (unsigned long)rx, | ||
+ | .len = len, | ||
+ | .delay_usecs = delay, | ||
+ | .speed_hz = speed, | ||
+ | .bits_per_word = bits, | ||
+ | }; | ||
+ | |||
+ | if (mode & SPI_TX_QUAD) | ||
+ | tr.tx_nbits = 4; | ||
+ | else if (mode & SPI_TX_DUAL) | ||
+ | tr.tx_nbits = 2; | ||
+ | if (mode & SPI_RX_QUAD) | ||
+ | tr.rx_nbits = 4; | ||
+ | else if (mode & SPI_RX_DUAL) | ||
+ | tr.rx_nbits = 2; | ||
+ | if (!(mode & SPI_LOOP)) { | ||
+ | if (mode & (SPI_TX_QUAD | SPI_TX_DUAL)) | ||
+ | tr.rx_buf = 0; | ||
+ | else if (mode & (SPI_RX_QUAD | SPI_RX_DUAL)) | ||
+ | tr.tx_buf = 0; | ||
+ | } | ||
+ | |||
+ | ret = ioctl(fd, SPI_IOC_MESSAGE(1), | ||
+ | if (ret < 1) | ||
+ | pabort(" | ||
+ | |||
+ | if (verbose) | ||
+ | hex_dump(tx, | ||
+ | |||
+ | if (output_file) { | ||
+ | out_fd = open(output_file, | ||
+ | if (out_fd < 0) | ||
+ | pabort(" | ||
+ | |||
+ | ret = write(out_fd, | ||
+ | if (ret != len) | ||
+ | pabort(" | ||
+ | |||
+ | close(out_fd); | ||
+ | } | ||
+ | |||
+ | if (verbose) | ||
+ | hex_dump(rx, | ||
+ | } | ||
+ | static void print_usage(const char *prog) | ||
+ | { | ||
+ | printf(" | ||
+ | puts(" | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | exit(1); | ||
+ | } | ||
+ | |||
+ | static void parse_opts(int argc, char *argv[]) | ||
+ | { | ||
+ | while (1) { | ||
+ | static const struct option lopts[] = { | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { " | ||
+ | { NULL, 0, 0, 0 }, | ||
+ | }; | ||
+ | int c; | ||
+ | |||
+ | c = getopt_long(argc, | ||
+ | lopts, NULL); | ||
+ | |||
+ | if (c == -1) | ||
+ | break; | ||
+ | |||
+ | switch (c) { | ||
+ | case ' | ||
+ | device = optarg; | ||
+ | break; | ||
+ | case ' | ||
+ | speed = atoi(optarg); | ||
+ | break; | ||
+ | case ' | ||
+ | delay = atoi(optarg); | ||
+ | break; | ||
+ | case ' | ||
+ | bits = atoi(optarg); | ||
+ | break; | ||
+ | case ' | ||
+ | input_file = optarg; | ||
+ | break; | ||
+ | case ' | ||
+ | output_file = optarg; | ||
+ | break; | ||
+ | case ' | ||
+ | mode |= SPI_LOOP; | ||
+ | break; | ||
+ | case ' | ||
+ | mode |= SPI_CPHA; | ||
+ | break; | ||
+ | case ' | ||
+ | mode |= SPI_CPOL; | ||
+ | break; | ||
+ | case ' | ||
+ | mode |= SPI_LSB_FIRST; | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_CS_HIGH; | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_3WIRE; | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_NO_CS; | ||
+ | | ||
+ | case ' | ||
+ | | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_READY; | ||
+ | | ||
+ | case ' | ||
+ | | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_TX_DUAL; | ||
+ | | ||
+ | case ' | ||
+ | mode |= SPI_TX_QUAD; | ||
+ | | ||
+ | case ' | ||
+ | | ||
+ | | ||
+ | case ' | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | if (mode & SPI_LOOP) { | ||
+ | if (mode & SPI_TX_DUAL) | ||
+ | mode |= SPI_RX_DUAL; | ||
+ | if (mode & SPI_TX_QUAD) | ||
+ | mode |= SPI_RX_QUAD; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | { | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | tx = malloc(size); | ||
+ | if (!tx) | ||
+ | | ||
+ | |||
+ | rx = malloc(size); | ||
+ | if (!rx) | ||
+ | | ||
+ | |||
+ | size = unescape((char *)tx, str, size); | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | |||
+ | static void transfer_file(int fd, char *filename) | ||
+ | { | ||
+ | ssize_t bytes; | ||
+ | struct stat sb; | ||
+ | int tx_fd; | ||
+ | uint8_t *tx; | ||
+ | uint8_t *rx; | ||
+ | |||
+ | if (stat(filename, | ||
+ | pabort(" | ||
+ | |||
+ | tx_fd = open(filename, | ||
+ | if (tx_fd < 0) | ||
+ | pabort(" | ||
+ | |||
+ | tx = malloc(sb.st_size); | ||
+ | if (!tx) | ||
+ | pabort(" | ||
+ | |||
+ | rx = malloc(sb.st_size); | ||
+ | if (!rx) | ||
+ | pabort(" | ||
+ | |||
+ | bytes = read(tx_fd, tx, sb.st_size); | ||
+ | if (bytes != sb.st_size) | ||
+ | pabort(" | ||
+ | |||
+ | transfer(fd, | ||
+ | free(rx); | ||
+ | free(tx); | ||
+ | close(tx_fd); | ||
+ | } | ||
+ | |||
+ | static uint64_t _read_count; | ||
+ | static uint64_t _write_count; | ||
+ | |||
+ | static void show_transfer_rate(void) | ||
+ | { | ||
+ | static uint64_t prev_read_count, | ||
+ | double rx_rate, tx_rate; | ||
+ | |||
+ | rx_rate = ((_read_count - prev_read_count) * 8) / (interval*1000.0); | ||
+ | tx_rate = ((_write_count - prev_write_count) * 8) / (interval*1000.0); | ||
+ | |||
+ | printf(" | ||
+ | |||
+ | prev_read_count = _read_count; | ||
+ | prev_write_count = _write_count; | ||
+ | } | ||
+ | |||
+ | static void transfer_buf(int fd, int len) | ||
+ | { | ||
+ | uint8_t *tx; | ||
+ | uint8_t *rx; | ||
+ | int i; | ||
+ | |||
+ | tx = malloc(len); | ||
+ | if (!tx) | ||
+ | pabort(" | ||
+ | for (i = 0; i < len; i++) | ||
+ | tx[i] = random(); | ||
+ | |||
+ | rx = malloc(len); | ||
+ | if (!rx) | ||
+ | pabort(" | ||
+ | |||
+ | transfer(fd, | ||
+ | |||
+ | _write_count += len; | ||
+ | _read_count += len; | ||
+ | |||
+ | if (mode & SPI_LOOP) { | ||
+ | if (memcmp(tx, rx, len)) { | ||
+ | fprintf(stderr, | ||
+ | hex_dump(tx, | ||
+ | hex_dump(rx, | ||
+ | exit(1); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | free(rx); | ||
+ | free(tx); | ||
+ | } | ||
+ | int main(int argc, char *argv[]) | ||
+ | { | ||
+ | int ret = 0; | ||
+ | int fd; | ||
+ | |||
+ | parse_opts(argc, | ||
+ | |||
+ | fd = open(device, | ||
+ | if (fd < 0) | ||
+ | pabort(" | ||
+ | |||
+ | /* | ||
+ | * spi mode | ||
+ | */ | ||
+ | ret = ioctl(fd, SPI_IOC_WR_MODE32, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | ret = ioctl(fd, SPI_IOC_RD_MODE32, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | /* | ||
+ | * bits per word | ||
+ | */ | ||
+ | ret = ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | ret = ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | /* | ||
+ | * max speed hz | ||
+ | */ | ||
+ | ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | ret = ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, | ||
+ | if (ret == -1) | ||
+ | pabort(" | ||
+ | |||
+ | printf(" | ||
+ | printf(" | ||
+ | printf(" | ||
+ | |||
+ | if (input_tx && input_file) | ||
+ | pabort(" | ||
+ | |||
+ | if (input_tx) | ||
+ | transfer_escaped_string(fd, | ||
+ | else if (input_file) | ||
+ | transfer_file(fd, | ||
+ | else if (transfer_size) { | ||
+ | struct timespec last_stat; | ||
+ | |||
+ | clock_gettime(CLOCK_MONOTONIC, | ||
+ | |||
+ | while (iterations-- > 0) { | ||
+ | struct timespec current; | ||
+ | |||
+ | transfer_buf(fd, | ||
+ | |||
+ | clock_gettime(CLOCK_MONOTONIC, | ||
+ | if (current.tv_sec - last_stat.tv_sec > interval) { | ||
+ | show_transfer_rate(); | ||
+ | last_stat = current; | ||
+ | } | ||
+ | } | ||
+ | printf(" | ||
+ | | ||
+ | } else | ||
+ | transfer(fd, | ||
+ | |||
+ | close(fd); | ||
+ | |||
+ | return ret; | ||
+ | } | ||
+ | |||
+ | ``` | ||
+ | |||
+ | Compile test code: | ||
+ | |||
+ | ```shell | ||
+ | $ gcc -o spidev_test spidev_test.c | ||
+ | ``` | ||
+ | |||
+ | ===== Test demonstration ===== | ||
+ | |||
+ | Prepare input text file: | ||
+ | |||
+ | ```shell | ||
+ | $ echo " | ||
+ | ``` | ||
+ | |||
+ | Connect PIN36(MOSI), | ||
+ | |||
+ | |||
+ | |||
+ | ```shell | ||
+ | $ ./ | ||
+ | spi mode: 0x0 | ||
+ | bits per word: 8 | ||
+ | max speed: 500000 Hz (500 KHz) | ||
+ | ``` | ||
+ | |||
+ | <WRAP important > | ||
+ | Please choose the correct spi device node. | ||
+ | </ | ||
+ | |||
+ | |||
+ | Check output data: | ||
+ | |||
+ | ```shell | ||
+ | $ cat ./ | ||
+ | Amazing Khadas! | ||
+ | ``` |