SPI (Serial Peripheral Interface), the following are some features supported by the Linux 4.4 SPI driver:
Motorola SPI protocol is used by default
Supports 8-bit and 16-bit
Software clock frequency and transfer rate up to 50MHz
Support SPI 4 transmission mode configurations
Each SPI controller supports one to two chip selects
Youyeetoo R1, a rk3588s motherboard developed by Hot Wheels Technology, provides 40pin expansion pins, including 1 spi pin. The pin distribution is shown in the figure below:
Pin Interface
Function | Pin |
---|---|
cs | 14 feet |
CLK | 23 feet |
MOSI | 21 feet |
MISO | 19 feet |
Hardware Connection
Short-circuit MOSI
and MISO
, as shown in the figure:
If you want to use shell commands in the Android system, you need to enter the Android command line window through ADB. For tutorials on using ADB, please refer to the adb debugging chapter. After opening the Android system command line through adb, you need to export the pwm device to user space first to operate the pwm pins.
adb shell setprop persist.sys.root_access 3
adb root
adb remount
adb shell
cat >> /system/bin/setup.sh << EOF
chmod 666 /dev/spidev0.0
EOF
The permission management of the Android system is very strict. Using c++ or java to operate files or resources in the Android root file system requires corresponding permissions. The ndk program written here is written based on the system app as a template. For system app, please refer to the chapter Creating system app.
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "spidev.h"
#include <android/log.h>
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "Tag", __VA_ARGS__)
#define SPI_DEV0_0 "/dev/spidev0.0"
static char tx_buffer[100] = "hello the world !";
static char rx_buffer[100];
static int fd; // SPI 控制引脚的设备文件描述符
static unsigned int mode = SPI_MODE_2; //用于保存 SPI 工作模式
static unsigned char bits = 8; // 接收、发送数据位数
static unsigned int speed = 500000; // 发送速度
static unsigned short delay; //保存延时时间
//spi初始化
static int spi_init(void);
//spi发送数据
static int transfer(int fd, char *tx, char *rx, unsigned int len);
extern "C" JNIEXPORT jstring JNICALL
Java_com_youyeetoo_r1_1spi_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
int ret;
int i;
ret = spi_init();
if( -1 == ret )
{
return env->NewStringUTF("spi_init error\n");
}
ret = transfer(fd, tx_buffer, rx_buffer, sizeof(tx_buffer));
if (-1 == ret )
{
return env->NewStringUTF("transfer error...\n");
}
/*打印 tx_buffer 和 rx_buffer*/
return env->NewStringUTF(rx_buffer);
}
int transfer(int fd, char *tx, char *rx, unsigned int len)
{
int ret;
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,
};
ret = ioctl(fd,SPI_IOC_MESSAGE(1),&tr);
if( ret == -1 )
{
LOGI("transfer fail\n");
return -1;
}
LOGI("transfer success\n");
return 0;
}
int spi_init(void)
{
int ret;
fd = open(SPI_DEV0_0, O_RDWR);
if(fd < 0)
{
perror("/dev/spidev0.0");
return -1;
}
//设置spi工作模式
ret = ioctl(fd,SPI_IOC_RD_MODE,&mode);
if( ret == -1)
{
printf("SPI_IOC_RD_MODE error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_MODE,&mode);
if( ret == -1)
{
printf("SPI_IOC_WR_MODE error......\n ");
goto fd_close;
}
//设置SPI通信的字长
ret = ioctl(fd,SPI_IOC_RD_BITS_PER_WORD,&bits);
if( ret == -1)
{
printf("SPI_IOC_RD_BITS_PER_WORD error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,&bits);
if( ret == -1)
{
printf("SPI_IOC_WR_BITS_PER_WORD error......\n ");
goto fd_close;
}
//设置SPI最高工作频率
ret = ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,&speed);
if( ret == -1)
{
printf("SPI_IOC_WR_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
ret = ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ,&speed);
if( ret == -1)
{
printf("SPI_IOC_RD_MAX_SPEED_HZ error......\n ");
goto fd_close;
}
printf("spi mode: 0x%x\n", mode);
printf("bits per word: %d\n", bits);
printf("max speed: %d Hz (%d KHz)\n", speed, speed / 1000);
return 0;
fd_close:
close(fd);
return -1;
}
Create a header file spidev.h
in the same directory as native-lib.cpp
with the following content:
/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
/*
* include/linux/spi/spidev.h
*
* Copyright (C) 2006 SWAPP
* Andrea Paterniani <a.paterniani@swapp-eng.it>
*
* 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, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef SPIDEV_H
#define SPIDEV_H
#include <linux/types.h>
#include <linux/ioctl.h>
/* User space versions of kernel symbols for SPI clocking modes,
* matching <linux/spi/spi.h>
*/
#define SPI_CPHA 0x01
#define SPI_CPOL 0x02
#define SPI_MODE_0 (0|0)
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04
#define SPI_LSB_FIRST 0x08
#define SPI_3WIRE 0x10
#define SPI_LOOP 0x20
#define SPI_NO_CS 0x40
#define SPI_READY 0x80
#define SPI_TX_DUAL 0x100
#define SPI_TX_QUAD 0x200
#define SPI_RX_DUAL 0x400
#define SPI_RX_QUAD 0x800
#define SPI_CS_WORD 0x1000
#define SPI_TX_OCTAL 0x2000
#define SPI_RX_OCTAL 0x4000
#define SPI_3WIRE_HIZ 0x8000
/*---------------------------------------------------------------------------*/
/* IOCTL commands */
#define SPI_IOC_MAGIC 'k'
/**
* struct spi_ioc_transfer - describes a single SPI transfer
* @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
* If no data is provided, zeroes are shifted out.
* @rx_buf: Holds pointer to userspace buffer for receive data, or null.
* @len: Length of tx and rx buffers, in bytes.
* @speed_hz: Temporary override of the device's bitrate.
* @bits_per_word: Temporary override of the device's wordsize.
* @delay_usecs: If nonzero, how long to delay after the last bit transfer
* before optionally deselecting the device before the next transfer.
* @cs_change: True to deselect device before starting the next transfer.
* @word_delay_usecs: If nonzero, how long to wait between words within one
* transfer. This property needs explicit support in the SPI controller,
* otherwise it is silently ignored.
*
* This structure is mapped directly to the kernel spi_transfer structure;
* the fields have the same meanings, except of course that the pointers
* are in a different address space (and may be of different sizes in some
* cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
* Zero-initialize the structure, including currently unused fields, to
* accommodate potential future updates.
*
* SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
* Pass it an array of related transfers, they'll execute together.
* Each transfer may be half duplex (either direction) or full duplex.
*
* struct spi_ioc_transfer mesg[4];
* ...
* status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
*
* So for example one transfer might send a nine bit command (right aligned
* in a 16-bit word), the next could read a block of 8-bit data before
* terminating that command by temporarily deselecting the chip; the next
* could send a different nine bit command (re-selecting the chip), and the
* last transfer might write some register values.
*/
struct spi_ioc_transfer {
unsigned long* tx_buf;
unsigned long* rx_buf;
unsigned int len;
unsigned int speed_hz;
unsigned short delay_usecs;
unsigned char bits_per_word;
unsigned char cs_change;
unsigned char tx_nbits;
unsigned char rx_nbits;
unsigned char word_delay_usecs;
unsigned char pad;
/* If the contents of 'struct spi_ioc_transfer' ever change
* incompatibly, then the ioctl number (currently 0) must change;
* ioctls with constant size fields get a bit more in the way of
* error checking than ones (like this) where that field varies.
*
* NOTE: struct layout is the same in 64bit and 32bit userspace.
*/
};
/* not all platforms use <asm-generic/ioctl.h> or _IOC_TYPECHECK() ... */
#define SPI_MSGSIZE(N) \
((((N)*(sizeof (struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
? ((N)*(sizeof (struct spi_ioc_transfer))) : 0)
#define SPI_IOC_MESSAGE(N) _IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
/* Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) (limited to 8 bits) */
#define SPI_IOC_RD_MODE _IOR(SPI_IOC_MAGIC, 1, unsigned char)
#define SPI_IOC_WR_MODE _IOW(SPI_IOC_MAGIC, 1, unsigned char)
/* Read / Write SPI bit justification */
#define SPI_IOC_RD_LSB_FIRST _IOR(SPI_IOC_MAGIC, 2, unsigned char)
#define SPI_IOC_WR_LSB_FIRST _IOW(SPI_IOC_MAGIC, 2, unsigned char)
/* Read / Write SPI device word length (1..N) */
#define SPI_IOC_RD_BITS_PER_WORD _IOR(SPI_IOC_MAGIC, 3, unsigned char)
#define SPI_IOC_WR_BITS_PER_WORD _IOW(SPI_IOC_MAGIC, 3, unsigned char)
/* Read / Write SPI device default max speed hz */
#define SPI_IOC_RD_MAX_SPEED_HZ _IOR(SPI_IOC_MAGIC, 4, unsigned int)
#define SPI_IOC_WR_MAX_SPEED_HZ _IOW(SPI_IOC_MAGIC, 4, unsigned int)
/* Read / Write of the SPI mode field */
#define SPI_IOC_RD_MODE32 _IOR(SPI_IOC_MAGIC, 5, unsigned int)
#define SPI_IOC_WR_MODE32 _IOW(SPI_IOC_MAGIC, 5, unsigned int)
#endif /* SPIDEV_H */